merge exports branch into calendar branch

This commit is contained in:
Julie Lenaerts 2022-09-07 10:11:43 +02:00
commit 8efb874742
397 changed files with 14547 additions and 10913 deletions

View File

@ -16,6 +16,8 @@ and this project adheres to
* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
* [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)
## Test releases

View File

@ -10,17 +10,17 @@
Exports
*******
Export is an important issue for the Chill software : users should be able to :
Export is an important issue within the Chill software : users should be able to :
- compute statistics about their activity ;
- list "things" which make part of their activities.
- list "things" which are a part of their activities.
The `main bundle`_ provides a powerful framework to build custom queries with re-usable parts across differents bundles.
.. contents:: Table of content
:local:
.. seealso::
.. seealso::
`The issue where this framework was discussed <https://git.framasoft.org/Chill-project/Chill-Main/issues/9>`_
Provides some information about the pursued features and architecture.
@ -32,37 +32,37 @@ Concepts
Some vocabulary: 3 "Export elements"
------------------------------------
Four terms are used for this framework :
Four terms are used for this framework :
exports
provides some basic operation on the date. Two kind of exports are available :
Exports
provide some basic operation on the data. Two kinds of exports are available :
- computed data : it may be "the number of people", "the number of activities", "the duration of activities", ...
- list data : it may be "the list of people", "the list of activity", ...
- list data : it may be "the list of people", "the list of activities", ...
filters
The filters make a filter on the date: it removes some information the user doesn't want to introduce in the computation done by export. In other word, filters make a filter...
Filters
The filters create a filter on the data: it removes some information the user doesn't want to introduce in the computation done by the export.
Example of filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ...
Example of a filter: "people under 18 years olds", "activities between the 1st of June and the 31st December", ...
aggregators
The aggregator aggregates the data into some group (some software use the term 'bucket').
Aggregators
The aggregator aggregates the data into some group (some software use the term 'bucket').
Example of aggregator : "group people by gender", "group people by nationality", "group activity by type", ...
Example of an aggregator : "group people by gender", "group people by nationality", "group activity by type", ...
formatters
The formatters format the data into a :class:`Symfony\Component\HttpFoundation\Response`, which will be returned "as is" by the controller to the web client.
Formatters
The formatters format the data into a :class:`Symfony\Component\HttpFoundation\Response`, which will be returned "as is" by the controller to the web client.
Example of formatter: "format data as CSV", "format data as ods spreadsheet", ...
Example of a formatter: "format data as CSV", "format data as an ods spreadsheet", ...
Anatomy of an export
---------------------
An export may be explained as a sentence, where each part of this sentence refers to one or multiple exports element. Examples :
An export can be thought of as a sentence where each part of this sentence refers to one or multiple export elements. Examples :
**Example 1**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender, and format them in a CSV spreadsheet.
Here :
Here :
- *count the number of people* is the export part
- *having at least one activity* is the filter part
@ -72,10 +72,10 @@ Here :
Note that :
- aggregators, filters, exports and aggregators are cross-bundle. Here the bundle *activity* provides a filter which apply on an export provided by the person bundle ;
- there may exists multiple aggregator or filter for one export. Currently, only one export is allowed.
- Aggregators, filters, exports and formatters are cross-bundle. Here the bundle *activity* provides a filter which is applied on an export provided by the person bundle ;
- Multiple aggregator or filter for one export may exist. Currently, only one export is allowed.
The result might be :
The result might be :
+-----------------------+----------------+---------------------------+
| Nationality | Gender | Number of people |
@ -89,9 +89,9 @@ The result might be :
| France | Female | 150 |
+-----------------------+----------------+---------------------------+
**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, group them by week, and format the data in a OpenDocument spreadsheet.
**Example 2**: Count the average duration of an activity with type "meeting", which occurs between the 1st of June and the 31st of December, group them by week, and format the data in an OpenDocument spreadsheet.
Here :
Here :
- *count the average duration of an activity* is the export part
- *activity with type meeting* is a filter part
@ -102,7 +102,7 @@ Here :
The result might be :
+-----------------------+----------------------+
| Week | Number of activities |
| Week | Number of activities |
+=======================+======================+
| 2015-10 | 10 |
+-----------------------+----------------------+
@ -116,77 +116,77 @@ The result might be :
Authorization and exports
-------------------------
Exports, filters and aggregators should not make see data the user is not allowed to see.
Exports, filters and aggregators should not show data the user is not allowed to see within the application.
In other words, developers are required to take care of user authorization for each export.
It should exists a special role that should be granted to users which are allowed to build exports. For more simplicity, this role should apply on center, and should not requires special circles.
There should be a specific role that grants permission to users who are allowed to build exports. For more simplicity, this role should apply on a center, and should not require special circles.
How does the magic works ?
How does the magic work ?
===========================
To build an export, we rely on the capacity of the database to execute queries with aggregate (i.e. GROUP BY) and filter (i.e. WHERE) instructions.
An export is an SQL query which is initiated by an export, and modified by aggregators and filters.
.. note::
.. note::
**Example**: Count the number of people having at least one activity in the last 12 month, and group them by nationality and gender
1. The report initiate the query
1. The report initiates the query
.. code-block:: SQL
SELECT count(people.*) FROM people
2. The filter add a where and join clause :
2. The filter adds a where and join clause :
.. code-block:: SQL
SELECT count(people.*) FROM people
RIGHT JOIN activity
SELECT count(people.*) FROM people
RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
3. The aggregator "nationality" add a GROUP BY clause and a column in the SELECT statement:
3. The aggregator "nationality" adds a GROUP BY clause and a column in the SELECT statement:
.. code-block:: sql
SELECT people.nationality, count(people.*) FROM people
RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
SELECT people.nationality, count(people.*) FROM people
RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
GROUP BY nationality
4. The aggregator "gender" do the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement :
4. The aggregator "gender" does the same job as the nationality aggregator : it adds a GROUP BY clause and a column in the SELECT statement :
.. code-block:: sql
SELECT people.nationality, people.gender, count(people.*)
FROM people RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
SELECT people.nationality, people.gender, count(people.*)
FROM people RIGHT JOIN activity
WHERE activity.date IS BETWEEN now AND 6 month ago
GROUP BY nationality, gender
Each filter, aggregator and filter may collect parameters from the user by providing a form. This form is appended to the export form. Here is an example.
Each filter, aggregator and filter may collect parameters from the user through a form. This form is appended to the export form. Here is an example.
.. figure:: /_static/screenshots/development/export_form-fullpage.png
The screenshot show the export form for ``CountPeople`` (Nombre de personnes). The filter by date of birth is checked (*Filtrer par date de naissance de la personne*), which allow to show a subform, which is provided by the :class:`Chill\PersonBundle\Export\Filter\BirthdateFilter`. The other filter, which are unchecked, does not show the subform.
The screenshot shows the export form for ``CountPeople`` (Nombre de personnes). The filter by date of birth is checked (*Filtrer par date de naissance de la personne*), which triggers a subform, which is provided by the :class:`Chill\PersonBundle\Export\Filter\BirthdateFilter`. The other unchecked filter does not show the subform.
Two aggregators are also checked : by Country of birth (*Aggréger les personnes par pays de naissance*, corresponding class is :class:`Chill\PersonBundle\Export\Aggregator\CountryOfBirthAggregator`, which also open a subform. The aggregator by gender (*Aggréger les personnes par genre*) is also checked, but there is no corresponding subform.
Two aggregators are also checked : by Country of birth (*Aggréger les personnes par pays de naissance*, the corresponding class is :class:`Chill\PersonBundle\Export\Aggregator\CountryOfBirthAggregator`, which also triggers a subform. The aggregator by gender (*Aggréger les personnes par genre*) is also checked, but there is no corresponding subform.
The Export Manager
------------------
The Export manager (:class:`Chill\MainBundle\Export\ExportManager` is the central class which register all exports, aggregators, filters and formatters.
The Export manager (:class:`Chill\MainBundle\Export\ExportManager` is the central class which registers all exports, aggregators, filters and formatters.
The export manager is also responsible for orchestrating the whole export process, producing a :class:`Symfony\FrameworkBundle\HttpFoundation\Request` to each export request.
The export manager is also responsible for orchestrating the whole export process, producing a :class:`Symfony\FrameworkBundle\HttpFoundation\Request` for each export request.
The export form step
--------------------
The form step allow to build a form, aggregating different parts of the module.
The form step allows you to build a form, combining different parts of the module.
The building of forms is separated between different subform, which are responsible for rendering their part of the form (aggregators, filters, and export).
The building of forms is split into different subforms, where each one is responsible for rendering their part of the form (aggregators, filters, and export).
.. figure:: /_static/puml/exports/form_steps.png
:scale: 40%
@ -194,12 +194,12 @@ The building of forms is separated between different subform, which are responsi
The formatter form step
-----------------------
The formatter form is processed *after* the user filled the export form. It is built the same way, but receive in parameters the data entered by the user on the previous step (i.e. export form). It may then adapt it accordingly (example: show a list of columns selected in aggregators).
The formatter form is processed *after* the user filled the export form. It is built the same way, but receives the data entered by the user on the previous step as parameters (i.e. export form). It may then adapt it accordingly (example: show a list of columns selected in aggregators).
Processing the export
---------------------
The export process may be explained by this schema :
The export process can be explained by this schema :
.. figure:: /_static/puml/exports/processing_export.png
:scale: 40%
@ -219,20 +219,20 @@ This is an example of the ``CountPerson`` export :
:language: php
:linenos:
* **Line 36**: the ``getType`` function return a string. This string will be used to find the aggregtors and filters which will apply to this export.
* **Line 41**: a simple description to help user to understand what your export does.
* **Line 36**: the ``getType`` function returns a string. This string will be used to find the aggregtors and filters which will apply to this export.
* **Line 41**: a simple description to help users understand what your export does.
* **Line 46**: The title of the export. A summary of what your export does.
* **Line 51**: The list of roles requires to execute this export.
* **Line 51**: The list of roles required to execute this export.
* **Line 56**: We initiate the query here...
* **Line 59**: We have to filter the query with centers the users checked in the form. We process the $acl variable to get all ``Center`` object in one array
* **Line 63**: We create the query, with a query builder.
* **Line 74**: We simply returns the result, but take care of hydrating the results as an array.
* **Line 103**: return the list of formatters types which are allowed to apply on this filter
* **Line 59**: We have to filter the query with centers the users checked in the form. We process the $acl variable to get all ``Center`` objects in one array
* **Line 63**: We create the query with a query builder.
* **Line 74**: We return the result, but make sure to hydrate the results as an array.
* **Line 103**: return the list of formatter types which are allowed to be applied on this filter
Filters
-------
This is an example of the *filter by birthdate*. This filter ask some information in a form (`buildForm` is not empty), and this form must be validated. To performs this validations, we implement a new Interface: :class:`Chill\MainBundle\Export\ExportElementValidatedInterface`:
This is an example of the *filter by birthdate*. This filter asks some information through a form (`buildForm` is not empty), and this form must be validated. To perform this validation, we implement a new Interface: :class:`Chill\MainBundle\Export\ExportElementValidatedInterface`:
.. literalinclude:: /_static/code/exports/BirthdateFilter.php
:language: php

View File

@ -24,6 +24,7 @@ parameters:
- "/spec/"
- "/var/"
- "/vendor/"
- "/tests/app"
# Psalm
tasks.psalm.blocking: true

File diff suppressed because it is too large Load Diff

View File

@ -25,16 +25,6 @@ parameters:
count: 1
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in &&, mixed given on the right side\\.$#"
count: 3
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 3

View File

@ -22,6 +22,7 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
@ -41,7 +42,6 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Serializer\SerializerInterface;
use function array_key_exists;
@ -57,6 +57,8 @@ final class ActivityController extends AbstractController
private ActivityTypeRepository $activityTypeRepository;
private CenterResolverManagerInterface $centerResolver;
private EntityManagerInterface $entityManager;
private EventDispatcherInterface $eventDispatcher;
@ -86,7 +88,9 @@ final class ActivityController extends AbstractController
EventDispatcherInterface $eventDispatcher,
LoggerInterface $logger,
SerializerInterface $serializer,
UserRepositoryInterface $userRepository
UserRepositoryInterface $userRepository,
CenterResolverManagerInterface $centerResolver
) {
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->activityTypeRepository = $activityTypeRepository;
@ -101,6 +105,8 @@ final class ActivityController extends AbstractController
$this->logger = $logger;
$this->serializer = $serializer;
$this->userRepository = $userRepository;
$this->centerResolver = $centerResolver;
}
/**
@ -203,7 +209,7 @@ final class ActivityController extends AbstractController
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null,
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,
@ -431,7 +437,7 @@ final class ActivityController extends AbstractController
$this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null,
'role' => new Role('CHILL_ACTIVITY_CREATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,

View File

@ -61,8 +61,6 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
ActivityVoter::DELETE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::SEE_DETAILS => [ActivityVoter::SEE],
ActivityVoter::FULL => [
ActivityVoter::CREATE_PERSON,
ActivityVoter::CREATE_ACCOMPANYING_COURSE,
ActivityVoter::DELETE,
ActivityVoter::UPDATE,
],

View File

@ -16,8 +16,8 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\HasCentersInterface;
use Chill\MainBundle\Entity\HasScopesInterface;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
@ -55,7 +55,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* getUserFunction="getUser",
* path="scope")
*/
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCenterInterface, HasScopeInterface
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCentersInterface, HasScopesInterface
{
public const SENTRECEIVED_RECEIVED = 'received';
@ -306,13 +306,17 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
* get the center
* center is extracted from person.
*/
public function getCenter(): ?Center
public function getCenters(): iterable
{
if ($this->person instanceof Person) {
return $this->person->getCenter();
return [$this->person->getCenter()];
}
return null;
if ($this->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
return $this->getAccompanyingPeriod()->getCenters() ?? [];
}
return [];
}
public function getComment(): CommentEmbeddable
@ -422,6 +426,19 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->scope;
}
public function getScopes(): iterable
{
if (null !== $this->getAccompanyingPeriod()) {
return $this->getAccompanyingPeriod()->getScopes();
}
if (null !== $this->getPerson()) {
return [$this->scope];
}
return [];
}
public function getSentReceived(): string
{
return $this->sentReceived;

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialActionAggregator implements AggregatorInterface
{
private SocialActionRender $actionRender;
private SocialActionRepository $actionRepository;
public function __construct(
SocialActionRender $actionRender,
SocialActionRepository $actionRepository
) {
$this->actionRender = $actionRender;
$this->actionRepository = $actionRepository;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('socialaction', $qb->getAllAliases(), true)) {
$qb->join('activity.socialActions', 'socialaction');
}
$qb->addSelect('socialaction.id AS socialaction_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('socialaction_aggregator');
} else {
$qb->groupBy('socialaction_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data)
{
return function ($value) {
if ('_header' === $value) {
return 'Social action';
}
$sa = $this->actionRepository->find($value);
return $this->actionRender->renderString($sa, []);
};
}
public function getQueryKeys($data): array
{
return ['socialaction_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by linked socialaction';
}
}

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialIssueAggregator implements AggregatorInterface
{
private SocialIssueRender $issueRender;
private SocialIssueRepository $issueRepository;
public function __construct(
SocialIssueRepository $issueRepository,
SocialIssueRender $issueRender
) {
$this->issueRepository = $issueRepository;
$this->issueRender = $issueRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('socialissue', $qb->getAllAliases(), true)) {
$qb->join('activity.socialIssues', 'socialissue');
}
$qb->addSelect('socialissue.id AS socialissue_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('socialissue_aggregator');
} else {
$qb->groupBy('socialissue_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Social issues';
}
$i = $this->issueRepository->find($value);
return $this->issueRender->renderString($i, []);
};
}
public function getQueryKeys($data): array
{
return ['socialissue_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by linked socialissue';
}
}

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByThirdpartyAggregator implements AggregatorInterface
{
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(
ThirdPartyRepository $thirdPartyRepository,
ThirdPartyRender $thirdPartyRender
) {
$this->thirdPartyRepository = $thirdPartyRepository;
$this->thirdPartyRender = $thirdPartyRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('thirdparty', $qb->getAllAliases(), true)) {
$qb->join('activity.thirdParties', 'thirdparty');
}
$qb->addSelect('thirdparty.id AS thirdparty_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('thirdparty_aggregator');
} else {
$qb->groupBy('thirdparty_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Accepted thirdparty';
}
$tp = $this->thirdPartyRepository->find($value);
return $this->thirdPartyRender->renderString($tp, []);
};
}
public function getQueryKeys($data): array
{
return ['thirdparty_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by linked thirdparties';
}
}

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByUserAggregator implements AggregatorInterface
{
private UserRender $userRender;
private UserRepository $userRepository;
public function __construct(
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('user', $qb->getAllAliases(), true)) {
$qb->join('activity.users', 'user');
}
$qb->addSelect('user.id AS users_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('users_aggregator');
} else {
$qb->groupBy('users_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Accepted users';
}
$u = $this->userRepository->find($value);
return $this->userRender->renderString($u, []);
};
}
public function getQueryKeys($data): array
{
return ['users_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by linked users';
}
}

View File

@ -0,0 +1,143 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use DateTime;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class DateAggregator implements AggregatorInterface
{
private const CHOICES = [
'by month' => 'month',
'by week' => 'week',
'by year' => 'year',
];
private const DEFAULT_CHOICE = 'year';
private TranslatorInterface $translator;
public function __construct(
TranslatorInterface $translator
) {
$this->translator = $translator;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$order = null;
switch ($data['frequency']) {
case 'month':
$fmt = 'MM';
break;
case 'week':
$fmt = 'IW';
break;
case 'year':
$fmt = 'YYYY'; $order = 'DESC';
break; // order DESC does not works !
default:
throw new RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency']));
}
$qb->addSelect(sprintf("TO_CHAR(activity.date, '%s') AS date_aggregator", $fmt));
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('date_aggregator');
} else {
$qb->groupBy('date_aggregator');
}
$orderBy = $qb->getDQLPart('orderBy');
if (!empty($orderBy)) {
$qb->addOrderBy('date_aggregator', $order);
} else {
$qb->orderBy('date_aggregator', $order);
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('frequency', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getLabels($key, array $values, $data)
{
return static function ($value) use ($data): string {
if ('_header' === $value) {
return 'by ' . $data['frequency'];
}
switch ($data['frequency']) {
case 'month':
$month = DateTime::createFromFormat('!m', $value);
return sprintf(
'%02d (%s)',
$value,
$month->format('M')
);
case 'week':
//return $this->translator->trans('for week') .' '. $value ;
case 'year':
//return $this->translator->trans('in year') .' '. $value ;
default:
return $value;
}
};
}
public function getQueryKeys($data): array
{
return ['date_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by date';
}
}

View File

@ -0,0 +1,92 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationTypeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class LocationTypeAggregator implements AggregatorInterface
{
private LocationTypeRepository $locationTypeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
LocationTypeRepository $locationTypeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->locationTypeRepository = $locationTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('location', $qb->getAllAliases(), true)) {
$qb->join('activity.location', 'location');
}
$qb->addSelect('IDENTITY(location.locationType) AS locationtype_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('locationtype_aggregator');
} else {
$qb->groupBy('locationtype_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Accepted locationtype';
}
$lt = $this->locationTypeRepository->find($value);
return $this->translatableStringHelper->localize(
$lt->getTitle()
);
};
}
public function getQueryKeys($data): array
{
return ['locationtype_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by locationtype';
}
}

View File

@ -0,0 +1,92 @@
<?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);
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class UserScopeAggregator implements AggregatorInterface
{
private ScopeRepository $scopeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ScopeRepository $scopeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('user', $qb->getAllAliases(), true)) {
$qb->join('activity.user', 'user');
}
$qb->addSelect('IDENTITY(user.mainScope) AS userscope_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('userscope_aggregator');
} else {
$qb->groupBy('userscope_aggregator');
}
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Scope';
}
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize(
$s->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['userscope_aggregator'];
}
public function getTitle(): string
{
return 'Group activity by userscope';
}
}

View File

@ -11,15 +11,15 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Closure;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use function in_array;
class ActivityTypeAggregator implements AggregatorInterface
{
@ -37,23 +37,31 @@ class ActivityTypeAggregator implements AggregatorInterface
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole()
public function addRole(): ?string
{
return new Role(ActivityStatsVoter::STATS);
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
// add select element
$qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY));
if (!in_array('type', $qb->getAllAliases(), true)) {
$qb->join('activity.activityType', 'type');
}
// add the "group by" part
$qb->addGroupBy(self::KEY);
$qb->addSelect(sprintf('IDENTITY(activity.activityType) AS %s', self::KEY));
$groupby = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy(self::KEY);
} else {
$qb->groupBy(self::KEY);
}
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@ -11,29 +11,33 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class ActivityUserAggregator implements AggregatorInterface
{
public const KEY = 'activity_user_id';
private UserRender $userRender;
private UserRepository $userRepository;
public function __construct(
UserRepository $userRepository
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole()
public function addRole(): ?string
{
return new Role(ActivityStatsVoter::STATS);
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
@ -47,7 +51,7 @@ class ActivityUserAggregator implements AggregatorInterface
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@ -62,10 +66,12 @@ class ActivityUserAggregator implements AggregatorInterface
return function ($value) {
if ('_header' === $value) {
return 'activity user';
return 'Activity user';
}
return $this->userRepository->find($value)->getUsername();
$u = $this->userRepository->find($value);
return $this->userRender->renderString($u, []);
};
}

View File

@ -9,11 +9,11 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
namespace Chill\ActivityBundle\Export\Aggregator\PersonAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
@ -23,7 +23,6 @@ use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
@ -47,9 +46,9 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole()
public function addRole(): ?string
{
return new Role(ActivityStatsVoter::STATS);
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
@ -104,9 +103,9 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
}
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY_PERSON;
}
public function buildForm(FormBuilderInterface $builder)

View File

@ -0,0 +1,24 @@
<?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);
namespace Chill\ActivityBundle\Export;
/**
* This class declare constants used for the export framework.
*/
abstract class Declarations
{
public const ACTIVITY = 'activity';
public const ACTIVITY_ACP = 'activity_linked_to_acp';
public const ACTIVITY_PERSON = 'activity_linked_to_person';
}

View File

@ -0,0 +1,107 @@
<?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);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface;
class AvgActivityDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Average activities linked to an accompanying period duration by various parameters.';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_avg_activity_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_avg_activity_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Average activity linked to an accompanying period duration';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp');
$qb->select('AVG(activity.durationTime) as export_avg_activity_duration');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -0,0 +1,107 @@
<?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);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface;
class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Average activities linked to an accompanying period visit duration by various parameters.';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_avg_activity_visit_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period visit duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_avg_activity_visit_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Average activity linked to an accompanying period visit duration';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp');
$qb->select('AVG(activity.travelTime) as export_avg_activity_visit_duration');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -0,0 +1,108 @@
<?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);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
class CountActivity implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Count activities linked to an accompanying period by various parameters.';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities linked to an accompanying period' : $value;
}
public function getQueryKeys($data): array
{
return ['export_count_activity'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Count activities linked to an accompanying period';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp');
$qb->select('COUNT(activity.id) as export_count_activity');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -0,0 +1,107 @@
<?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);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface;
class SumActivityDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Sum activities linked to an accompanying period duration by various parameters.';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_sum_activity_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_sum_activity_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Sum activity linked to an accompanying period duration';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp');
$qb->select('SUM(activity.durationTime) as export_sum_activity_duration');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -0,0 +1,107 @@
<?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);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\Form\FormBuilderInterface;
class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(Activity::class);
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Sum activities linked to an accompanying period visit duration by various parameters.';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_sum_activity_visit_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period visit duration' : $value;
}
public function getQueryKeys($data): array
{
return ['export_sum_activity_visit_duration'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Sum activity linked to an accompanying period visit duration';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$qb = $this->repository->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp');
$qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -9,18 +9,20 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class CountActivity implements ExportInterface
class CountActivity implements ExportInterface, GroupedExportInterface
{
protected ActivityRepository $activityRepository;
@ -41,7 +43,12 @@ class CountActivity implements ExportInterface
public function getDescription()
{
return 'Count activities by various parameters.';
return 'Count activities linked to a person by various parameters.';
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
public function getLabels($key, array $values, $data)
@ -50,7 +57,7 @@ class CountActivity implements ExportInterface
throw new LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities' : $value;
return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value;
}
public function getQueryKeys($data)
@ -65,24 +72,23 @@ class CountActivity implements ExportInterface
public function getTitle()
{
return 'Count activities';
return 'Count activities linked to a person';
}
public function getType()
public function getType(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this
->activityRepository
->createQueryBuilder('activity')
->select('COUNT(activity.id) as export_count_activity')
$qb = $this->activityRepository->createQueryBuilder('activity')
->join('activity.person', 'person');
$qb->select('COUNT(activity.id) as export_count_activity');
$qb
->where($qb->expr()->in('person.center', ':centers'))
->setParameter('centers', $centers);
@ -90,13 +96,17 @@ class CountActivity implements ExportInterface
return $qb;
}
public function requiredRole()
public function requiredRole(): string
{
return new Role(ActivityStatsVoter::STATS);
return ActivityStatsVoter::STATS;
}
public function supportsModifiers()
{
return ['person', 'activity'];
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@ -9,21 +9,23 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use DateTime;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@ -32,7 +34,7 @@ use function array_key_exists;
use function count;
use function in_array;
class ListActivity implements ListInterface
class ListActivity implements ListInterface, GroupedExportInterface
{
protected EntityManagerInterface $entityManager;
@ -94,7 +96,12 @@ class ListActivity implements ListInterface
public function getDescription()
{
return 'List activities';
return 'List activities linked to a person description';
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
public function getLabels($key, array $values, $data)
@ -180,12 +187,12 @@ class ListActivity implements ListInterface
public function getTitle()
{
return 'List activities';
return 'List activity linked to a person';
}
public function getType()
public function getType(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
@ -267,13 +274,17 @@ class ListActivity implements ListInterface
return $qb;
}
public function requiredRole()
public function requiredRole(): string
{
return new Role(ActivityStatsVoter::LISTS);
return ActivityStatsVoter::LISTS;
}
public function supportsModifiers()
{
return ['activity', 'person'];
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@ -9,23 +9,26 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export;
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
* This export allow to compute stats on activity duration.
*
* The desired stat must be given in constructor.
*/
class StatActivityDuration implements ExportInterface
class StatActivityDuration implements ExportInterface, GroupedExportInterface
{
public const SUM = 'sum';
@ -59,17 +62,22 @@ class StatActivityDuration implements ExportInterface
public function getDescription()
{
if (self::SUM === $this->action) {
return 'Sum activities duration by various parameters.';
return 'Sum activities linked to a person duration by various parameters.';
}
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
public function getLabels($key, array $values, $data)
{
if ('export_stat_activity' !== $key) {
throw new LogicException(sprintf('The key %s is not used by this export', $key));
}
$header = self::SUM === $this->action ? 'Sum of activities duration' : false;
$header = self::SUM === $this->action ? 'Sum activities linked to a person duration' : false;
return static fn (string $value) => '_header' === $value ? $header : $value;
}
@ -87,19 +95,19 @@ class StatActivityDuration implements ExportInterface
public function getTitle()
{
if (self::SUM === $this->action) {
return 'Sum activity duration';
return 'Sum activity linked to a person duration';
}
}
public function getType()
public function getType(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(
static fn (array $el): string => $el['center'],
static fn (array $el): Center => $el['center'],
$acl
);
@ -118,13 +126,17 @@ class StatActivityDuration implements ExportInterface
->setParameter(':centers', $centers);
}
public function requiredRole()
public function requiredRole(): string
{
return new Role(ActivityStatsVoter::STATS);
return ActivityStatsVoter::STATS;
}
public function supportsModifiers()
{
return ['person', 'activity'];
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@ -0,0 +1,92 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialActionFilter implements FilterInterface
{
private SocialActionRender $actionRender;
public function __construct(SocialActionRender $actionRender)
{
$this->actionRender = $actionRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
if (!in_array('socialaction', $qb->getAllAliases(), true)) {
$qb->join('activity.socialActions', 'socialaction');
}
$clause = $qb->expr()->in('socialaction.id', ':socialactions');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('socialactions', $data['accepted_socialactions']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_socialactions', EntityType::class, [
'class' => SocialAction::class,
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$actions = [];
foreach ($data['accepted_socialactions'] as $sa) {
$actions[] = $this->actionRender->renderString($sa, []);
}
return ['Filtered activity by linked socialaction: only %actions%', [
'%actions%' => implode(', ou ', $actions),
]];
}
public function getTitle(): string
{
return 'Filter activity by linked socialaction';
}
}

View File

@ -0,0 +1,92 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialIssueFilter implements FilterInterface
{
private SocialIssueRender $issueRender;
public function __construct(SocialIssueRender $issueRender)
{
$this->issueRender = $issueRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
if (!in_array('socialissue', $qb->getAllAliases(), true)) {
$qb->join('activity.socialIssues', 'socialissue');
}
$clause = $qb->expr()->in('socialissue.id', ':socialissues');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('socialissues', $data['accepted_socialissues']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_socialissues', EntityType::class, [
'class' => SocialIssue::class,
'choice_label' => function (SocialIssue $si) {
return $this->issueRender->renderString($si, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$issues = [];
foreach ($data['accepted_socialissues'] as $si) {
$issues[] = $this->issueRender->renderString($si, []);
}
return ['Filtered activity by linked socialissue: only %issues%', [
'%issues%' => implode(', ou ', $issues),
]];
}
public function getTitle(): string
{
return 'Filter activity by linked socialissue';
}
}

View File

@ -0,0 +1,92 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByUserFilter 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)
{
$where = $qb->getDQLPart('where');
if (!in_array('user', $qb->getAllAliases(), true)) {
$qb->join('activity.users', 'user');
}
$clause = $qb->expr()->in('user.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
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_users', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$users = [];
foreach ($data['accepted_users'] as $u) {
$users[] = $this->userRender->renderString($u, []);
}
return ['Filtered activity by linked users: only %users%', [
'%users%' => implode(', ou ', $users),
]];
}
public function getTitle(): string
{
return 'Filter activity by linked users';
}
}

View File

@ -0,0 +1,92 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class EmergencyFilter implements FilterInterface
{
private const CHOICES = [
'activity is emergency' => true,
'activity is not emergency' => false,
];
private const DEFAULT_CHOICE = false;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->eq('activity.emergency', ':emergency');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('emergency', $data['accepted_emergency']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_emergency', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function describeAction($data, $format = 'string'): array
{
foreach (self::CHOICES as $k => $v) {
if ($v === $data['accepted_emergency']) {
$choice = $k;
}
}
return ['Filtered activity by emergency: only %emergency%', [
'%emergency%' => $this->translator->trans($choice),
]];
}
public function getTitle(): string
{
return 'Filter activity by emergency';
}
}

View File

@ -0,0 +1,93 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class LocationTypeFilter implements FilterInterface
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('location', $qb->getAllAliases(), true)) {
$qb->join('activity.location', 'location');
}
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('location.locationType', ':locationtype');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('locationtype', $data['accepted_locationtype']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_locationtype', EntityType::class, [
'class' => LocationType::class,
'choice_label' => function (LocationType $type) {
return $this->translatableStringHelper->localize($type->getTitle());
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$types = [];
foreach ($data['accepted_locationtype'] as $type) {
$types[] = $this->translatableStringHelper->localize(
$type->getTitle()
);
}
return ['Filtered activity by locationtype: only %types%', [
'%types%' => implode(', ou ', $types),
]];
}
public function getTitle(): string
{
return 'Filter activity by locationtype';
}
}

View File

@ -0,0 +1,89 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class SentReceivedFilter implements FilterInterface
{
private const CHOICES = [
'is sent' => Activity::SENTRECEIVED_SENT,
'is received' => Activity::SENTRECEIVED_RECEIVED,
];
private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->eq('activity.sentReceived', ':sentreceived');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('sentreceived', $data['accepted_sentreceived']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_sentreceived', ChoiceType::class, [
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function describeAction($data, $format = 'string'): array
{
$sentreceived = array_flip(self::CHOICES)[$data['accepted_sentreceived']];
return ['Filtered activity by sentreceived: only %sentreceived%', [
'%sentreceived%' => $this->translator->trans($sentreceived),
]];
}
public function getTitle(): string
{
return 'Filter activity by sentreceived';
}
}

View File

@ -0,0 +1,88 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UserFilter 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)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('activity.user', ':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
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_users', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true,
'label' => 'Creators',
]);
}
public function describeAction($data, $format = 'string'): array
{
$users = [];
foreach ($data['accepted_users'] as $u) {
$users[] = $this->userRender->renderString($u, []);
}
return ['Filtered activity by user: only %users%', [
'%users%' => implode(', ou ', $users),
]];
}
public function getTitle(): string
{
return 'Filter activity by user';
}
}

View File

@ -0,0 +1,96 @@
<?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);
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class UserScopeFilter implements FilterInterface
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('user', $qb->getAllAliases(), true)) {
$qb->join('activity.user', 'user');
}
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('user.mainScope', ':userscope');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('userscope', $data['accepted_userscope']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_userscope', EntityType::class, [
'class' => Scope::class,
'choice_label' => function (Scope $s) {
return $this->translatableStringHelper->localize(
$s->getName()
);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$scopes = [];
foreach ($data['accepted_userscope'] as $s) {
$scopes[] = $this->translatableStringHelper->localize(
$s->getName()
);
}
return ['Filtered activity by userscope: only %scopes%', [
'%scopes%' => implode(', ou ', $scopes),
]];
}
public function getTitle(): string
{
return 'Filter activity by userscope';
}
}

View File

@ -11,12 +11,13 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\Export\FilterType;
use DateTime;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
@ -32,7 +33,7 @@ class ActivityDateFilter implements FilterInterface
$this->translator = $translator;
}
public function addRole()
public function addRole(): ?string
{
return null;
}
@ -57,36 +58,22 @@ class ActivityDateFilter implements FilterInterface
$qb->setParameter('date_to', $data['date_to']);
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add(
'date_from',
DateType::class,
[
$builder
->add('date_from', ChillDateType::class, [
'label' => 'Activities after this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]
);
$builder->add(
'date_to',
DateType::class,
[
])
->add('date_to', ChillDateType::class, [
'label' => 'Activities before this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]
);
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var \Symfony\Component\Form\FormInterface $filterForm */

View File

@ -12,8 +12,8 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
@ -22,7 +22,6 @@ use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
@ -41,15 +40,15 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
$this->activityTypeRepository = $activityTypeRepository;
}
public function addRole()
public function addRole(): ?string
{
return new Role(ActivityStatsVoter::STATS);
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('activity.type', ':selected_activity_types');
$clause = $qb->expr()->in('activity.activityType', ':selected_activity_types');
if ($where instanceof Expr\Andx) {
$where->add($clause);
@ -61,39 +60,33 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
$qb->setParameter('selected_activity_types', $data['types']);
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add(
'types',
EntityType::class,
[
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()),
'multiple' => true,
'expanded' => false,
]
);
$builder->add('types', EntityType::class, [
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getName()),
'group_by' => fn (ActivityType $type) => $this->translatableStringHelper->localize($type->getCategory()->getName()),
'multiple' => true,
'expanded' => false,
]);
}
public function describeAction($data, $format = 'string')
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
fn (ActivityType $t): string => '"' . $this->translatableStringHelper->localize($t->getName()) . '"',
fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()),
$this->activityTypeRepository->findBy(['id' => $data['types']->toArray()])
);
return [
'Filtered by activity type: only %list%',
[
'%list%' => implode(', ', $reasonsNames),
],
];
return ['Filtered by activity type: only %list%', [
'%list%' => implode(', ou ', $reasonsNames),
]];
}
public function getTitle()

View File

@ -9,11 +9,11 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
namespace Chill\ActivityBundle\Export\Filter\PersonFilters;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
@ -23,7 +23,6 @@ use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function array_key_exists;
@ -43,9 +42,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
$this->activityReasonRepository = $activityReasonRepository;
}
public function addRole()
public function addRole(): ?string
{
return new Role(ActivityStatsVoter::STATS);
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
@ -79,9 +78,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
$qb->setParameter('selected_activity_reasons', $data['reasons']);
}
public function applyOn()
public function applyOn(): string
{
return 'activity';
return Declarations::ACTIVITY_PERSON;
}
public function buildForm(FormBuilderInterface $builder)

View File

@ -9,7 +9,7 @@
declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Filter;
namespace Chill\ActivityBundle\Export\Filter\PersonFilters;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
@ -52,7 +52,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
$this->translator = $translator;
}
public function addRole()
public function addRole(): ?string
{
return null;
}
@ -102,7 +102,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
public function applyOn()
public function applyOn(): string
{
return Declarations::PERSON_IMPLIED_IN;
}

View File

@ -14,17 +14,20 @@ namespace Chill\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Form\Type\PrivateCommentType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
@ -50,6 +53,7 @@ use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use function in_array;
class ActivityType extends AbstractType
@ -109,19 +113,18 @@ class ActivityType extends AbstractType
$activityType = $options['activityType'];
// TODO revoir la gestion des center au niveau du form des activité.
if ($options['center']) {
if ($options['center'] instanceof Center && null !== $options['data']->getPerson()) {
$builder->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
// TODO make required again once scope and rights are fixed
'required' => false,
'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
'required' => true,
]);
}
/** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = null;
if ($options['accompanyingPeriod']) {
if ($options['accompanyingPeriod'] instanceof AccompanyingPeriod) {
$accompanyingPeriod = $options['accompanyingPeriod'];
}
@ -218,12 +221,10 @@ class ActivityType extends AbstractType
]);
}
if ($activityType->isVisible('user') && $options['center']) {
$builder->add('user', UserPickerType::class, [
if ($activityType->isVisible('user') && $options['center'] instanceof Center) {
$builder->add('user', PickUserDynamicType::class, [
'label' => $activityType->getLabel('user'),
'required' => $activityType->isRequired('user'),
'center' => $options['center'],
'role' => $options['role'],
]);
}
@ -442,8 +443,8 @@ class ActivityType extends AbstractType
$resolver
->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod'])
->setAllowedTypes('center', ['null', 'Chill\MainBundle\Entity\Center'])
->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role')
->setAllowedTypes('center', ['null', Center::class])
->setAllowedTypes('role', [Role::class, 'string'])
->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class)
->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']);
}

View File

@ -120,3 +120,11 @@
{{ form_end(edit_form) }}
{# {{ form(delete_form) }} #}
{% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}

View File

@ -46,7 +46,7 @@
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %}
{% if is_granted('CHILL_ACTIVITY_CREATE_PERSON', person) %}
{% if is_granted('CHILL_ACTIVITY_CREATE', person) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"

View File

@ -119,3 +119,11 @@
</li>
</ul>
{{ form_end(form) }}
{% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}

View File

@ -133,7 +133,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
// change attribute CREATE
if (self::CREATE === $attribute) {
$attribute = self::CREATE_PERSON;
return $this->voterHelper->voteOnAttribute(self::CREATE_PERSON, $subject->getPerson(), $token);
}
} elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) {
@ -144,7 +144,8 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
if (AccompanyingPeriod::STEP_CLOSED === $subject->getAccompanyingPeriod()->getStep()) {
return false;
}
$attribute = self::CREATE_ACCOMPANYING_COURSE;
return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject->getAccompanyingPeriod(), $token);
}
} else {
throw new RuntimeException('Could not determine context of activity.');
@ -158,12 +159,12 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
// transform the attribute
if (self::CREATE === $attribute) {
$attribute = self::CREATE_ACCOMPANYING_COURSE;
return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject, $token);
}
} elseif ($subject instanceof Person) {
// transform the attribute
if (self::CREATE === $attribute) {
$attribute = self::CREATE_PERSON;
return $this->voterHelper->voteOnAttribute(self::CREATE_PERSON, $subject, $token);
}
}

View File

@ -130,8 +130,10 @@ class ActivityContext implements
return $this->personRender->renderString($p, []);
},
'multiple' => false,
'required' => false,
'expanded' => true,
'label' => $options[$key . 'Label'],
'placeholder' => $this->translator->trans('Any person selected'),
]);
}
}

View File

@ -3,26 +3,48 @@ services:
autowire: true
autoconfigure: true
chill.activity.export.count_activity:
class: Chill\ActivityBundle\Export\Export\CountActivity
## Indicators
chill.activity.export.count_activity_linked_to_person:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity
tags:
- { name: chill.export, alias: 'count_activity' }
- { name: chill.export, alias: 'count_activity_linked_to_person' }
chill.activity.export.sum_activity_duration:
class: Chill\ActivityBundle\Export\Export\StatActivityDuration
chill.activity.export.sum_activity_duration_linked_to_person:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration
tags:
- { name: chill.export, alias: 'sum_activity_duration' }
- { name: chill.export, alias: 'sum_activity_duration_linked_to_person' }
chill.activity.export.list_activity:
class: Chill\ActivityBundle\Export\Export\ListActivity
chill.activity.export.list_activity_linked_to_person:
class: Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity
tags:
- { name: chill.export, alias: 'list_activity' }
- { name: chill.export, alias: 'list_activity_linked_to_person' }
chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\ActivityReasonFilter
chill.activity.export.count_activity_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity
tags:
- { name: chill.export_filter, alias: 'activity_reason_filter' }
- { name: chill.export, alias: 'count_activity_linked_to_acp' }
chill.activity.export.sum_activity_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityDuration
tags:
- { name: chill.export, alias: 'sum_activity_duration_linked_to_acp' }
chill.activity.export.sum_activity_visit_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityVisitDuration
tags:
- { name: chill.export, alias: 'sum_activity_visit_duration_linked_to_acp' }
chill.activity.export.avg_activity_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityDuration
tags:
- { name: chill.export, alias: 'avg_activity_duration_linked_to_acp' }
chill.activity.export.avg_activity_visit_duration_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityVisitDuration
tags:
- { name: chill.export, alias: 'avg_activity_visit_duration_linked_to_acp' }
## Filters
chill.activity.export.type_filter:
class: Chill\ActivityBundle\Export\Filter\ActivityTypeFilter
tags:
@ -33,15 +55,61 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_date_filter' }
chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter
tags:
- { name: chill.export_filter, alias: 'activity_reason_filter' }
chill.activity.export.person_having_an_activity_between_date_filter:
class: Chill\ActivityBundle\Export\Filter\PersonHavingActivityBetweenDateFilter
class: Chill\ActivityBundle\Export\Filter\PersonFilters\PersonHavingActivityBetweenDateFilter
tags:
- #0 register as a filter
name: chill.export_filter
alias: 'activity_person_having_ac_bw_date_filter'
chill.activity.export.locationtype_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter
tags:
- { name: chill.export_filter, alias: 'activity_locationtype_filter' }
chill.activity.export.byuser_filter: # TMS (M2M)
class: Chill\ActivityBundle\Export\Filter\ACPFilters\ByUserFilter
tags:
- { name: chill.export_filter, alias: 'activity_byuser_filter' }
chill.activity.export.emergency_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter
tags:
- { name: chill.export_filter, alias: 'activity_emergency_filter' }
chill.activity.export.sentreceived_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\SentReceivedFilter
tags:
- { name: chill.export_filter, alias: 'activity_sentreceived_filter' }
chill.activity.export.bysocialaction_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter
tags:
- { name: chill.export_filter, alias: 'activity_bysocialaction_filter' }
chill.activity.export.bysocialissue_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter
tags:
- { name: chill.export_filter, alias: 'activity_bysocialissue_filter' }
chill.activity.export.user_filter: # Creator (M2O)
class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter
tags:
- { name: chill.export_filter, alias: 'activity_user_filter' }
chill.activity.export.userscope_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserScopeFilter
tags:
- { name: chill.export_filter, alias: 'activity_userscope_filter' }
## Aggregators
chill.activity.export.reason_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ActivityReasonAggregator
class: Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator
tags:
- { name: chill.export_aggregator, alias: activity_reason_aggregator }
@ -54,3 +122,38 @@ services:
class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator
tags:
- { name: chill.export_aggregator, alias: activity_user_aggregator }
chill.activity.export.locationtype_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\LocationTypeAggregator
tags:
- { name: chill.export_aggregator, alias: activity_locationtype_aggregator }
chill.activity.export.date_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\DateAggregator
tags:
- { name: chill.export_aggregator, alias: activity_date_aggregator }
chill.activity.export.byuser_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByUserAggregator
tags:
- { name: chill.export_aggregator, alias: activity_byuser_aggregator }
chill.activity.export.bythirdparty_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator
tags:
- { name: chill.export_aggregator, alias: activity_bythirdparty_aggregator }
chill.activity.export.bysocialaction_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\BySocialActionAggregator
tags:
- { name: chill.export_aggregator, alias: activity_bysocialaction_aggregator }
chill.activity.export.bysocialissue_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\BySocialIssueAggregator
tags:
- { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator }
chill.activity.export.userscope_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\UserScopeAggregator
tags:
- { name: chill.export_aggregator, alias: activity_userscope_aggregator }

View File

@ -203,18 +203,39 @@ Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr d
The activity has been successfully removed.: L'activité a été supprimée.
# exports
Count activities: Nombre d'activités
Count activities by various parameters.: Compte le nombre d'activités enregistrées en fonction de différents paramètres.
Sum activity duration: Total de la durée des activités
Sum activities duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres.
List activities: Liste les activités
Number of activities: Nombre d'activités
Exports of activities linked to a person: Exports des activités liées à une personne
Number of activities linked to a person: Nombre d'activités liées à une personne
Count activities linked to a person: Nombre d'activités
Count activities linked to a person by various parameters.: Compte le nombre d'activités enregistrées et liées à une personne en fonction de différents paramètres.
Sum activity linked to a person duration: Durée des activités
Sum activities linked to a person duration: Durée des activités liés à un usager
Sum activities linked to a person duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres.
List activity linked to a person: Liste les activités
List activities linked to a person: Liste des activités liés à un usager
List activities linked to a person description: Crée la liste des activités en fonction de différents paramètres.
Exports of activities linked to an accompanying period: Exports des activités liées à un parcours
Number of activities linked to an accompanying period: Nombre d'activités liées à un parcours
Count activities linked to an accompanying period: Nombre d'activités
Count activities linked to an accompanying period by various parameters.: Compte le nombre d'activités enregistrées et liées à un parcours en fonction de différents paramètres.
Sum activity linked to an accompanying period duration: Somme de la durée des activités
Sum activities linked to an accompanying period duration: Somme de la durée des activités liées à un parcours
Sum activities linked to an accompanying period duration by various parameters.: Additionne la durée des activités en fonction de différents paramètres.
Sum activity linked to an accompanying period visit duration: Somme de la durée de déplacement des activités
Sum activities linked to an accompanying period visit duration: Somme de la durée de déplacement des activités liées à un parcours
Sum activities linked to an accompanying period visit duration by various parameters.: Additionne la durée de déplacement des activités en fonction de différents paramètres.
Average activity linked to an accompanying period duration: Moyenne de la durée des activités
Average activities linked to an accompanying period duration: Moyenne de la durée des activités liées à un parcours
Average activities linked to an accompanying period duration by various parameters.: Moyenne de la durée des activités en fonction de différents paramètres.
Average activity linked to an accompanying period visit duration: Moyenne de la durée de déplacement des activités
Average activities linked to an accompanying period visit duration: Moyenne de la durée de déplacement des activités liées à un parcours
Average activities linked to an accompanying period visit duration by various parameters.: Moyenne de la durée de déplacement des activités en fonction de différents paramètres.
#filters
Filter by reason: Filtrer par sujet d'activité
Filter by reason: Filtrer les activités par sujet
'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%'
'Filtered by activity type: only %list%': "Filtré par type d'activity: seulement %list%"
Filtered by date activity: Filtrer par date d'activité
'Filtered by activity type: only %list%': "Filtré par type d'activité: uniquement %list%"
Filtered by date activity: Filtrer les activités par date
Activities after this date: Activités après cette date
Activities before this date: Activités avant cette date
"Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'activité: uniquement entre %date_from% et %date_to%"
@ -226,18 +247,59 @@ Implied in an activity before this date: Impliqué dans une activité avant cett
Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par personnes associées à une activité entre %date_from% et %date_to% avec les sujets %reasons_name%
Activity reasons for those activities: Sujets de ces activités
Filter by activity type: Filtrer par type d'activité
Filter by activity type: Filtrer les activités par type
Filter activity by locationtype: Filtrer les activités par type de localisation
'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
Accepted locationtype: Types de localisation
Filter activity by linked users: Filtrer les activités par TMS
'Filtered activity by linked users: only %users%': "Filtré par TMS: uniquement %users%"
Accepted users: TMS(s)
Filter activity by emergency: Filtrer les activités par urgence
'Filtered activity by emergency: only %emergency%': "Filtré par urgence: uniquement si %emergency%"
activity is emergency: l'activité est urgente
activity is not emergency: l'activité n'est pas urgente
Filter activity by sentreceived: Filtrer les activités par envoyé/reçu
'Filtered activity by sentreceived: only %sentreceived%': "Filtré par envoyé/reçu: uniquement %sentreceived%"
Accepted sentreceived: ''
is sent: envoyé
is received: reçu
Filter activity by linked socialaction: Filtrer les activités par action liée
'Filtered activity by linked socialaction: only %actions%': "Filtré par action liée: uniquement %actions%"
Filter activity by linked socialissue: Filtrer les activités par problématique liée
'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%"
Filter activity by user: Filtrer les activités par créateur
'Filtered activity by user: only %users%': "Filtré par créateur: uniquement %users%"
Creators: Créateurs
Filter activity by userscope: Filtrer les activités par service du créateur
'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%"
Accepted userscope: Services
#aggregators
Activity type: Type d'activité
Activity user: Utilisateur lié à l'activity
Activity user: Utilisateur lié à l'activité
By reason: Par sujet
By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité
Aggregate by activity user: Aggréger par utilisateur lié à l'activité
Aggregate by activity type: Aggréger par type d'activité
Aggregate by activity reason: Aggréger par sujet de l'activité
Aggregate by activity user: Grouper les activités par utilisateur
Aggregate by activity type: Grouper les activités par type
Aggregate by activity reason: Grouper les activités par sujet
Group activity by locationtype: Grouper les activités par type de localisation
Group activity by date: Grouper les activités par date
Frequency: Fréquence
by month: Par mois
by week: Par semaine
for week: Semaine
by year: Par année
in year: En
Group activity by linked users: Grouper les activités par TMS impliqué
Group activity by linked thirdparties: Grouper les activités par tiers impliqué
Accepted thirdparty: Tiers impliqué
Group activity by linked socialaction: Grouper les activités par action liée
Group activity by linked socialissue: Grouper les activités par problématique liée
Group activity by userscope: Grouper les activités par service du créateur
Last activities: Les dernières activités

View File

@ -195,7 +195,7 @@ Number of activities: Nombre d'activités
#filters
Filter by reason: Filtrer par sujet d'activité
'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%'
'Filtered by activity type: only %list%': "Filtré par type d'activity: seulement %list%"
'Filtered by activity type: only %list%': "Filtré par type d'activity: uniquement %list%"
Filtered by date activity: Filtrer par date d'activité
Activities after this date: Activités après cette date
Activities before this date: Activités avant cette date
@ -217,9 +217,9 @@ By reason: Par sujet
By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité
Aggregate by activity user: Aggréger par utilisateur lié à l'activité
Aggregate by activity type: Aggréger par type d'activité
Aggregate by activity reason: Aggréger par sujet de l'activité
Aggregate by activity user: Grouper par utilisateur lié à l'activité
Aggregate by activity type: Grouper par type d'activité
Aggregate by activity reason: Grouper par sujet de l'activité
Last activities: Les dernières activités

View File

@ -32,6 +32,7 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
$loader->load('services/exports.yaml');
$loader->load('services/controller.yml');
$loader->load('services/fixtures.yml');
$loader->load('services/form.yml');

View File

@ -0,0 +1,88 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class AgentAggregator implements AggregatorInterface
{
private UserRender $userRender;
private UserRepository $userRepository;
public function __construct(
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$qb->addSelect('u.id AS agent_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('agent_aggregator');
} else {
$qb->groupBy('agent_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Agent';
}
$r = $this->userRepository->find($value);
return $this->userRender->renderString($r, []);
};
}
public function getQueryKeys($data): array
{
return ['agent_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by agent';
}
}

View File

@ -0,0 +1,91 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CancelReasonRepository;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class CancelReasonAggregator implements AggregatorInterface
{
private CancelReasonRepository $cancelReasonRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
CancelReasonRepository $cancelReasonRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->cancelReasonRepository = $cancelReasonRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
// TODO: still needs to take into account appointments without a cancel reason somehow
$qb->join('cal.cancelReason', 'cr');
$qb->addSelect('IDENTITY(cal.cancelReason) as cancel_reason_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('cancel_reason_aggregator');
} else {
$qb->groupBy('cancel_reason_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Cancel reason';
}
$j = $this->cancelReasonRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['cancel_reason_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by cancel reason';
}
}

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserJobRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class JobAggregator implements AggregatorInterface
{
private UserJobRepository $jobRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
UserJobRepository $jobRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->jobRepository = $jobRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$qb->addSelect('IDENTITY(u.userJob) as job_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('job_aggregator');
} else {
$qb->groupBy('job_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Job';
}
$j = $this->jobRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getLabel()
);
};
}
public function getQueryKeys($data): array
{
return ['job_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by agent job';
}
}

View File

@ -0,0 +1,83 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationRepository;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
final class LocationAggregator implements AggregatorInterface
{
private LocationRepository $locationRepository;
public function __construct(
LocationRepository $locationRepository
) {
$this->locationRepository = $locationRepository;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.location', 'l');
$qb->addSelect('IDENTITY(cal.location) as location_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('location_aggregator');
} else {
$qb->groupBy('location_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Location';
}
$l = $this->locationRepository->find($value);
return $l->getName();
};
}
public function getQueryKeys($data): array
{
return ['location_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by location';
}
}

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationTypeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class LocationTypeAggregator implements AggregatorInterface
{
private LocationTypeRepository $locationTypeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
LocationTypeRepository $locationTypeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->locationTypeRepository = $locationTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.location', 'l');
$qb->addSelect('IDENTITY(l.locationType) as location_type_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('location_type_aggregator');
} else {
$qb->groupBy('location_type_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Location type';
}
$j = $this->locationTypeRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getTitle()
);
};
}
public function getQueryKeys($data): array
{
return ['location_type_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by location type';
}
}

View File

@ -0,0 +1,74 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Role\Role;
class MonthYearAggregator implements AggregatorInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$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");
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('month_year_aggregator');
} else {
$qb->groupBy('month_year_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// No form needed
}
public function getLabels($key, array $values, $data): Closure
{
return static function ($value): string {
if ('_header' === $value) {
return 'by month and year';
}
$month = substr($value, 0, 2);
$year = substr($value, 3, 4);
return strftime('%B %G', mktime(0, 0, 0, $month, '1', $year));
};
}
public function getQueryKeys($data): array
{
return ['month_year_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by month and year';
}
}

View File

@ -0,0 +1,90 @@
<?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);
namespace Chill\CalendarBundle\Export\Aggregator;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final class ScopeAggregator implements AggregatorInterface
{
private ScopeRepository $scopeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ScopeRepository $scopeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$qb->addSelect('IDENTITY(u.mainScope) as scope_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('scope_aggregator');
} else {
$qb->groupBy('scope_aggregator');
}
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'Scope';
}
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize(
$s->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['scope_aggregator'];
}
public function getTitle(): string
{
return 'Group appointments by agent scope';
}
}

View File

@ -0,0 +1,20 @@
<?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);
namespace Chill\CalendarBundle\Export;
/**
* This class declare constants used for the export framework.
*/
abstract class Declarations
{
public const CALENDAR_TYPE = 'calendar';
}

View File

@ -0,0 +1,118 @@
<?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);
namespace Chill\CalendarBundle\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Closure;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Security\Core\Role\Role;
class CountAppointments implements ExportInterface, GroupedExportInterface
{
private CalendarRepository $calendarRepository;
public function __construct(CalendarRepository $calendarRepository)
{
$this->calendarRepository = $calendarRepository;
}
public function buildForm(FormBuilderInterface $builder)
{
// No form necessary
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Count appointments by various parameters.';
}
public function getGroup(): string
{
return 'Exports of calendar';
}
public function getLabels($key, array $values, $data): Closure
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Count appointments';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
/**
* Initiate the query.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('COUNT(cal.id) AS export_result');
return $qb;
}
public function requiredRole(): string
{
// which role should we give here?
return PersonVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
}

View File

@ -0,0 +1,110 @@
<?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);
namespace Chill\CalendarBundle\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
class StatAppointmentAvgDuration implements ExportInterface, GroupedExportInterface
{
private CalendarRepository $calendarRepository;
public function __construct(
CalendarRepository $calendarRepository
) {
$this->calendarRepository = $calendarRepository;
}
public function buildForm(FormBuilderInterface $builder): void
{
// no form needed
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Get the average of appointment duration according to various filters';
}
public function getGroup(): string
{
return 'Exports of calendar';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Average appointment duration';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb
->select('AVG(cal.endDate - cal.startDate) AS export_result');
return $qb;
}
public function requiredRole(): string
{
return AccompanyingPeriodVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
}

View File

@ -0,0 +1,110 @@
<?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);
namespace Chill\CalendarBundle\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
class StatAppointmentSumDuration implements ExportInterface, GroupedExportInterface
{
private CalendarRepository $calendarRepository;
public function __construct(
CalendarRepository $calendarRepository
) {
$this->calendarRepository = $calendarRepository;
}
public function buildForm(FormBuilderInterface $builder): void
{
// no form needed
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'Get the sum of appointment durations according to various filters';
}
public function getGroup(): string
{
return 'Exports of calendar';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($qb, $data)
{
return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'Sum of appointment durations';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb
->select('SUM(cal.endDate - cal.startDate) AS export_result');
return $qb;
}
public function requiredRole(): string
{
return AccompanyingPeriodVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
}

View File

@ -0,0 +1,87 @@
<?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);
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class AgentFilter 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)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('cal.user', ':agents');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('agents', $data['accepted_agents']);
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_agents', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$users = [];
foreach ($data['accepted_agents'] as $r) {
$users[] = $r;
}
return [
'Filtered by agent: only %agents%', [
'%agents' => implode(', ou ', $users),
], ];
}
public function getTitle(): string
{
return 'Filter appointments by agent';
}
}

View File

@ -0,0 +1,79 @@
<?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);
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class BetweenDatesFilter implements FilterInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->andX(
$qb->expr()->gte('cal.startDate', ':dateFrom'),
$qb->expr()->lte('cal.endDate', ':dateTo')
);
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('dateFrom', $data['date_from'], Types::DATE_MUTABLE);
// modify dateTo so that entire day is also taken into account up until the beginning of the next day.
$qb->setParameter('dateTo', $data['date_to']->modify('+1 day'), Types::DATE_MUTABLE);
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('date_from', ChillDateType::class, [
'data' => new DateTime(),
])
->add('date_to', ChillDateType::class, [
'data' => new DateTime(),
]);
}
public function describeAction($data, $format = 'string'): array
{
return ['Filtered by appointments between %dateFrom% and %dateTo%', [
'%dateFrom%' => $data['date_from']->format('d-m-Y'),
'%dateTo%' => $data['date_to']->format('d-m-Y'),
]];
}
public function getTitle(): string
{
return 'Filter appointments between certain dates';
}
}

View File

@ -0,0 +1,98 @@
<?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);
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class JobFilter implements FilterInterface
{
protected TranslatorInterface $translator;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper
) {
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('u.userJob', ':job');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('job', $data['job']);
}
public function applyOn(): string
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('job', EntityType::class, [
'class' => UserJob::class,
'choice_label' => function (UserJob $j) {
return $this->translatableStringHelper->localize(
$j->getLabel()
);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$userJobs = [];
foreach ($data['job'] as $j) {
$userJobs[] = $this->translatableStringHelper->localize(
$j->getLabel()
);
}
return ['Filtered by agent job: only %jobs%', [
'%jobs%' => implode(', ou ', $userJobs),
]];
}
public function getTitle(): string
{
return 'Filter appointments by agent job';
}
}

View File

@ -0,0 +1,98 @@
<?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);
namespace Chill\CalendarBundle\Export\Filter;
use Chill\CalendarBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ScopeFilter implements FilterInterface
{
protected TranslatorInterface $translator;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper
) {
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->join('cal.user', 'u');
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('u.mainScope', ':scope');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('scope', $data['scope']);
}
public function applyOn()
{
return Declarations::CALENDAR_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('scope', EntityType::class, [
'class' => Scope::class,
'choice_label' => function (Scope $s) {
return $this->translatableStringHelper->localize(
$s->getName()
);
},
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string')
{
$scopes = [];
foreach ($data['scope'] as $s) {
$scopes[] = $this->translatableStringHelper->localize(
$s->getName()
);
}
return ['Filtered by agent scope: only %scopes%', [
'%scopes%' => implode(', ou ', $scopes),
]];
}
public function getTitle()
{
return 'Filter appointments by agent scope';
}
}

View File

@ -0,0 +1,104 @@
services:
## Indicators
chill.calendar.export.count_appointments:
class: Chill\CalendarBundle\Export\Export\CountAppointments
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: count_appointments }
chill.calendar.export.average_duration_appointments:
class: Chill\CalendarBundle\Export\Export\StatAppointmentAvgDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: average_duration_appointments }
chill.calendar.export.sum_duration_appointments:
class: Chill\CalendarBundle\Export\Export\StatAppointmentSumDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: sum_duration_appointments }
## Filters
chill.calendar.export.agent_filter:
class: Chill\CalendarBundle\Export\Filter\AgentFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: agent_filter }
chill.calendar.export.job_filter:
class: Chill\CalendarBundle\Export\Filter\JobFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: job_filter }
chill.calendar.export.scope_filter:
class: Chill\CalendarBundle\Export\Filter\ScopeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: scope_filter }
chill.calendar.export.between_dates_filter:
class: Chill\CalendarBundle\Export\Filter\BetweenDatesFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: between_dates_filter }
## Aggregator
chill.calendar.export.agent_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\AgentAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: agent_aggregator }
chill.calendar.export.job_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\JobAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: job_aggregator }
chill.calendar.export.scope_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\ScopeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: scope_aggregator }
chill.calendar.export.location_type_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationTypeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_type_aggregator }
chill.calendar.export.location_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_aggregator }
chill.calendar.export.cancel_reason_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\CancelReasonAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: cancel_reason_aggregator }
chill.calendar.export.month_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\MonthYearAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: month_aggregator }

View File

@ -69,3 +69,36 @@ invite:
declined: Refusé
pending: En attente
tentative: Accepté provisoirement
# exports
Exports of calendar: Exports des rendez-vous
Count appointments: Nombre de rendez-vous
Count appointments by various parameters.: Compte le nombre de rendez-vous en fonction de différents paramètres.
Average appointment duration: Moyenne de la durée des rendez-vous
Get the average of appointment duration according to various filters: Calcule la moyenne des durées des rendez-vous en fonction de différents paramètres.
Sum of appointment durations: Somme de la durée des rendez-vous
Get the sum of appointment durations according to various filters: Calcule la somme des durées des rendez-vous en fonction de différents paramètres.
'Filtered by agent: only %agents%': "Filtré par agents: uniquement %agents%"
Filter appointments by agent: Filtrer les rendez-vous par agents
Filter appointments by agent job: Filtrer les rendez-vous par métiers des agents
'Filtered by agent job: only %jobs%': 'Filtré par métiers des agents: uniquement les %jobs%'
Filter appointments by agent scope: Filtrer les rendez-vous par services des agents
'Filtered by agent scope: only %scopes%': 'Filtré par services des agents: uniquement les services %scopes%'
Filter appointments between certain dates: Filtrer les rendez-vous par date du rendez-vous
'Filtered by appointments between %dateFrom% and %dateTo%': 'Filtré par rendez-vous entre %dateFrom% et %dateTo%'
Group appointments by agent: Grouper les rendez-vous par agent
Group appointments by agent job: Grouper les rendez-vous par métier de l'agent
Group appointments by agent scope: Grouper les rendez-vous par service de l'agent
Group appointments by location type: Grouper les rendez-vous par type de localisation
Group appointments by location: Grouper les rendez-vous par lieu de rendez-vous
Group appointments by cancel reason: Grouper les rendez-vous par motif d'annulation
Group appointments by month and year: Grouper les rendez-vous par mois et année
Scope: Service
Job: Métier
Location type: Type de localisation
Location: Lieu de rendez-vous
by month and year: Par mois et année

View File

@ -188,7 +188,7 @@ class CustomFieldChoice extends AbstractCustomField
$choices = [];
foreach ($cf->getOptions()[self::CHOICES] as $choice) {
if (false === $choices['active']) {
if (false === $choice['active']) {
continue;
}
$choices[$choice['slug']] = $this->translatableStringHelper

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Entity;
use Chill\MainBundle\Entity\HasScopesInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\Mapping as ORM;
@ -18,7 +19,7 @@ use Doctrine\ORM\Mapping as ORM;
* @ORM\Entity
* @ORM\Table("chill_doc.accompanyingcourse_document")
*/
class AccompanyingCourseDocument extends Document
class AccompanyingCourseDocument extends Document implements HasScopesInterface
{
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
@ -31,6 +32,15 @@ class AccompanyingCourseDocument extends Document
return $this->course;
}
public function getScopes(): iterable
{
if (null !== $this->course) {
return [];
}
return $this->course->getScopes();
}
public function setCourse(?AccompanyingPeriod $course): self
{
$this->course = $course;

View File

@ -16,8 +16,6 @@ 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\HasScopeInterface;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
@ -26,7 +24,7 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\MappedSuperclass
*/
class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdateInterface
class Document implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
@ -70,13 +68,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate
*/
private $object;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*
* @var \Chill\MainBundle\Entity\Scope The document's center
*/
private $scope;
/**
* @ORM\ManyToOne(targetEntity="Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate")
*/
@ -122,16 +113,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate
return $this->object;
}
/**
* Get scope.
*
* @return \Chill\MainBundle\Entity\Scope
*/
public function getScope(): ?Scope
{
return $this->scope;
}
public function getTemplate(): ?DocGeneratorTemplate
{
return $this->template;
@ -175,13 +156,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate
return $this;
}
public function setScope($scope): self
{
$this->scope = $scope;
return $this;
}
public function setTemplate(?DocGeneratorTemplate $template): self
{
$this->template = $template;

View File

@ -13,6 +13,7 @@ namespace Chill\DocStoreBundle\Entity;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Scope;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\Mapping as ORM;
@ -27,6 +28,13 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
*/
private Person $person;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*
* @var \Chill\MainBundle\Entity\Scope The document's center
*/
private $scope;
public function getCenter()
{
return $this->getPerson()->getCenter();
@ -37,10 +45,22 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
return $this->person;
}
public function getScope(): ?Scope
{
return $this->scope;
}
public function setPerson($person): self
{
$this->person = $person;
return $this;
}
public function setScope($scope): self
{
$this->scope = $scope;
return $this;
}
}

View File

@ -88,10 +88,5 @@ class AccompanyingCourseDocumentType extends AbstractType
$resolver->setDefaults([
'data_class' => Document::class,
]);
// $resolver->setRequired(['role', 'center'])
// ->setAllowedTypes('role', [ \Symfony\Component\Security\Core\Role\Role::class ])
// ->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ])
// ;
}
}

View File

@ -65,7 +65,7 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
$this->addACL($qb, $person);
foreach ($orderBy as $field => $order) {
$qb->addOrderBy($field, $order);
$qb->addOrderBy('d.' . $field, $order);
}
$qb->setFirstResult($offset)->setMaxResults($limit);

View File

@ -106,6 +106,10 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
) {
return false;
}
if (self::CREATE === $attribute && null !== $subject->getCourse()) {
return $this->voterHelper->voteOnAttribute($attribute, $subject->getCourse(), $token);
}
}
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);

View File

@ -35,10 +35,10 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
$menu->addChild('Events', [
'route' => 'chill_event_admin_index',
])
])
->setAttribute('class', 'list-group-item-header')
->setExtras([
'order' => 6500
'order' => 6500,
]);
$menu->addChild('Event type', [

View File

@ -126,7 +126,7 @@ class Version20160318111334 extends AbstractMigration
$this->addSql('ALTER TABLE chill_event_participation '
. 'ADD CONSTRAINT FK_4E7768AC217BBB47 '
. 'FOREIGN KEY (person_id) '
. 'REFERENCES Person (id) '
. 'REFERENCES chill_person_person(id) '
. 'NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_participation '
. 'ADD CONSTRAINT FK_4E7768ACD60322AC '

View File

@ -86,6 +86,9 @@ class ExportController extends AbstractController
{
/** @var \Chill\MainBundle\Export\ExportManager $exportManager */
$exportManager = $this->exportManager;
$export = $exportManager->getExport($alias);
$key = $request->query->get('key', null);
[$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key);
@ -100,7 +103,8 @@ class ExportController extends AbstractController
$viewVariables = [
'alias' => $alias,
'export' => $exportManager->getExport($alias),
'export' => $export,
'export_group' => $this->getExportGroup($export),
];
if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) {
@ -316,6 +320,7 @@ class ExportController extends AbstractController
'form' => $form->createView(),
'export_alias' => $alias,
'export' => $export,
'export_group' => $this->getExportGroup($export),
]);
}
@ -371,6 +376,7 @@ class ExportController extends AbstractController
[
'form' => $form->createView(),
'export' => $export,
'export_group' => $this->getExportGroup($export),
]
);
}
@ -514,10 +520,26 @@ class ExportController extends AbstractController
[
'form' => $form->createView(),
'export' => $export,
'export_group' => $this->getExportGroup($export),
]
);
}
private function getExportGroup($target): string
{
$exportManager = $this->exportManager;
$groups = $exportManager->getExportsGrouped(true);
foreach ($groups as $group => $array) {
foreach ($array as $alias => $export) {
if ($export === $target) {
return $group;
}
}
}
}
/**
* get the next step. If $reverse === true, the previous step is returned.
*

View File

@ -92,5 +92,8 @@ final class PostalCodeAPIController extends ApiController
$qb->where('e.country = :country')
->setParameter('country', $request->query->get('country'));
}
$qb->andWhere('e.origin = :zero')
->setParameter('zero', 0);
}
}

View File

@ -12,7 +12,9 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class UserApiController extends ApiController
@ -58,4 +60,14 @@ class UserApiController extends ApiController
['groups' => ['read']]
);
}
/**
* @param QueryBuilder $query
*/
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {
$query->andWhere($query->expr()->eq('e.enabled', "'TRUE'"));
}
}
}

View File

@ -0,0 +1,25 @@
<?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);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
class UserJobApiController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {
$query->andWhere($query->expr()->eq('e.active', "'TRUE'"));
}
}
}

View File

@ -19,8 +19,10 @@ use Chill\MainBundle\Controller\LanguageController;
use Chill\MainBundle\Controller\LocationController;
use Chill\MainBundle\Controller\LocationTypeController;
use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\Extract;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
@ -30,6 +32,7 @@ use Chill\MainBundle\Doctrine\DQL\Replace;
use Chill\MainBundle\Doctrine\DQL\Similarity;
use Chill\MainBundle\Doctrine\DQL\STContains;
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
use Chill\MainBundle\Doctrine\DQL\ToChar;
use Chill\MainBundle\Doctrine\DQL\Unaccent;
use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator;
use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType;
@ -240,6 +243,10 @@ class ChillMainExtension extends Extension implements
'ST_CONTAINS' => STContains::class,
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
],
'datetime_functions' => [
'EXTRACT' => Extract::class,
'TO_CHAR' => ToChar::class,
],
],
'hydrators' => [
'chill_flat_hierarchy_list' => FlatHierarchyEntityHydrator::class,
@ -504,6 +511,7 @@ class ChillMainExtension extends Extension implements
'name' => 'user_job',
'base_path' => '/api/1.0/main/user-job',
'base_role' => 'ROLE_USER',
'controller' => UserJobApiController::class,
'actions' => [
'_index' => [
'methods' => [

View File

@ -0,0 +1,61 @@
<?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);
namespace Chill\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\DateDiffFunction;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\PathExpression;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Extract postgresql function
* https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT.
*
* Usage : EXTRACT(field FROM timestamp)
* TODO allow interval usage -> EXTRACT(field FROM interval)
*/
class Extract extends FunctionNode
{
private string $field;
private $value;
//private PathExpression $value;
//private FunctionNode $value;
//private DateDiffFunction $value;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'EXTRACT(%s FROM %s)',
$this->field,
$this->value->dispatch($sqlWalker)
);
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$parser->match(Lexer::T_IDENTIFIER);
$this->field = $parser->getLexer()->token['value'];
$parser->match(Lexer::T_FROM);
//$this->value = $parser->ScalarExpression();
$this->value = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -0,0 +1,46 @@
<?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);
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;
/**
* Usage : TO_CHAR(datetime, fmt).
*/
class ToChar extends FunctionNode
{
private $datetime;
private $fmt;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
'TO_CHAR(%s, %s)',
$sqlWalker->walkArithmeticPrimary($this->datetime),
$sqlWalker->walkArithmeticPrimary($this->fmt)
);
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->datetime = $parser->ArithmeticExpression();
$parser->match(Lexer::T_COMMA);
$this->fmt = $parser->StringExpression();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -0,0 +1,72 @@
<?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);
namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="chill_main_geographical_unit")
* @ORM\Entity
*/
class GeographicalUnit
{
/**
* @ORM\Column(type="text", nullable=true)
*/
private $geom;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $layerName;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $unitName;
public function getId(): ?int
{
return $this->id;
}
public function getLayerName(): ?string
{
return $this->layerName;
}
public function getUnitName(): ?string
{
return $this->unitName;
}
public function setLayerName(?string $layerName): self
{
$this->layerName = $layerName;
return $this;
}
public function setUnitName(?string $unitName): self
{
$this->unitName = $unitName;
return $this;
}
}

View File

@ -139,10 +139,8 @@ interface ExportInterface extends ExportElementInterface
/**
* Return the required Role to execute the Export.
*
* @return \Symfony\Component\Security\Core\Role\Role
*/
public function requiredRole();
public function requiredRole(): string;
/**
* Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface)

View File

@ -550,7 +550,7 @@ class ExportManager
if (null === $centers) {
$centers = $this->authorizationHelper->getReachableCenters(
$this->user,
$role->getRole(),
$role
);
}
@ -568,6 +568,10 @@ class ExportManager
'role' => $role->getRole(),
]);
///// Bypasse les autorisations qui empêche d'afficher les nouveaux exports
return true;
///// TODO supprimer le return true
return false;
}
}

View File

@ -449,7 +449,9 @@ class SpreadSheetFormatter implements FormatterInterface
protected function getTitle()
{
return $this->translator->trans($this->export->getTitle());
$title = $this->translator->trans($this->export->getTitle());
return substr($title, 0, 30) . '…';
}
protected function initializeCache($key)

View File

@ -26,9 +26,9 @@ interface ModifierInterface extends ExportElementInterface
* If null, will used the ExportInterface::requiredRole role from
* the current executing export.
*
* @return \Symfony\Component\Security\Core\Role\Role|null A role required to execute this ModifiersInterface
* @return string|null A role required to execute this ModifiersInterface
*/
public function addRole();
public function addRole(): ?string;
/**
* Alter the query initiated by the export, to add the required statements

View File

@ -59,7 +59,7 @@ class CommentType extends AbstractType
$view->vars = array_replace(
$view->vars,
[
'hideLabel' => true,
'fullWidth' => true,
]
);
}

View File

@ -75,8 +75,6 @@ class PickCenterType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$export = $this->exportManager->getExport($options['export_alias']);
$centers = $this->authorizationHelper->getReachableCenters(
$this->user,

View File

@ -46,7 +46,7 @@ class PrivateCommentType extends AbstractType
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['hideLabel'] = true;
$view->vars['fullWidth'] = true;
}
public function configureOptions(OptionsResolver $resolver)

View File

@ -108,7 +108,7 @@ class ScopePickerType extends AbstractType
$view->vars = array_replace(
$view->vars,
[
'hideLabel' => true,
'fullWidth' => true,
]
);
}

View File

@ -14,6 +14,7 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Country;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
final class CountryRepository implements ObjectRepository
@ -25,6 +26,11 @@ final class CountryRepository implements ObjectRepository
$this->repository = $entityManager->getRepository(Country::class);
}
public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder
{
return $this->repository->createQueryBuilder($alias, $indexBy);
}
public function find($id, $lockMode = null, $lockVersion = null): ?Country
{
return $this->repository->find($id, $lockMode, $lockVersion);

View File

@ -46,7 +46,6 @@ require('../lib/collection/index.js');
require('../lib/breadcrumb/index.js');
require('../lib/download-report/index.js');
require('../lib/select_interactive_loading/index.js');
require('../lib/export-list/index.js');
//require('../lib/show_hide/index.js');
//require('../lib/tabs/index.js');

View File

@ -99,11 +99,14 @@ div.flex-table {
div.item-bloc {
flex-direction: column;
&:nth-child(even) {
background-color: $gray-200;
&:not(.no-altern) { // class to avoid even/odd
.chill-user-quote {
background-color: shade-color($gray-200, 5%)
&:nth-child(even) {
background-color: $gray-200;
.chill-user-quote {
background-color: shade-color($gray-200, 5%)
}
}
}

View File

@ -1,20 +0,0 @@
.container-export {
margin-left: 1rem;
margin-right: 1rem;
.export-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.export-list__element {
min-width: 18rem;
max-width: 20rem;
padding: 1rem;
margin: 1rem;
flex-grow: 1;
border: 1px solid var(--chill-gray);
}
}
}

View File

@ -1 +0,0 @@
require('./export-list.scss');

View File

@ -1,14 +1,23 @@
// Configuration
/*
* This shared file is used in Chill sass sheets and Vue sass styles to use some bootstrap/chill variables.
* Search on @import 'ChillMainAssets/module/bootstrap/shared';
*/
// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
@import "bootstrap/scss/functions";
/*
* Replaced by CHILL variables
* it is necessary to keep the possibility of making a diff with original !
* original: "bootstrap/scss/variables";
*/
// 2. Include any default variable overrides here
@import "custom/_variables";
// 3. Include remainder of required Bootstrap stylesheets
@import "bootstrap/scss/variables";
// 4. Include any default map overrides here
@import "custom/_maps";
// 5. Include remainder of required parts
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/root";

View File

@ -1,18 +1,8 @@
/*!
* Bootstrap v5.0.1 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*
* Enable / disable bootstrap assets
*/
// Bootstrap configuration files are shared with chill entrypoint
// Some Bootstrap variables and configuration files are shared with chill entrypoint
@import "shared";
// scss-docs-start import-stack
// Layout & components
@import "bootstrap/scss/root";
// 6. Optionally include any other parts as needed
@import "bootstrap/scss/utilities";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@ -42,14 +32,11 @@
@import "bootstrap/scss/carousel";
@import "bootstrap/scss/spinners";
@import "bootstrap/scss/offcanvas";
// Helpers
@import "bootstrap/scss/helpers";
// Utilities
// 7. Optionally include utilities API last to generate classes based on the Sass map in
@import "bootstrap/scss/utilities/api";
// scss-docs-end import-stack
// CHILL custom
// 8. Add additional custom code here
@import "custom";
@import "custom/_debug";

View File

@ -0,0 +1,69 @@
//// CHILL OVERRIDE DEFAULT BOOTSTRAP COLORS MAP
// 'beige' variations
$beige-100: tint-color($beige, 80%);
$beige-200: tint-color($beige, 60%);
$beige-300: tint-color($beige, 40%);
$beige-400: tint-color($beige, 20%);
$beige-500: $beige;
$beige-600: shade-color($beige, 20%);
$beige-700: shade-color($beige, 40%);
$beige-800: shade-color($beige, 60%);
$beige-900: shade-color($beige, 80%);
$chill-blue: $blue;
$chill-green: $green;
$chill-green-dark: $green-600;
$chill-orange: $orange;
$chill-yellow: $yellow;
$chill-red: $red;
$chill-beige: $beige;
$chill-pink: $pink;
$chill-dark-gray: $gray-800;
$chill-gray: $gray-600;
$chill-l-gray: $gray-400;
$chill-ll-gray: $gray-300;
$chill-light-gray: $gray-200;
$chill-llight-gray: $gray-100;
$colors: (
"blue": $blue,
"green": $green,
"green-dark": $green-600,
"yellow": $yellow,
"orange": $orange,
"red": $red,
"pink": $pink,
"beige": $beige,
"white": $white,
"gray": $gray-600,
"dark": $gray-800,
"black": $black
);
$chill-colors: (
"chill-blue": $chill-blue,
"chill-green": $chill-green,
"chill-green-dark": $chill-green-dark,
"chill-orange": $chill-orange,
"chill-yellow": $chill-yellow,
"chill-red": $chill-red,
"chill-beige": $chill-beige,
"chill-pink": $chill-pink,
"chill-dark-gray": $chill-dark-gray,
"chill-gray": $chill-gray,
"chill-l-gray": $chill-l-gray,
"chill-ll-gray": $chill-ll-gray,
"chill-light-gray": $chill-light-gray,
"chill-llight-gray": $chill-llight-gray,
);
// Merge the maps
$theme-colors: map-merge($theme-colors, $chill-colors);
// Chill color classes
@each $color, $value in $chill-colors {
.#{$color} {
color: $value;
}
}

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