mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-11 01:04:57 +00:00
Compare commits
72 Commits
upgrade/sy
...
upgrade-ph
Author | SHA1 | Date | |
---|---|---|---|
d1a4891b9d
|
|||
5749660760 | |||
b679dbe26c
|
|||
6c3fa5cb98
|
|||
6bdd1f31d3 | |||
ff3dab0934
|
|||
977299192f | |||
77997e2b6f | |||
6e618e688b | |||
dad36927f3 | |||
f5448f9d95 | |||
a31c4063a1 | |||
c8f95528c0 | |||
20023dff67
|
|||
d82a3e0ff6
|
|||
04359f27c6
|
|||
5109490aad
|
|||
8b82e0c535 | |||
5351223d44 | |||
674e057f67
|
|||
b2e79b677b
|
|||
748e566c7e
|
|||
1f4c51f3bc | |||
dc6eeccaab | |||
0083842509
|
|||
ca7be4ecd0 | |||
785c70fe92 | |||
8bbca7e61a
|
|||
5d21612c2e | |||
fb9f182edd | |||
bbd3d2a83f
|
|||
e87420dc57 | |||
f1bf02d2b4 | |||
8a35c2e2ee | |||
fbd555e89a
|
|||
66dc027354
|
|||
8863e0a92e
|
|||
db9fef095a
|
|||
1df2342c49 | |||
addbdacee8 | |||
8a684734e7
|
|||
1abaf2acb0 | |||
a0ae1f0d0f | |||
2554da9dd8
|
|||
3e3f20993d
|
|||
997f3cdb09
|
|||
ab5ad7ae14
|
|||
36413f16c3
|
|||
f75b90cb26
|
|||
1956836f88
|
|||
ea4294d12d
|
|||
c73e57ad73
|
|||
5b729e1cb1
|
|||
229af2e4f9
|
|||
e80a6e417b
|
|||
c5989de120
|
|||
fcbc00d0f1
|
|||
722f053f06
|
|||
97b7ff2e43
|
|||
f3e0302f3f
|
|||
4974995ea2
|
|||
f2e1c73f37
|
|||
cdaca533a0
|
|||
73c0dd0e9e | |||
8fd9010ea5 | |||
488a0e5f0c | |||
cb0ff88318 | |||
be965e8698 | |||
241e605ea6 | |||
fe3d437096 | |||
c1f5f02c41 | |||
087ada2250 |
@@ -34,6 +34,7 @@
|
|||||||
"sensio/framework-extra-bundle": "^5.5",
|
"sensio/framework-extra-bundle": "^5.5",
|
||||||
"spomky-labs/base64url": "^2.0",
|
"spomky-labs/base64url": "^2.0",
|
||||||
"symfony/browser-kit": "^4.4",
|
"symfony/browser-kit": "^4.4",
|
||||||
|
"symfony/clock": "^6.2",
|
||||||
"symfony/css-selector": "^4.4",
|
"symfony/css-selector": "^4.4",
|
||||||
"symfony/expression-language": "^4.4",
|
"symfony/expression-language": "^4.4",
|
||||||
"symfony/form": "^4.4",
|
"symfony/form": "^4.4",
|
||||||
|
203
docs/source/development/entity-info.rst
Normal file
203
docs/source/development/entity-info.rst
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS
|
||||||
|
Permission is granted to copy, distribute and/or modify this document
|
||||||
|
under the terms of the GNU Free Documentation License, Version 1.3
|
||||||
|
or any later version published by the Free Software Foundation;
|
||||||
|
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||||
|
A copy of the license is included in the section entitled "GNU
|
||||||
|
Free Documentation License".
|
||||||
|
|
||||||
|
.. _entity-info:
|
||||||
|
|
||||||
|
Stats about event on entity in php world
|
||||||
|
########################################
|
||||||
|
|
||||||
|
It is necessary to be able to gather information about events for some entities:
|
||||||
|
|
||||||
|
- when the event has been done;
|
||||||
|
- who did it;
|
||||||
|
- ...
|
||||||
|
|
||||||
|
Those "infos" are not linked with right management, like describe in :ref:`timelines`.
|
||||||
|
|
||||||
|
|
||||||
|
“infos” for some stats and info about an entity
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Building an info means:
|
||||||
|
|
||||||
|
- create an Entity, and map this entity to a SQL view (not a regular table);
|
||||||
|
- use the framework to build this entity dynamically.
|
||||||
|
|
||||||
|
A framework api is built to be able to build multiple “infos” entities
|
||||||
|
through “union” views:
|
||||||
|
|
||||||
|
- use a command ``bin/console chill:db:sync-views`` to synchronize view (create view if it does not exists, or update
|
||||||
|
views when new SQL parts are added in the UNION query. Internally, this command call a new ``ViewEntityInfoManager``,
|
||||||
|
which iterate over available views to build the SQL;
|
||||||
|
- one can create a new “view entity info” by implementing a
|
||||||
|
``ViewEntityInfoProviderInterface``
|
||||||
|
- this implementation of the interface is free to create another
|
||||||
|
interface for building each part of the UNION query. This interface
|
||||||
|
is created for AccompanyingPeriodInfo:
|
||||||
|
``Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface``
|
||||||
|
|
||||||
|
So, converting new “events” into rows for ``AccompanyingPeriodInfo`` is
|
||||||
|
just implementing this interface!
|
||||||
|
|
||||||
|
Implementation for AccompanyingPeriod (``AccompanyingPeriod/AccompanyingPeriodInfo``)
|
||||||
|
-------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
A class is created for computing some statistical info for an
|
||||||
|
AccompanyingPeriod: ``AccompanyingPeriod/AccompanyingPeriodInfo``. This
|
||||||
|
contains information about “something happens”, who did it and when.
|
||||||
|
|
||||||
|
Having those info in table answer some questions like:
|
||||||
|
|
||||||
|
- when is the last and the first action (AccompanyingPeriodWork,
|
||||||
|
Activity, AccompanyingPeriodWorkEvaluation, …) on the period;
|
||||||
|
- who is “acting” on the period, and when is the last “action” for each
|
||||||
|
user.
|
||||||
|
|
||||||
|
The AccompanyingPeriod info is mapped to a SQL view, not a table. The
|
||||||
|
sql view is built dynamically (see below), and gather infos from
|
||||||
|
ActivityBundle, PersonBundle, CalendarBundle, … It is possible to create
|
||||||
|
custom bundle and add info on this view.
|
||||||
|
|
||||||
|
.. code:: php
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @ORM\Entity()
|
||||||
|
* @ORM\Table(name="view_chill_person_accompanying_period_info") <==== THIS IS A VIEW, NOT A TABLE
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodInfo
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Why do we need this ?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
For multiple jobs in PHP world:
|
||||||
|
|
||||||
|
- moving the accompanying period to another steps when inactive,
|
||||||
|
automatically;
|
||||||
|
- listing all the users which are intervening on the action on a new
|
||||||
|
“Liste des intervenants” page;
|
||||||
|
- filtering on exports
|
||||||
|
|
||||||
|
Later, we will launch automatic anonymise for accompanying period and
|
||||||
|
all related entities through this information.
|
||||||
|
|
||||||
|
How is built the SQL views which is mapped to “info” entities ?
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The AccompanyingPeriodInfo entity is mapped by a SQL view (not a regular
|
||||||
|
table).
|
||||||
|
|
||||||
|
The sql view is built dynamically, it is a SQL view like this, for now (April 2023):
|
||||||
|
|
||||||
|
.. code:: sql
|
||||||
|
|
||||||
|
create view view_chill_person_accompanying_period_info
|
||||||
|
(accompanyingperiod_id, relatedentity, relatedentityid, user_id, infodate, discriminator, metadata) as
|
||||||
|
SELECT w.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork'::text AS relatedentity,
|
||||||
|
w.id AS relatedentityid,
|
||||||
|
cpapwr.user_id,
|
||||||
|
w.enddate AS infodate,
|
||||||
|
'accompanying_period_work_end'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work w
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON w.id = cpapwr.accompanyingperiodwork_id
|
||||||
|
WHERE w.enddate IS NOT NULL
|
||||||
|
UNION
|
||||||
|
SELECT cpapw.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity,
|
||||||
|
e.id AS relatedentityid,
|
||||||
|
e.updatedby_id AS user_id,
|
||||||
|
e.updatedat AS infodate,
|
||||||
|
'accompanying_period_work_evaluation_updated_at'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
WHERE e.updatedat IS NOT NULL
|
||||||
|
UNION
|
||||||
|
SELECT cpapw.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity,
|
||||||
|
e.id AS relatedentityid,
|
||||||
|
cpapwr.user_id,
|
||||||
|
e.maxdate AS infodate,
|
||||||
|
'accompanying_period_work_evaluation_start'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id
|
||||||
|
WHERE e.maxdate IS NOT NULL
|
||||||
|
UNION
|
||||||
|
SELECT cpapw.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity,
|
||||||
|
e.id AS relatedentityid,
|
||||||
|
cpapwr.user_id,
|
||||||
|
e.startdate AS infodate,
|
||||||
|
'accompanying_period_work_evaluation_start'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id
|
||||||
|
UNION
|
||||||
|
SELECT cpapw.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument'::text AS relatedentity,
|
||||||
|
doc.id AS relatedentityid,
|
||||||
|
doc.updatedby_id AS user_id,
|
||||||
|
doc.updatedat AS infodate,
|
||||||
|
'accompanying_period_work_evaluation_document_updated_at'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work_evaluation_document doc
|
||||||
|
JOIN chill_person_accompanying_period_work_evaluation e ON doc.accompanyingperiodworkevaluation_id = e.id
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
WHERE doc.updatedat IS NOT NULL
|
||||||
|
UNION
|
||||||
|
SELECT cpapw.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation'::text AS relatedentity,
|
||||||
|
e.id AS relatedentityid,
|
||||||
|
cpapwr.user_id,
|
||||||
|
e.maxdate AS infodate,
|
||||||
|
'accompanying_period_work_evaluation_max'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id
|
||||||
|
WHERE e.maxdate IS NOT NULL
|
||||||
|
UNION
|
||||||
|
SELECT w.accompanyingperiod_id,
|
||||||
|
'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork'::text AS relatedentity,
|
||||||
|
w.id AS relatedentityid,
|
||||||
|
cpapwr.user_id,
|
||||||
|
w.startdate AS infodate,
|
||||||
|
'accompanying_period_work_start'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM chill_person_accompanying_period_work w
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON w.id = cpapwr.accompanyingperiodwork_id
|
||||||
|
UNION
|
||||||
|
SELECT activity.accompanyingperiod_id,
|
||||||
|
'Chill\ActivityBundle\Entity\Activity'::text AS relatedentity,
|
||||||
|
activity.id AS relatedentityid,
|
||||||
|
au.user_id,
|
||||||
|
activity.date AS infodate,
|
||||||
|
'activity_date'::text AS discriminator,
|
||||||
|
'{}'::jsonb AS metadata
|
||||||
|
FROM activity
|
||||||
|
LEFT JOIN activity_user au ON activity.id = au.activity_id
|
||||||
|
WHERE activity.accompanyingperiod_id IS NOT NULL;
|
||||||
|
|
||||||
|
As you can see, the view gather multiple SELECT queries and bind them
|
||||||
|
with UNION.
|
||||||
|
|
||||||
|
Each SELECT query is built dynamically, through a class implementing an
|
||||||
|
interface: ``Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface``, `like
|
||||||
|
here <https://gitlab.com/Chill-Projet/chill-bundles/-/blob/master/src/Bundle/ChillPersonBundle/Service/EntityInfo/AccompanyingPeriodInfoQueryPart/AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo.php>`__
|
||||||
|
|
||||||
|
To add new `SELECT` query in different `UNION` parts in the sql view, create a
|
||||||
|
service and implements this interface: ``Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface``.
|
@@ -35,6 +35,7 @@ As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the fram
|
|||||||
manual/index.rst
|
manual/index.rst
|
||||||
Assets <assets.rst>
|
Assets <assets.rst>
|
||||||
Cron Jobs <cronjob.rst>
|
Cron Jobs <cronjob.rst>
|
||||||
|
Info about entities <entity-info.rst>
|
||||||
|
|
||||||
Layout and UI
|
Layout and UI
|
||||||
**************
|
**************
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
A copy of the license is included in the section entitled "GNU
|
A copy of the license is included in the section entitled "GNU
|
||||||
Free Documentation License".
|
Free Documentation License".
|
||||||
|
|
||||||
|
.. _timelines:
|
||||||
|
|
||||||
Timelines
|
Timelines
|
||||||
*********
|
*********
|
||||||
|
|
||||||
|
@@ -151,6 +151,7 @@ This script will :
|
|||||||
|
|
||||||
# mount into to container
|
# mount into to container
|
||||||
./docker-php.sh
|
./docker-php.sh
|
||||||
|
bin/console chill:db:sync-views
|
||||||
# and load fixtures
|
# and load fixtures
|
||||||
bin/console doctrine:migrations:migrate
|
bin/console doctrine:migrations:migrate
|
||||||
|
|
||||||
@@ -161,7 +162,7 @@ Chill will be available at ``http://localhost:8001.`` Currently, there isn't any
|
|||||||
|
|
||||||
# mount into to container
|
# mount into to container
|
||||||
./docker-php.sh
|
./docker-php.sh
|
||||||
# and load fixtures
|
# and load fixtures (do not this for production)
|
||||||
bin/console doctrine:fixtures:load --purge-with-truncate
|
bin/console doctrine:fixtures:load --purge-with-truncate
|
||||||
|
|
||||||
There are several users available:
|
There are several users available:
|
||||||
@@ -204,8 +205,10 @@ How to create the database schema (= run migrations) ?
|
|||||||
# if a container is running
|
# if a container is running
|
||||||
./docker-php.sh
|
./docker-php.sh
|
||||||
bin/console doctrine:migrations:migrate
|
bin/console doctrine:migrations:migrate
|
||||||
|
bin/console chill:db:sync-views
|
||||||
# if not
|
# if not
|
||||||
docker-compose run --user $(id -u) php bin/console doctrine:migrations:migrate
|
docker-compose run --user $(id -u) php bin/console doctrine:migrations:migrate
|
||||||
|
docker-compose run --user $(id -u) php bin/console chill:db:sync-views
|
||||||
|
|
||||||
|
|
||||||
How to read the email sent by the program ?
|
How to read the email sent by the program ?
|
||||||
@@ -236,6 +239,23 @@ How to open a terminal in the project
|
|||||||
# if not
|
# if not
|
||||||
docker-compose run --user $(id -u) php /bin/bash
|
docker-compose run --user $(id -u) php /bin/bash
|
||||||
|
|
||||||
|
How to run cron-jobs ?
|
||||||
|
======================
|
||||||
|
|
||||||
|
Some command must be executed in :ref:`cron jobs <cronjob>`. To execute them:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# if a container is running
|
||||||
|
./docker-php.sh
|
||||||
|
bin/console chill:cron-job:execute
|
||||||
|
# some of them are executed only during the night. So, we have to force the execution during the day:
|
||||||
|
bin/console chill:cron-job:execute 'name-of-the-cron'
|
||||||
|
# if not
|
||||||
|
docker-compose run --user $(id -u) php bin/console chill:cron-job:execute
|
||||||
|
# some of them are executed only during the night. So, we have to force the execution during the day:
|
||||||
|
docker-compose run --user $(id -u) php bin/console chill:cron-job:execute 'name-of-the-cron'
|
||||||
|
|
||||||
How to run composer ?
|
How to run composer ?
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
@@ -38,6 +38,14 @@ This should be adapted to your needs:
|
|||||||
|
|
||||||
* Think about how you will backup your database. Some adminsys find easier to store database outside of docker, which might be easier to administrate or replicate.
|
* Think about how you will backup your database. Some adminsys find easier to store database outside of docker, which might be easier to administrate or replicate.
|
||||||
|
|
||||||
|
Run migrations on each update
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Every time you start a new version, you should apply update the sql schema:
|
||||||
|
|
||||||
|
- running ``bin/console doctrine:migration:migrate`` to run sql migration;
|
||||||
|
- synchonizing sql views to the last state: ``bin/console chill:db:sync-views``
|
||||||
|
|
||||||
Cron jobs
|
Cron jobs
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
@@ -2,11 +2,20 @@
|
|||||||
|
|
||||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:noNamespaceSchemaLocation="tests/app/vendor/phpunit/phpunit/phpunit.xsd"
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
|
||||||
backupGlobals="false"
|
backupGlobals="false"
|
||||||
colors="true"
|
colors="true"
|
||||||
bootstrap="tests/app/tests/bootstrap.php"
|
bootstrap="tests/app/tests/bootstrap.php"
|
||||||
>
|
cacheResultFile=".phpunit.cache/test-results"
|
||||||
|
executionOrder="depends,defects"
|
||||||
|
forceCoversAnnotation="true"
|
||||||
|
beStrictAboutCoversAnnotation="true"
|
||||||
|
beStrictAboutOutputDuringTests="true"
|
||||||
|
beStrictAboutTodoAnnotatedTests="true"
|
||||||
|
convertDeprecationsToExceptions="true"
|
||||||
|
failOnRisky="true"
|
||||||
|
failOnWarning="true"
|
||||||
|
verbose="true">
|
||||||
<php>
|
<php>
|
||||||
<ini name="error_reporting" value="-1" />
|
<ini name="error_reporting" value="-1" />
|
||||||
<server name="APP_ENV" value="test" force="true" />
|
<server name="APP_ENV" value="test" force="true" />
|
||||||
|
@@ -650,8 +650,8 @@ final class ActivityController extends AbstractController
|
|||||||
throw $this->createNotFoundException('Accompanying Period not found');
|
throw $this->createNotFoundException('Accompanying Period not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Add permission
|
// TODO Add permission
|
||||||
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
|
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
|
||||||
} else {
|
} else {
|
||||||
throw $this->createNotFoundException('Person or Accompanying Period not found');
|
throw $this->createNotFoundException('Person or Accompanying Period not found');
|
||||||
}
|
}
|
||||||
|
@@ -26,12 +26,12 @@ final class PersonMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
/**
|
/**
|
||||||
* @var AuthorizationCheckerInterface
|
* @var AuthorizationCheckerInterface
|
||||||
*/
|
*/
|
||||||
protected $authorizationChecker;
|
private $authorizationChecker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var TranslatorInterface
|
* @var TranslatorInterface
|
||||||
*/
|
*/
|
||||||
protected $translator;
|
private $translator;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AuthorizationCheckerInterface $authorizationChecker,
|
AuthorizationCheckerInterface $authorizationChecker,
|
||||||
|
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\ActivityBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class ActivityUsersDateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'activity.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return Activity::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'activity.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'au.user_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'activity.date';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'activity_date';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return '\'{}\'::jsonb';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'activity
|
||||||
|
LEFT JOIN activity_user au on activity.id = au.activity_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return 'activity.accompanyingperiod_id IS NOT NULL';
|
||||||
|
}
|
||||||
|
}
|
@@ -34,6 +34,7 @@ services:
|
|||||||
resource: '../Validator/Constraints/'
|
resource: '../Validator/Constraints/'
|
||||||
|
|
||||||
Chill\ActivityBundle\Service\DocGenerator\:
|
Chill\ActivityBundle\Service\DocGenerator\:
|
||||||
autowire: true
|
|
||||||
autoconfigure: true
|
|
||||||
resource: '../Service/DocGenerator/'
|
resource: '../Service/DocGenerator/'
|
||||||
|
|
||||||
|
Chill\ActivityBundle\Service\EntityInfo\:
|
||||||
|
resource: '../Service/EntityInfo/'
|
||||||
|
@@ -16,8 +16,14 @@
|
|||||||
<td class="el-type">
|
<td class="el-type">
|
||||||
{% if f.isResource %}
|
{% if f.isResource %}
|
||||||
{{ f.resource.name|localize_translatable_string }}
|
{{ f.resource.name|localize_translatable_string }}
|
||||||
|
{% if f.resource.getKind is same as 'other' %}
|
||||||
|
: {{ f.getComment }}
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ f.charge.name|localize_translatable_string }}
|
{{ f.charge.name|localize_translatable_string }}
|
||||||
|
{% if f.charge.getKind is same as 'other' %}
|
||||||
|
: {{ f.getComment }}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ f.amount|format_currency('EUR') }}</td>
|
<td>{{ f.amount|format_currency('EUR') }}</td>
|
||||||
|
@@ -13,6 +13,7 @@ namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
|
|||||||
|
|
||||||
use ArrayObject;
|
use ArrayObject;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\Common\Collections\ReadableCollection;
|
||||||
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
|
||||||
@@ -51,7 +52,9 @@ class CollectionDocGenNormalizer implements ContextAwareNormalizerInterface, Nor
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $data instanceof Collection
|
return $data instanceof ReadableCollection
|
||||||
|| (null === $data && Collection::class === ($context['docgen:expects'] ?? null));
|
|| (null === $data && Collection::class === ($context['docgen:expects'] ?? null))
|
||||||
|
|| (null === $data && ReadableCollection::class === ($context['docgen:expects'] ?? null))
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
|
|||||||
|
|
||||||
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
|
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Doctrine\Common\Collections\ReadableCollection;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Component\PropertyAccess\PropertyAccess;
|
use Symfony\Component\PropertyAccess\PropertyAccess;
|
||||||
@@ -271,6 +272,14 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
|
|||||||
if ($isTranslatable) {
|
if ($isTranslatable) {
|
||||||
$data[$key] = $this->translatableStringHelper
|
$data[$key] = $this->translatableStringHelper
|
||||||
->localize($value);
|
->localize($value);
|
||||||
|
} elseif ($value instanceof ReadableCollection) {
|
||||||
|
// when normalizing collection, we should not preserve keys (to ensure that the result is a list)
|
||||||
|
// this is why we make call to the normalizer again to use the CollectionDocGenNormalizer
|
||||||
|
$data[$key] =
|
||||||
|
$this->normalizer->normalize($value, $format, array_merge(
|
||||||
|
$objectContext,
|
||||||
|
$attribute->getNormalizationContextForGroups($expectedGroups)
|
||||||
|
));
|
||||||
} elseif (is_iterable($value)) {
|
} elseif (is_iterable($value)) {
|
||||||
$arr = [];
|
$arr = [];
|
||||||
|
|
||||||
|
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\DocGeneratorBundle\tests\Serializer\Normalizer;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class CollectionDocGenNormalizerTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->normalizer = self::$container->get(NormalizerInterface::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNormalizeFilteredArray(): void
|
||||||
|
{
|
||||||
|
$coll = new ArrayCollection([
|
||||||
|
(object) ['v' => 'foo'],
|
||||||
|
(object) ['v' => 'bar'],
|
||||||
|
(object) ['v' => 'baz'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
//filter to get non continuous indexes
|
||||||
|
$criteria = new Criteria();
|
||||||
|
$criteria->where(Criteria::expr()->neq('v', 'bar'));
|
||||||
|
|
||||||
|
$filtered = $coll->matching($criteria);
|
||||||
|
$normalized = $this->normalizer->normalize($filtered, 'docgen', []);
|
||||||
|
|
||||||
|
self::assertIsArray($normalized);
|
||||||
|
self::assertArrayHasKey(0, $normalized);
|
||||||
|
self::assertArrayHasKey(1, $normalized);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,51 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<a :class="props.classes" @click="download_and_open($event)">
|
<a v-if="!state.is_ready" :class="props.classes" @click="download_and_open($event)">
|
||||||
<i class="fa fa-download"></i>
|
<i class="fa fa-download"></i>
|
||||||
Télécharger
|
Télécharger
|
||||||
</a>
|
</a>
|
||||||
|
<a v-else :class="props.classes" target="_blank" :type="props.storedObject.type" :download="buildDocumentName()" :href="state.href_url" ref="open_button">
|
||||||
|
<i class="fa fa-external-link"></i>
|
||||||
|
Ouvrir
|
||||||
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {reactive} from "vue";
|
import {reactive, ref, nextTick, onMounted} from "vue";
|
||||||
import {build_download_info_link, download_and_decrypt_doc} from "./helpers";
|
import {build_download_info_link, download_and_decrypt_doc} from "./helpers";
|
||||||
import mime from "mime";
|
import mime from "mime";
|
||||||
import {StoredObject} from "../../types";
|
import {StoredObject} from "../../types";
|
||||||
|
|
||||||
interface DownloadButtonConfig {
|
interface DownloadButtonConfig {
|
||||||
storedObject: StoredObject,
|
storedObject: StoredObject,
|
||||||
classes: {[k: string]: boolean},
|
classes: { [k: string]: boolean },
|
||||||
filename?: string,
|
filename?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DownloadButtonState {
|
interface DownloadButtonState {
|
||||||
content: null|string
|
is_ready: boolean,
|
||||||
|
is_running: boolean,
|
||||||
|
href_url: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<DownloadButtonConfig>();
|
const props = defineProps<DownloadButtonConfig>();
|
||||||
const state: DownloadButtonState = reactive({content: null});
|
const state: DownloadButtonState = reactive({is_ready: false, is_running: false, href_url: "#"});
|
||||||
|
|
||||||
|
const open_button = ref<HTMLAnchorElement | null>(null);
|
||||||
|
|
||||||
|
function buildDocumentName(): string {
|
||||||
|
const document_name = props.filename || 'document';
|
||||||
|
const ext = mime.getExtension(props.storedObject.type);
|
||||||
|
|
||||||
|
if (null !== ext) {
|
||||||
|
return document_name + '.' + ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
return document_name;
|
||||||
|
}
|
||||||
|
|
||||||
async function download_and_open(event: Event): Promise<void> {
|
async function download_and_open(event: Event): Promise<void> {
|
||||||
const button = event.target as HTMLAnchorElement;
|
const button = event.target as HTMLAnchorElement;
|
||||||
|
|
||||||
if (null === state.content) {
|
if (state.is_running) {
|
||||||
event.preventDefault();
|
console.log('state is running, aborting');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.is_running = true;
|
||||||
|
|
||||||
|
if (state.is_ready) {
|
||||||
|
console.log('state is ready. This should not happens');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const urlInfo = build_download_info_link(props.storedObject.filename);
|
const urlInfo = build_download_info_link(props.storedObject.filename);
|
||||||
|
let raw;
|
||||||
|
|
||||||
const raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
|
try {
|
||||||
state.content = window.URL.createObjectURL(raw);
|
raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
|
||||||
|
} catch (e) {
|
||||||
button.href = window.URL.createObjectURL(raw);
|
console.error("error while downloading and decrypting document");
|
||||||
button.type = props.storedObject.type;
|
console.error(e);
|
||||||
|
throw e;
|
||||||
button.download = props.filename || 'document';
|
|
||||||
|
|
||||||
const ext = mime.getExtension(props.storedObject.type);
|
|
||||||
if (null !== ext) {
|
|
||||||
button.download = button.download + '.' + ext;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
button.click();
|
console.log('document downloading (and decrypting) successfully');
|
||||||
|
|
||||||
|
console.log('creating the url')
|
||||||
|
state.href_url = window.URL.createObjectURL(raw);
|
||||||
|
console.log('url created', state.href_url);
|
||||||
|
state.is_running = false;
|
||||||
|
state.is_ready = true;
|
||||||
|
console.log('new button marked as ready');
|
||||||
|
console.log('will click on button');
|
||||||
|
|
||||||
|
console.log('openbutton is now', open_button.value);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
console.log('next tick actions');
|
||||||
|
console.log('openbutton after next tick', open_button.value);
|
||||||
|
open_button.value?.click();
|
||||||
|
console.log('open button should have been clicked');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -149,16 +149,21 @@ async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (iv.length === 0) {
|
if (iv.length === 0) {
|
||||||
|
console.log('returning document immediatly');
|
||||||
return rawResponse.blob();
|
return rawResponse.blob();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('start decrypting doc');
|
||||||
|
|
||||||
const rawBuffer = await rawResponse.arrayBuffer();
|
const rawBuffer = await rawResponse.arrayBuffer();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const key = await window.crypto.subtle
|
const key = await window.crypto.subtle
|
||||||
.importKey('jwk', keyData, { name: algo }, false, ['decrypt']);
|
.importKey('jwk', keyData, { name: algo }, false, ['decrypt']);
|
||||||
|
console.log('key created');
|
||||||
const decrypted = await window.crypto.subtle
|
const decrypted = await window.crypto.subtle
|
||||||
.decrypt({ name: algo, iv: iv }, key, rawBuffer);
|
.decrypt({ name: algo, iv: iv }, key, rawBuffer);
|
||||||
|
console.log('doc decrypted');
|
||||||
|
|
||||||
return Promise.resolve(new Blob([decrypted]));
|
return Promise.resolve(new Blob([decrypted]));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
<p>{% transchoice total with { '%pattern%' : pattern } %}%total% events match the search %pattern%{% endtranschoice %}</p>
|
<p>{% transchoice total with { '%pattern%' : pattern } %}%total% events match the search %pattern%{% endtranschoice %}</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
table.events td:last-child {
|
||||||
|
width: 15em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% if events|length > 0 %}
|
{% if events|length > 0 %}
|
||||||
|
|
||||||
<p>{{ 'Results %start%-%end% of %total%'|trans({ '%start%' : start, '%end%': start + events|length, '%total%' : total } ) }}</p>
|
<p>{{ 'Results %start%-%end% of %total%'|trans({ '%start%' : start, '%end%': start + events|length, '%total%' : total } ) }}</p>
|
||||||
|
<table class="table table-bordered border-dark align-middle events">
|
||||||
<table class="table events">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="chill-red">{{ 'Name'|trans }}</th>
|
<th class="chill-red">{{ 'Name'|trans }}</th>
|
||||||
@@ -25,7 +29,7 @@
|
|||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
{# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #}
|
{# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #}
|
||||||
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-dark">
|
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-view">
|
||||||
{{ 'See'|trans }}
|
{{ 'See'|trans }}
|
||||||
</a>
|
</a>
|
||||||
{# {% endif %} #}
|
{# {% endif %} #}
|
||||||
|
@@ -24,7 +24,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>{{ 'Events participation' |trans }}</h2>
|
<h2>{{ 'Events participation' |trans }}</h2>
|
||||||
|
|
||||||
<table class="table table-striped table-bordered mt-3 events">
|
<table class="table table-striped table-bordered border-dark align-middle mt-3 events">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="chill-green">{{ 'Date'|trans }}</th>
|
<th class="chill-green">{{ 'Date'|trans }}</th>
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-chill-green' } }) }}
|
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-submit' } }) }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<h1>{{ 'Details of an event'|trans }}</h1>
|
<h1>{{ 'Details of an event'|trans }}</h1>
|
||||||
|
|
||||||
<table class="table record_properties">
|
<table class="table table-bordered border-dark align-middle">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'Name'|trans }}</th>
|
<th>{{ 'Name'|trans }}</th>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
<p>{% transchoice count %}%count% participations to this event{% endtranschoice %}</p>
|
<p>{% transchoice count %}%count% participations to this event{% endtranschoice %}</p>
|
||||||
|
|
||||||
{% if count > 0 %}
|
{% if count > 0 %}
|
||||||
<table class="table">
|
<table class="table table-bordered border-dark align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ 'Person'|trans }}</th>
|
<th>{{ 'Person'|trans }}</th>
|
||||||
|
@@ -12,13 +12,14 @@ declare(strict_types=1);
|
|||||||
namespace Chill\EventBundle\Search;
|
namespace Chill\EventBundle\Search;
|
||||||
|
|
||||||
use Chill\EventBundle\Entity\Event;
|
use Chill\EventBundle\Entity\Event;
|
||||||
|
use Chill\EventBundle\Repository\EventRepository;
|
||||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Chill\MainBundle\Search\AbstractSearch;
|
use Chill\MainBundle\Search\AbstractSearch;
|
||||||
use Chill\MainBundle\Search\SearchInterface;
|
use Chill\MainBundle\Search\SearchInterface;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
use Doctrine\ORM\EntityRepository;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;
|
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;
|
||||||
|
|
||||||
use function count;
|
use function count;
|
||||||
@@ -40,15 +41,17 @@ class EventSearch extends AbstractSearch
|
|||||||
{
|
{
|
||||||
public const NAME = 'event_regular';
|
public const NAME = 'event_regular';
|
||||||
|
|
||||||
|
private $security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var EntityRepository
|
* @var EventRepository
|
||||||
*/
|
*/
|
||||||
private $er;
|
private $er;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var AuthorizationHelper
|
* @var AuthorizationHelper
|
||||||
*/
|
*/
|
||||||
private $helper;
|
private $authorizationHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var PaginatorFactory
|
* @var PaginatorFactory
|
||||||
@@ -60,21 +63,16 @@ class EventSearch extends AbstractSearch
|
|||||||
*/
|
*/
|
||||||
private $templating;
|
private $templating;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \Chill\MainBundle\Entity\User
|
|
||||||
*/
|
|
||||||
private $user;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
TokenStorageInterface $tokenStorage,
|
Security $security,
|
||||||
EntityRepository $eventRepository,
|
EventRepository $eventRepository,
|
||||||
AuthorizationHelper $authorizationHelper,
|
AuthorizationHelper $authorizationHelper,
|
||||||
TemplatingEngine $templating,
|
TemplatingEngine $templating,
|
||||||
PaginatorFactory $paginatorFactory
|
PaginatorFactory $paginatorFactory
|
||||||
) {
|
) {
|
||||||
$this->user = $tokenStorage->getToken()->getUser();
|
$this->security = $security;
|
||||||
$this->er = $eventRepository;
|
$this->er = $eventRepository;
|
||||||
$this->helper = $authorizationHelper;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
$this->templating = $templating;
|
$this->templating = $templating;
|
||||||
$this->paginationFactory = $paginatorFactory;
|
$this->paginationFactory = $paginatorFactory;
|
||||||
}
|
}
|
||||||
@@ -101,7 +99,7 @@ class EventSearch extends AbstractSearch
|
|||||||
|
|
||||||
if ('html' === $format) {
|
if ('html' === $format) {
|
||||||
return $this->templating->render(
|
return $this->templating->render(
|
||||||
'ChillEventBundle:Event:list.html.twig',
|
'@ChillEvent/Event/list.html.twig',
|
||||||
[
|
[
|
||||||
'events' => $this->search($terms, $start, $limit, $options),
|
'events' => $this->search($terms, $start, $limit, $options),
|
||||||
'pattern' => $this->recomposePattern($terms, $this->getAvailableTerms(), $terms['_domain']),
|
'pattern' => $this->recomposePattern($terms, $this->getAvailableTerms(), $terms['_domain']),
|
||||||
@@ -140,8 +138,10 @@ class EventSearch extends AbstractSearch
|
|||||||
protected function composeQuery(QueryBuilder &$qb, $terms)
|
protected function composeQuery(QueryBuilder &$qb, $terms)
|
||||||
{
|
{
|
||||||
// add security clauses
|
// add security clauses
|
||||||
$reachableCenters = $this->helper
|
$reachableCenters = $this->authorizationHelper->getReachableCenters(
|
||||||
->getReachableCenters($this->user, 'CHILL_EVENT_SEE');
|
$this->security->getUser(),
|
||||||
|
'CHILL_EVENT_SEE'
|
||||||
|
);
|
||||||
|
|
||||||
if (count($reachableCenters) === 0) {
|
if (count($reachableCenters) === 0) {
|
||||||
// add a clause to block all events
|
// add a clause to block all events
|
||||||
@@ -152,8 +152,9 @@ class EventSearch extends AbstractSearch
|
|||||||
$orWhere = $qb->expr()->orX();
|
$orWhere = $qb->expr()->orX();
|
||||||
|
|
||||||
foreach ($reachableCenters as $center) {
|
foreach ($reachableCenters as $center) {
|
||||||
$circles = $this->helper->getReachableScopes(
|
$n = $n+1;
|
||||||
$this->user,
|
$circles = $this->authorizationHelper->getReachableScopes(
|
||||||
|
$this->security->getUser(),
|
||||||
'CHILL_EVENT_SEE',
|
'CHILL_EVENT_SEE',
|
||||||
$center
|
$center
|
||||||
);
|
);
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
services:
|
services:
|
||||||
chill_event.search_events:
|
Chill\EventBundle\Search\EventSearch:
|
||||||
class: Chill\EventBundle\Search\EventSearch
|
|
||||||
arguments:
|
arguments:
|
||||||
- "@security.token_storage"
|
$security: '@Symfony\Component\Security\Core\Security'
|
||||||
- "@chill_event.repository.event"
|
$eventRepository: "@chill_event.repository.event"
|
||||||
- "@chill.main.security.authorization.helper"
|
$authorizationHelper: "@chill.main.security.authorization.helper"
|
||||||
- "@templating"
|
$templating: "@templating"
|
||||||
- "@chill_main.paginator_factory"
|
$paginatorFactory: "@chill_main.paginator_factory"
|
||||||
tags:
|
tags:
|
||||||
- { name: chill.search, alias: 'event_regular' }
|
- { name: chill.search, alias: 'event_regular' }
|
||||||
|
|
||||||
|
@@ -30,7 +30,7 @@ The event was created: L'événement a été créé
|
|||||||
|
|
||||||
#crud participation
|
#crud participation
|
||||||
Edit all the participations: Modifier toutes les participations
|
Edit all the participations: Modifier toutes les participations
|
||||||
Edit the participation: Modifier la participation
|
Edit the participation: Modifier la participation à l'événement
|
||||||
Participation Edit: Modifier une participation
|
Participation Edit: Modifier une participation
|
||||||
Add a participation: Ajouter un participant
|
Add a participation: Ajouter un participant
|
||||||
Participation creation: Ajouter une participation
|
Participation creation: Ajouter une participation
|
||||||
|
@@ -30,6 +30,7 @@ use Chill\MainBundle\Search\SearchApiInterface;
|
|||||||
use Chill\MainBundle\Security\ProvideRoleInterface;
|
use Chill\MainBundle\Security\ProvideRoleInterface;
|
||||||
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
||||||
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||||
|
use Chill\MainBundle\Service\EntityInfo\ViewEntityInfoProviderInterface;
|
||||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||||
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
||||||
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
||||||
@@ -62,6 +63,8 @@ class ChillMainBundle extends Bundle
|
|||||||
->addTag('chill_main.workflow_handler');
|
->addTag('chill_main.workflow_handler');
|
||||||
$container->registerForAutoconfiguration(CronJobInterface::class)
|
$container->registerForAutoconfiguration(CronJobInterface::class)
|
||||||
->addTag('chill_main.cron_job');
|
->addTag('chill_main.cron_job');
|
||||||
|
$container->registerForAutoconfiguration(ViewEntityInfoProviderInterface::class)
|
||||||
|
->addTag('chill_main.entity_info_provider');
|
||||||
|
|
||||||
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
||||||
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Command;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Service\EntityInfo\ViewEntityInfoManager;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class SynchronizeEntityInfoViewsCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ViewEntityInfoManager $viewEntityInfoManager,
|
||||||
|
) {
|
||||||
|
parent::__construct('chill:db:sync-views');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setDescription('Update or create sql views which provide info for various entities');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$this->viewEntityInfoManager->synchronizeOnDB();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@@ -16,14 +16,18 @@ use Chill\MainBundle\Entity\RoleScope;
|
|||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Chill\MainBundle\Form\PermissionsGroupType;
|
use Chill\MainBundle\Form\PermissionsGroupType;
|
||||||
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
|
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
|
||||||
|
use Chill\MainBundle\Repository\PermissionsGroupRepository;
|
||||||
|
use Chill\MainBundle\Repository\RoleScopeRepository;
|
||||||
use Chill\MainBundle\Security\RoleProvider;
|
use Chill\MainBundle\Security\RoleProvider;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||||
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Security\Core\Role\RoleHierarchy;
|
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
@@ -32,62 +36,28 @@ use function array_key_exists;
|
|||||||
/**
|
/**
|
||||||
* Class PermissionsGroupController.
|
* Class PermissionsGroupController.
|
||||||
*/
|
*/
|
||||||
class PermissionsGroupController extends AbstractController
|
final class PermissionsGroupController extends AbstractController
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var RoleHierarchy
|
|
||||||
*/
|
|
||||||
private $roleHierarchy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var RoleProvider
|
|
||||||
*/
|
|
||||||
private $roleProvider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var TranslatableStringHelper
|
|
||||||
*/
|
|
||||||
private $translatableStringHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var TranslatorInterface
|
|
||||||
*/
|
|
||||||
private $translator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var ValidatorInterface
|
|
||||||
*/
|
|
||||||
private $validator;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PermissionsGroupController constructor.
|
* PermissionsGroupController constructor.
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
TranslatableStringHelper $translatableStringHelper,
|
private readonly TranslatableStringHelper $translatableStringHelper,
|
||||||
RoleProvider $roleProvider,
|
private readonly RoleProvider $roleProvider,
|
||||||
RoleHierarchy $roleHierarchy,
|
private readonly RoleHierarchyInterface $roleHierarchy,
|
||||||
TranslatorInterface $translator,
|
private readonly TranslatorInterface $translator,
|
||||||
ValidatorInterface $validator
|
private readonly ValidatorInterface $validator,
|
||||||
|
private readonly EntityManagerInterface $em,
|
||||||
|
private readonly PermissionsGroupRepository $permissionsGroupRepository,
|
||||||
|
private readonly RoleScopeRepository $roleScopeRepository,
|
||||||
) {
|
) {
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
|
||||||
$this->roleProvider = $roleProvider;
|
|
||||||
$this->roleHierarchy = $roleHierarchy;
|
|
||||||
$this->translator = $translator;
|
|
||||||
$this->validator = $validator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $id
|
|
||||||
*
|
|
||||||
* @throws type
|
|
||||||
*
|
|
||||||
* @return Respon
|
|
||||||
*/
|
*/
|
||||||
public function addLinkRoleScopeAction(Request $request, $id)
|
public function addLinkRoleScopeAction(Request $request, int $id): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$permissionsGroup = $this->permissionsGroupRepository->find($id);
|
||||||
|
|
||||||
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id);
|
|
||||||
|
|
||||||
if (!$permissionsGroup) {
|
if (!$permissionsGroup) {
|
||||||
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
||||||
@@ -106,7 +76,7 @@ class PermissionsGroupController extends AbstractController
|
|||||||
$violations = $this->validator->validate($permissionsGroup);
|
$violations = $this->validator->validate($permissionsGroup);
|
||||||
|
|
||||||
if ($violations->count() === 0) {
|
if ($violations->count() === 0) {
|
||||||
$em->flush();
|
$this->em->flush();
|
||||||
|
|
||||||
$this->addFlash(
|
$this->addFlash(
|
||||||
'notice',
|
'notice',
|
||||||
@@ -166,16 +136,15 @@ class PermissionsGroupController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Creates a new PermissionsGroup entity.
|
* Creates a new PermissionsGroup entity.
|
||||||
*/
|
*/
|
||||||
public function createAction(Request $request)
|
public function createAction(Request $request): Response
|
||||||
{
|
{
|
||||||
$permissionsGroup = new PermissionsGroup();
|
$permissionsGroup = new PermissionsGroup();
|
||||||
$form = $this->createCreateForm($permissionsGroup);
|
$form = $this->createCreateForm($permissionsGroup);
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isValid()) {
|
if ($form->isValid()) {
|
||||||
$em = $this->getDoctrine()->getManager();
|
$this->em->persist($permissionsGroup);
|
||||||
$em->persist($permissionsGroup);
|
$this->em->flush();
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
return $this->redirect($this->generateUrl(
|
return $this->redirect($this->generateUrl(
|
||||||
'admin_permissionsgroup_edit',
|
'admin_permissionsgroup_edit',
|
||||||
@@ -191,18 +160,11 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* remove an association between permissionsGroup and roleScope.
|
* remove an association between permissionsGroup and roleScope.
|
||||||
*
|
|
||||||
* @param int $pgid permissionsGroup id
|
|
||||||
* @param int $rsid roleScope id
|
|
||||||
*
|
|
||||||
* @return redirection to edit form
|
|
||||||
*/
|
*/
|
||||||
public function deleteLinkRoleScopeAction($pgid, $rsid)
|
public function deleteLinkRoleScopeAction(int $pgid, int $rsid): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$permissionsGroup = $this->permissionsGroupRepository->find($pgid);
|
||||||
|
$roleScope = $this->roleScopeRepository->find($rsid);
|
||||||
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($pgid);
|
|
||||||
$roleScope = $em->getRepository(\Chill\MainBundle\Entity\RoleScope::class)->find($rsid);
|
|
||||||
|
|
||||||
if (!$permissionsGroup) {
|
if (!$permissionsGroup) {
|
||||||
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
||||||
@@ -214,7 +176,7 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$permissionsGroup->removeRoleScope($roleScope);
|
$permissionsGroup->removeRoleScope($roleScope);
|
||||||
} catch (RuntimeException $ex) {
|
} catch (RuntimeException) {
|
||||||
$this->addFlash(
|
$this->addFlash(
|
||||||
'notice',
|
'notice',
|
||||||
$this->translator->trans("The role '%role%' and circle "
|
$this->translator->trans("The role '%role%' and circle "
|
||||||
@@ -231,7 +193,7 @@ class PermissionsGroupController extends AbstractController
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$em->flush();
|
$this->em->flush();
|
||||||
|
|
||||||
if ($roleScope->getScope() !== null) {
|
if ($roleScope->getScope() !== null) {
|
||||||
$this->addFlash(
|
$this->addFlash(
|
||||||
@@ -260,14 +222,10 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a form to edit an existing PermissionsGroup entity.
|
* Displays a form to edit an existing PermissionsGroup entity.
|
||||||
*
|
|
||||||
* @param mixed $id
|
|
||||||
*/
|
*/
|
||||||
public function editAction($id)
|
public function editAction(int $id): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$permissionsGroup = $this->permissionsGroupRepository->find($id);
|
||||||
|
|
||||||
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id);
|
|
||||||
|
|
||||||
if (!$permissionsGroup) {
|
if (!$permissionsGroup) {
|
||||||
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
||||||
@@ -311,11 +269,9 @@ class PermissionsGroupController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Lists all PermissionsGroup entities.
|
* Lists all PermissionsGroup entities.
|
||||||
*/
|
*/
|
||||||
public function indexAction()
|
public function indexAction(): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$entities = $this->permissionsGroupRepository->findAllOrderedAlphabetically();
|
||||||
|
|
||||||
$entities = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->findAll();
|
|
||||||
|
|
||||||
return $this->render('@ChillMain/PermissionsGroup/index.html.twig', [
|
return $this->render('@ChillMain/PermissionsGroup/index.html.twig', [
|
||||||
'entities' => $entities,
|
'entities' => $entities,
|
||||||
@@ -325,7 +281,7 @@ class PermissionsGroupController extends AbstractController
|
|||||||
/**
|
/**
|
||||||
* Displays a form to create a new PermissionsGroup entity.
|
* Displays a form to create a new PermissionsGroup entity.
|
||||||
*/
|
*/
|
||||||
public function newAction()
|
public function newAction(): Response
|
||||||
{
|
{
|
||||||
$permissionsGroup = new PermissionsGroup();
|
$permissionsGroup = new PermissionsGroup();
|
||||||
$form = $this->createCreateForm($permissionsGroup);
|
$form = $this->createCreateForm($permissionsGroup);
|
||||||
@@ -338,14 +294,10 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds and displays a PermissionsGroup entity.
|
* Finds and displays a PermissionsGroup entity.
|
||||||
*
|
|
||||||
* @param mixed $id
|
|
||||||
*/
|
*/
|
||||||
public function showAction($id)
|
public function showAction(int $id): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$permissionsGroup = $this->permissionsGroupRepository->find($id);
|
||||||
|
|
||||||
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id);
|
|
||||||
|
|
||||||
if (!$permissionsGroup) {
|
if (!$permissionsGroup) {
|
||||||
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
|
||||||
@@ -393,15 +345,10 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits an existing PermissionsGroup entity.
|
* Edits an existing PermissionsGroup entity.
|
||||||
*
|
|
||||||
* @param mixed $id
|
|
||||||
*/
|
*/
|
||||||
public function updateAction(Request $request, $id)
|
public function updateAction(Request $request, int $id): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$permissionsGroup = $this->permissionsGroupRepository
|
||||||
|
|
||||||
$permissionsGroup = $em
|
|
||||||
->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)
|
|
||||||
->find($id);
|
->find($id);
|
||||||
|
|
||||||
if (!$permissionsGroup) {
|
if (!$permissionsGroup) {
|
||||||
@@ -413,7 +360,7 @@ class PermissionsGroupController extends AbstractController
|
|||||||
$editForm->handleRequest($request);
|
$editForm->handleRequest($request);
|
||||||
|
|
||||||
if ($editForm->isValid()) {
|
if ($editForm->isValid()) {
|
||||||
$em->flush();
|
$this->em->flush();
|
||||||
|
|
||||||
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', ['id' => $id]));
|
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', ['id' => $id]));
|
||||||
}
|
}
|
||||||
@@ -452,18 +399,11 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* get a role scope by his parameters. The role scope is persisted if it
|
* get a role scope by his parameters. The role scope is persisted if it
|
||||||
* doesn't exists in database.
|
* doesn't exist in database.
|
||||||
*
|
|
||||||
* @param Scope $scope
|
|
||||||
* @param string $role
|
|
||||||
*
|
|
||||||
* @return RoleScope
|
|
||||||
*/
|
*/
|
||||||
protected function getPersistentRoleScopeBy($role, ?Scope $scope = null)
|
protected function getPersistentRoleScopeBy(string $role, ?Scope $scope = null): RoleScope
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
$roleScope = $this->roleScopeRepository
|
||||||
|
|
||||||
$roleScope = $em->getRepository(\Chill\MainBundle\Entity\RoleScope::class)
|
|
||||||
->findOneBy(['role' => $role, 'scope' => $scope]);
|
->findOneBy(['role' => $role, 'scope' => $scope]);
|
||||||
|
|
||||||
if (null === $roleScope) {
|
if (null === $roleScope) {
|
||||||
@@ -471,7 +411,7 @@ class PermissionsGroupController extends AbstractController
|
|||||||
->setRole($role)
|
->setRole($role)
|
||||||
->setScope($scope);
|
->setScope($scope);
|
||||||
|
|
||||||
$em->persist($roleScope);
|
$this->em->persist($roleScope);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $roleScope;
|
return $roleScope;
|
||||||
@@ -479,10 +419,8 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* creates a form to add a role scope to permissionsgroup.
|
* creates a form to add a role scope to permissionsgroup.
|
||||||
*
|
|
||||||
* @return \Symfony\Component\Form\Form The form
|
|
||||||
*/
|
*/
|
||||||
private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup)
|
private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup): FormInterface
|
||||||
{
|
{
|
||||||
return $this->createFormBuilder()
|
return $this->createFormBuilder()
|
||||||
->setAction($this->generateUrl(
|
->setAction($this->generateUrl(
|
||||||
@@ -499,10 +437,8 @@ class PermissionsGroupController extends AbstractController
|
|||||||
* Creates a form to create a PermissionsGroup entity.
|
* Creates a form to create a PermissionsGroup entity.
|
||||||
*
|
*
|
||||||
* @param PermissionsGroup $permissionsGroup The entity
|
* @param PermissionsGroup $permissionsGroup The entity
|
||||||
*
|
|
||||||
* @return \Symfony\Component\Form\Form The form
|
|
||||||
*/
|
*/
|
||||||
private function createCreateForm(PermissionsGroup $permissionsGroup)
|
private function createCreateForm(PermissionsGroup $permissionsGroup): FormInterface
|
||||||
{
|
{
|
||||||
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
|
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
|
||||||
'action' => $this->generateUrl('admin_permissionsgroup_create'),
|
'action' => $this->generateUrl('admin_permissionsgroup_create'),
|
||||||
@@ -518,13 +454,11 @@ class PermissionsGroupController extends AbstractController
|
|||||||
* Creates a form to delete a link to roleScope.
|
* Creates a form to delete a link to roleScope.
|
||||||
*
|
*
|
||||||
* @param mixed $permissionsGroup The entity id
|
* @param mixed $permissionsGroup The entity id
|
||||||
*
|
|
||||||
* @return \Symfony\Component\Form\Form The form
|
|
||||||
*/
|
*/
|
||||||
private function createDeleteRoleScopeForm(
|
private function createDeleteRoleScopeForm(
|
||||||
PermissionsGroup $permissionsGroup,
|
PermissionsGroup $permissionsGroup,
|
||||||
RoleScope $roleScope
|
RoleScope $roleScope
|
||||||
) {
|
): FormInterface {
|
||||||
return $this->createFormBuilder()
|
return $this->createFormBuilder()
|
||||||
->setAction($this->generateUrl(
|
->setAction($this->generateUrl(
|
||||||
'admin_permissionsgroup_delete_role_scope',
|
'admin_permissionsgroup_delete_role_scope',
|
||||||
@@ -537,12 +471,8 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a form to edit a PermissionsGroup entity.
|
* Creates a form to edit a PermissionsGroup entity.
|
||||||
*
|
|
||||||
* @param PermissionsGroup $permissionsGroup The entity
|
|
||||||
*
|
|
||||||
* @return \Symfony\Component\Form\Form The form
|
|
||||||
*/
|
*/
|
||||||
private function createEditForm(PermissionsGroup $permissionsGroup)
|
private function createEditForm(PermissionsGroup $permissionsGroup): FormInterface
|
||||||
{
|
{
|
||||||
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
|
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
|
||||||
'action' => $this->generateUrl('admin_permissionsgroup_update', ['id' => $permissionsGroup->getId()]),
|
'action' => $this->generateUrl('admin_permissionsgroup_update', ['id' => $permissionsGroup->getId()]),
|
||||||
@@ -556,10 +486,8 @@ class PermissionsGroupController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* expand roleScopes to be easily shown in template.
|
* expand roleScopes to be easily shown in template.
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
private function getExpandedRoles(array $roleScopes)
|
private function getExpandedRoles(array $roleScopes): array
|
||||||
{
|
{
|
||||||
$expandedRoles = [];
|
$expandedRoles = [];
|
||||||
|
|
||||||
@@ -567,10 +495,10 @@ class PermissionsGroupController extends AbstractController
|
|||||||
if (!array_key_exists($roleScope->getRole(), $expandedRoles)) {
|
if (!array_key_exists($roleScope->getRole(), $expandedRoles)) {
|
||||||
$expandedRoles[$roleScope->getRole()] =
|
$expandedRoles[$roleScope->getRole()] =
|
||||||
array_map(
|
array_map(
|
||||||
static fn (Role $role) => $role->getRole(),
|
static fn ($role) => $role,
|
||||||
$this->roleHierarchy
|
$this->roleHierarchy
|
||||||
->getReachableRoles(
|
->getReachableRoleNames(
|
||||||
[new Role($roleScope->getRole())]
|
[$roleScope->getRole()]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
144
src/Bundle/ChillMainBundle/Controller/UserExportController.php
Normal file
144
src/Bundle/ChillMainBundle/Controller/UserExportController.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||||
|
use League\Csv\Writer;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
final readonly class UserExportController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private UserRepositoryInterface $userRepository,
|
||||||
|
private Security $security,
|
||||||
|
private TranslatorInterface $translator,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \League\Csv\CannotInsertRecord
|
||||||
|
* @throws \League\Csv\Exception
|
||||||
|
* @throws \League\Csv\UnavailableStream
|
||||||
|
*
|
||||||
|
* @Route("/{_locale}/admin/main/users/export/list.{_format}", requirements={"_format": "csv"}, name="chill_main_users_export_list")
|
||||||
|
*/
|
||||||
|
public function userList(Request $request, string $_format = 'csv'): StreamedResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||||
|
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
|
||||||
|
}
|
||||||
|
|
||||||
|
$users = $this->userRepository->findAllAsArray($request->getLocale());
|
||||||
|
|
||||||
|
$csv = Writer::createFromPath('php://temp', 'r+');
|
||||||
|
$csv->insertOne(
|
||||||
|
array_map(
|
||||||
|
fn (string $e) => $this->translator->trans('admin.users.export.' . $e),
|
||||||
|
[
|
||||||
|
'id',
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'enabled',
|
||||||
|
'civility_id',
|
||||||
|
'civility_abbreviation',
|
||||||
|
'civility_name',
|
||||||
|
'label',
|
||||||
|
'mainCenter_id' ,
|
||||||
|
'mainCenter_name',
|
||||||
|
'mainScope_id',
|
||||||
|
'mainScope_name',
|
||||||
|
'userJob_id',
|
||||||
|
'userJob_name',
|
||||||
|
'currentLocation_id',
|
||||||
|
'currentLocation_name',
|
||||||
|
'mainLocation_id',
|
||||||
|
'mainLocation_name',
|
||||||
|
'absenceStart'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$csv->addFormatter(fn (array $row) => null !== ($row['absenceStart'] ?? null) ? array_merge($row, ['absenceStart' => $row['absenceStart']->format('Y-m-d')]) : $row);
|
||||||
|
$csv->insertAll($users);
|
||||||
|
|
||||||
|
return new StreamedResponse(
|
||||||
|
function () use ($csv) {
|
||||||
|
foreach ($csv->chunk(1024) as $chunk) {
|
||||||
|
echo $chunk;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Response::HTTP_OK,
|
||||||
|
[
|
||||||
|
'Content-Encoding' => 'none',
|
||||||
|
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||||
|
'Content-Disposition' => 'attachment; users.csv',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return StreamedResponse
|
||||||
|
* @throws \League\Csv\CannotInsertRecord
|
||||||
|
* @throws \League\Csv\Exception
|
||||||
|
* @throws \League\Csv\UnavailableStream
|
||||||
|
*
|
||||||
|
* @Route("/{_locale}/admin/main/users/export/permissions.{_format}", requirements={"_format": "csv"}, name="chill_main_users_export_permissions")
|
||||||
|
*/
|
||||||
|
public function userPermissionsList(string $_format = 'csv'): StreamedResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||||
|
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userPermissions = $this->userRepository->findAllUserACLAsArray();
|
||||||
|
|
||||||
|
$csv = Writer::createFromPath('php://temp', 'r+');
|
||||||
|
$csv->insertOne(
|
||||||
|
array_map(
|
||||||
|
fn (string $e) => $this->translator->trans('admin.users.export.' . $e),
|
||||||
|
[
|
||||||
|
'id',
|
||||||
|
'username',
|
||||||
|
'email',
|
||||||
|
'label',
|
||||||
|
'enabled',
|
||||||
|
'center_id',
|
||||||
|
'center_name',
|
||||||
|
'permissionsGroup_id',
|
||||||
|
'permissionsGroup_name',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$csv->insertAll($userPermissions);
|
||||||
|
|
||||||
|
return new StreamedResponse(
|
||||||
|
function () use ($csv) {
|
||||||
|
foreach ($csv->chunk(1024) as $chunk) {
|
||||||
|
echo $chunk;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Response::HTTP_OK,
|
||||||
|
[
|
||||||
|
'Content-Encoding' => 'none',
|
||||||
|
'Content-Type' => 'text/csv; charset=UTF-8',
|
||||||
|
'Content-Disposition' => 'attachment; users.csv',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -359,9 +359,9 @@ class WorkflowController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context)
|
// TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context)
|
||||||
$entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData();
|
$entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData() ?? [];
|
||||||
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
|
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData() ?? [];
|
||||||
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData();
|
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData() ?? [];
|
||||||
|
|
||||||
$workflow->apply($entityWorkflow, $transition);
|
$workflow->apply($entityWorkflow, $transition);
|
||||||
|
|
||||||
|
@@ -41,12 +41,12 @@ class TrackCreateUpdateSubscriber implements EventSubscriber
|
|||||||
{
|
{
|
||||||
$object = $args->getObject();
|
$object = $args->getObject();
|
||||||
|
|
||||||
if (
|
if ($object instanceof TrackCreationInterface) {
|
||||||
$object instanceof TrackCreationInterface
|
|
||||||
&& $this->security->getUser() instanceof User
|
|
||||||
) {
|
|
||||||
$object->setCreatedBy($this->security->getUser());
|
|
||||||
$object->setCreatedAt(new DateTimeImmutable('now'));
|
$object->setCreatedAt(new DateTimeImmutable('now'));
|
||||||
|
|
||||||
|
if ($this->security->getUser() instanceof User) {
|
||||||
|
$object->setCreatedBy($this->security->getUser());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->onUpdate($object);
|
$this->onUpdate($object);
|
||||||
@@ -61,12 +61,12 @@ class TrackCreateUpdateSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
protected function onUpdate(object $object): void
|
protected function onUpdate(object $object): void
|
||||||
{
|
{
|
||||||
if (
|
if ($object instanceof TrackUpdateInterface) {
|
||||||
$object instanceof TrackUpdateInterface
|
|
||||||
&& $this->security->getUser() instanceof User
|
|
||||||
) {
|
|
||||||
$object->setUpdatedBy($this->security->getUser());
|
|
||||||
$object->setUpdatedAt(new DateTimeImmutable('now'));
|
$object->setUpdatedAt(new DateTimeImmutable('now'));
|
||||||
|
|
||||||
|
if ($this->security->getUser() instanceof User) {
|
||||||
|
$object->setUpdatedBy($this->security->getUser());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -506,11 +506,11 @@ class User implements UserInterface
|
|||||||
*
|
*
|
||||||
* @return User
|
* @return User
|
||||||
*/
|
*/
|
||||||
public function setUsername($name)
|
public function setUsername(?string $name)
|
||||||
{
|
{
|
||||||
$this->username = $name;
|
$this->username = (string) $name;
|
||||||
|
|
||||||
if (empty($this->getLabel())) {
|
if ("" === trim($this->getLabel())) {
|
||||||
$this->setLabel($name);
|
$this->setLabel($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -43,6 +43,10 @@ class EntityToJsonTransformer implements DataTransformerInterface
|
|||||||
|
|
||||||
public function reverseTransform($value)
|
public function reverseTransform($value)
|
||||||
{
|
{
|
||||||
|
if ("" === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
$denormalized = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
|
$denormalized = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
if ($this->multiple) {
|
if ($this->multiple) {
|
||||||
@@ -56,10 +60,6 @@ class EntityToJsonTransformer implements DataTransformerInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('' === $value) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->denormalizeOne($denormalized);
|
return $this->denormalizeOne($denormalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,6 +38,19 @@ final class PermissionsGroupRepository implements ObjectRepository
|
|||||||
return $this->repository->findAll();
|
return $this->repository->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<PermissionsGroup>
|
||||||
|
*/
|
||||||
|
public function findAllOrderedAlphabetically(): array
|
||||||
|
{
|
||||||
|
$qb = $this->repository->createQueryBuilder('pg');
|
||||||
|
|
||||||
|
return $qb->select(['pg', 'pg.name AS HIDDEN sort_name'])
|
||||||
|
->orderBy('sort_name')
|
||||||
|
->getQuery()
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed|null $limit
|
* @param mixed|null $limit
|
||||||
* @param mixed|null $offset
|
* @param mixed|null $offset
|
||||||
|
@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository;
|
|||||||
|
|
||||||
use Chill\MainBundle\Entity\GroupCenter;
|
use Chill\MainBundle\Entity\GroupCenter;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Doctrine\ORM\AbstractQuery;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\NoResultException;
|
use Doctrine\ORM\NoResultException;
|
||||||
@@ -76,6 +77,81 @@ final class UserRepository implements UserRepositoryInterface
|
|||||||
return $this->repository->findAll();
|
return $this->repository->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $lang
|
||||||
|
*/
|
||||||
|
public function findAllAsArray(string $lang): iterable
|
||||||
|
{
|
||||||
|
$dql = sprintf(<<<'DQL'
|
||||||
|
SELECT
|
||||||
|
u.id AS id,
|
||||||
|
u.username AS username,
|
||||||
|
u.email,
|
||||||
|
u.enabled,
|
||||||
|
IDENTITY(u.civility) AS civility_id,
|
||||||
|
JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation,
|
||||||
|
JSON_EXTRACT(civility.name, :lang) AS civility_name,
|
||||||
|
u.label,
|
||||||
|
mainCenter.id AS mainCenter_id,
|
||||||
|
mainCenter.name AS mainCenter_name,
|
||||||
|
IDENTITY(u.mainScope) AS mainScope_id,
|
||||||
|
JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name,
|
||||||
|
IDENTITY(u.userJob) AS userJob_id,
|
||||||
|
JSON_EXTRACT(userJob.label, :lang) AS userJob_name,
|
||||||
|
currentLocation.id AS currentLocation_id,
|
||||||
|
currentLocation.name AS currentLocation_name,
|
||||||
|
mainLocation.id AS mainLocation_id,
|
||||||
|
mainLocation.name AS mainLocation_name,
|
||||||
|
u.absenceStart
|
||||||
|
FROM Chill\MainBundle\Entity\User u
|
||||||
|
LEFT JOIN u.civility civility
|
||||||
|
LEFT JOIN u.currentLocation currentLocation
|
||||||
|
LEFT JOIN u.mainLocation mainLocation
|
||||||
|
LEFT JOIN u.mainCenter mainCenter
|
||||||
|
LEFT JOIN u.mainScope mainScope
|
||||||
|
LEFT JOIN u.userJob userJob
|
||||||
|
ORDER BY u.label
|
||||||
|
DQL);
|
||||||
|
|
||||||
|
$query = $this->entityManager->createQuery($dql)
|
||||||
|
->setHydrationMode(AbstractQuery::HYDRATE_ARRAY)
|
||||||
|
->setParameter('lang', $lang)
|
||||||
|
;
|
||||||
|
|
||||||
|
foreach ($query->toIterable() as $u) {
|
||||||
|
yield $u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAllUserACLAsArray(): iterable
|
||||||
|
{
|
||||||
|
$sql = <<<'SQL'
|
||||||
|
SELECT
|
||||||
|
u.id,
|
||||||
|
u.username,
|
||||||
|
u.email,
|
||||||
|
u.label,
|
||||||
|
u.enabled,
|
||||||
|
c.id AS center_id,
|
||||||
|
c.name AS center_name,
|
||||||
|
pg.id AS permissionsGroup_id,
|
||||||
|
pg.name AS permissionsGroup_name
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN user_groupcenter ON u.id = user_groupcenter.user_id
|
||||||
|
LEFT JOIN group_centers ON user_groupcenter.groupcenter_id = group_centers.id
|
||||||
|
LEFT JOIN centers c on group_centers.center_id = c.id
|
||||||
|
LEFT JOIN permission_groups pg on group_centers.permissionsgroup_id = pg.id
|
||||||
|
ORDER BY u.username, c.name, pg.name
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$query = $this->entityManager->getConnection()->executeQuery($sql);
|
||||||
|
|
||||||
|
foreach ($query->iterateAssociative() as $u) {
|
||||||
|
yield $u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed|null $limit
|
* @param mixed|null $limit
|
||||||
* @param mixed|null $offset
|
* @param mixed|null $offset
|
||||||
|
@@ -14,6 +14,9 @@ namespace Chill\MainBundle\Repository;
|
|||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template ObjectRepository<User>
|
||||||
|
*/
|
||||||
interface UserRepositoryInterface extends ObjectRepository
|
interface UserRepositoryInterface extends ObjectRepository
|
||||||
{
|
{
|
||||||
public function countBy(array $criteria): int;
|
public function countBy(array $criteria): int;
|
||||||
@@ -24,20 +27,25 @@ interface UserRepositoryInterface extends ObjectRepository
|
|||||||
|
|
||||||
public function countByUsernameOrEmail(string $pattern): int;
|
public function countByUsernameOrEmail(string $pattern): int;
|
||||||
|
|
||||||
public function find($id, $lockMode = null, $lockVersion = null): ?User;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return User[]
|
* Find a list of all users.
|
||||||
*/
|
|
||||||
public function findAll(): array;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param mixed|null $limit
|
|
||||||
* @param mixed|null $offset
|
|
||||||
*
|
*
|
||||||
* @return User[]
|
* The main purpose for this method is to provide a lightweight list of all users in the database.
|
||||||
|
*
|
||||||
|
* @param string $lang The lang to display all the translatable string (no fallback if not present)
|
||||||
|
* @return iterable<array{id: int, username: string, email: string, enabled: bool, civility_id: int, civility_abbreviation: string, civility_name: string, label: string, mainCenter_id: int, mainCenter_name: string, mainScope_id: int, mainScope_name: string, userJob_id: int, userJob_name: string, currentLocation_id: int, currentLocation_name: string, mainLocation_id: int, mainLocation_name: string, absenceStart: \DateTimeImmutable}>
|
||||||
*/
|
*/
|
||||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
|
public function findAllAsArray(string $lang): iterable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a list of permissions associated to each users.
|
||||||
|
*
|
||||||
|
* The main purpose for this method is to provide a lightweight list of all permissions group and center
|
||||||
|
* associated to each user.
|
||||||
|
*
|
||||||
|
* @return iterable<array{id: int, username: string, email: string, enabled: bool, center_id: int, center_name: string, permissionsGroup_id: int, permissionsGroup_name: string}>
|
||||||
|
*/
|
||||||
|
public function findAllUserACLAsArray(): iterable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array|User[]
|
* @return array|User[]
|
||||||
@@ -53,8 +61,6 @@ interface UserRepositoryInterface extends ObjectRepository
|
|||||||
|
|
||||||
public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array;
|
public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array;
|
||||||
|
|
||||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?User;
|
|
||||||
|
|
||||||
public function findOneByUsernameOrEmail(string $pattern): ?User;
|
public function findOneByUsernameOrEmail(string $pattern): ?User;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,6 +74,4 @@ interface UserRepositoryInterface extends ObjectRepository
|
|||||||
* @param mixed $flag
|
* @param mixed $flag
|
||||||
*/
|
*/
|
||||||
public function findUsersHavingFlags($flag, array $amongstUsers = []): array;
|
public function findUsersHavingFlags($flag, array $amongstUsers = []): array;
|
||||||
|
|
||||||
public function getClassName(): string;
|
|
||||||
}
|
}
|
||||||
|
@@ -39,8 +39,13 @@
|
|||||||
{{ 'This will eventually restrict your possibilities in filtering the data.'|trans }}</p>
|
{{ 'This will eventually restrict your possibilities in filtering the data.'|trans }}</p>
|
||||||
|
|
||||||
<h3 class="m-3">{{ 'Center'|trans }}</h3>
|
<h3 class="m-3">{{ 'Center'|trans }}</h3>
|
||||||
|
|
||||||
{{ form_widget(form.centers.center) }}
|
{{ form_widget(form.centers.center) }}
|
||||||
|
|
||||||
|
<div class="mb-3 mt-3">
|
||||||
|
<input id="toggle-check-all" class="btn btn-misc" type= "button" onclick='uncheckAll(this)' value="{{ 'uncheck all centers'|trans|e('html_attr') }}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if form.centers.regroupment is defined %}
|
{% if form.centers.regroupment is defined %}
|
||||||
<h3 class="m-3">{{ 'Pick aggregated centers'|trans }}</h3>
|
<h3 class="m-3">{{ 'Pick aggregated centers'|trans }}</h3>
|
||||||
{{ form_widget(form.centers.regroupment) }}
|
{{ form_widget(form.centers.regroupment) }}
|
||||||
@@ -53,3 +58,15 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const uncheckAll = () => {
|
||||||
|
const allCenters = document.getElementsByName('centers[center][]');
|
||||||
|
|
||||||
|
allCenters.forEach(checkbox => checkbox.checked = false)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock js %}
|
||||||
|
@@ -98,6 +98,18 @@
|
|||||||
<li class='cancel'>
|
<li class='cancel'>
|
||||||
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fa fa-download"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="{{ path('chill_main_users_export_list') }}">{{ 'admin.users.export_list_csv'|trans }}</a></li>
|
||||||
|
<li><a class="dropdown-item" href="{{ path('chill_main_users_export_permissions') }}">{{ 'admin.users.export_permissions_csv'|trans }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_crud_admin_user_new') }}" class="btn btn-create">{{ 'Create'|trans }}</a>
|
<a href="{{ path('chill_crud_admin_user_new') }}" class="btn btn-create">{{ 'Create'|trans }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Service\EntityInfo;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Connection;
|
||||||
|
|
||||||
|
class ViewEntityInfoManager
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
/**
|
||||||
|
* @var ViewEntityInfoProviderInterface[]
|
||||||
|
*/
|
||||||
|
private iterable $vienEntityInfoProviders,
|
||||||
|
private Connection $connection,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function synchronizeOnDB(): void
|
||||||
|
{
|
||||||
|
$this->connection->transactional(function (Connection $conn): void {
|
||||||
|
foreach ($this->vienEntityInfoProviders as $viewProvider) {
|
||||||
|
foreach ($this->createOrReplaceViewSQL($viewProvider, $viewProvider->getViewName()) as $sql) {
|
||||||
|
$conn->executeQuery($sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string>
|
||||||
|
*/
|
||||||
|
private function createOrReplaceViewSQL(ViewEntityInfoProviderInterface $viewProvider, string $viewName): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"DROP VIEW IF EXISTS {$viewName}",
|
||||||
|
sprintf("CREATE VIEW {$viewName} AS %s", $viewProvider->getViewQuery())
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Service\EntityInfo;
|
||||||
|
|
||||||
|
interface ViewEntityInfoProviderInterface
|
||||||
|
{
|
||||||
|
public function getViewQuery(): string;
|
||||||
|
|
||||||
|
public function getViewName(): string;
|
||||||
|
}
|
@@ -1,6 +1,9 @@
|
|||||||
parameters:
|
parameters:
|
||||||
# cl_chill_main.example.class: Chill\MainBundle\Example
|
# cl_chill_main.example.class: Chill\MainBundle\Example
|
||||||
|
|
||||||
|
imports:
|
||||||
|
- ./services/clock.yaml
|
||||||
|
|
||||||
services:
|
services:
|
||||||
_defaults:
|
_defaults:
|
||||||
autowire: true
|
autowire: true
|
||||||
@@ -118,3 +121,7 @@ services:
|
|||||||
lazy: true
|
lazy: true
|
||||||
arguments:
|
arguments:
|
||||||
$jobs: !tagged_iterator chill_main.cron_job
|
$jobs: !tagged_iterator chill_main.cron_job
|
||||||
|
|
||||||
|
Chill\MainBundle\Service\EntityInfo\ViewEntityInfoManager:
|
||||||
|
arguments:
|
||||||
|
$vienEntityInfoProviders: !tagged_iterator chill_main.entity_info_provider
|
||||||
|
4
src/Bundle/ChillMainBundle/config/services/clock.yaml
Normal file
4
src/Bundle/ChillMainBundle/config/services/clock.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# temporary, waiting for symfony 6.0 to load clock
|
||||||
|
services:
|
||||||
|
Symfony\Component\Clock\NativeClock: ~
|
||||||
|
Symfony\Component\Clock\ClockInterface: '@Symfony\Component\Clock\NativeClock'
|
@@ -67,3 +67,7 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
tags:
|
tags:
|
||||||
- {name: console.command }
|
- {name: console.command }
|
||||||
|
|
||||||
|
Chill\MainBundle\Command\SynchronizeEntityInfoViewsCommand:
|
||||||
|
tags:
|
||||||
|
- {name: console.command}
|
||||||
|
@@ -37,3 +37,6 @@ services:
|
|||||||
Chill\MainBundle\Controller\RegroupmentController:
|
Chill\MainBundle\Controller\RegroupmentController:
|
||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\MainBundle\Controller\UserExportController:
|
||||||
|
tags: ['controller.service_arguments']
|
||||||
|
@@ -285,6 +285,8 @@ The export will contains only data from the picked centers.: L'export ne contien
|
|||||||
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis.
|
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis.
|
||||||
Go to export options: Vers la préparation de l'export
|
Go to export options: Vers la préparation de l'export
|
||||||
Pick aggregated centers: Regroupement de centres
|
Pick aggregated centers: Regroupement de centres
|
||||||
|
uncheck all centers: Désélectionner tous les centres
|
||||||
|
check all centers: Sélectionner tous les centres
|
||||||
# export creation step 'export' : choose aggregators, filtering and formatter
|
# export creation step 'export' : choose aggregators, filtering and formatter
|
||||||
Formatter: Mise en forme
|
Formatter: Mise en forme
|
||||||
Choose the formatter: Choisissez le format d'export voulu.
|
Choose the formatter: Choisissez le format d'export voulu.
|
||||||
@@ -564,6 +566,7 @@ export:
|
|||||||
_as_string: Adresse formattée
|
_as_string: Adresse formattée
|
||||||
confidential: Adresse confidentielle ?
|
confidential: Adresse confidentielle ?
|
||||||
isNoAddress: Adresse incomplète ?
|
isNoAddress: Adresse incomplète ?
|
||||||
|
steps: Escaliers
|
||||||
_lat: Latitude
|
_lat: Latitude
|
||||||
_lon: Longitude
|
_lon: Longitude
|
||||||
|
|
||||||
@@ -609,3 +612,32 @@ absence:
|
|||||||
You are listed as absent, as of: Votre absence est indiquée à partir du
|
You are listed as absent, as of: Votre absence est indiquée à partir du
|
||||||
No absence listed: Aucune absence indiquée.
|
No absence listed: Aucune absence indiquée.
|
||||||
Is absent: Absent?
|
Is absent: Absent?
|
||||||
|
|
||||||
|
admin:
|
||||||
|
users:
|
||||||
|
export_list_csv: Liste des utilisateurs (format CSV)
|
||||||
|
export_permissions_csv: Association utilisateurs - groupes de permissions - centre (format CSV)
|
||||||
|
export:
|
||||||
|
id: Identifiant
|
||||||
|
username: Nom d'utilisateur
|
||||||
|
email: Courriel
|
||||||
|
enabled: Activé
|
||||||
|
civility_id: Identifiant civilité
|
||||||
|
civility_abbreviation: Abbréviation civilité
|
||||||
|
civility_name: Civilité
|
||||||
|
label: Label
|
||||||
|
mainCenter_id: Identifiant centre principal
|
||||||
|
mainCenter_name: Centre principal
|
||||||
|
mainScope_id: Identifiant service principal
|
||||||
|
mainScope_name: Service principal
|
||||||
|
userJob_id: Identifiant métier
|
||||||
|
userJob_name: Métier
|
||||||
|
currentLocation_id: Identifiant localisation actuelle
|
||||||
|
currentLocation_name: Localisation actuelle
|
||||||
|
mainLocation_id: Identifiant localisation principale
|
||||||
|
mainLocation_name: Localisation principale
|
||||||
|
absenceStart: Absent à partir du
|
||||||
|
center_id: Identifiant du centre
|
||||||
|
center_name: Centre
|
||||||
|
permissionsGroup_id: Identifiant du groupe de permissions
|
||||||
|
permissionsGroup_name: Groupe de permissions
|
||||||
|
@@ -51,7 +51,10 @@ class UserRefEventSubscriber implements EventSubscriberInterface
|
|||||||
|
|
||||||
public function onStateEntered(EnteredEvent $enteredEvent): void
|
public function onStateEntered(EnteredEvent $enteredEvent): void
|
||||||
{
|
{
|
||||||
if ($enteredEvent->getMarking()->has(AccompanyingPeriod::STEP_CONFIRMED)) {
|
if (
|
||||||
|
$enteredEvent->getMarking()->has(AccompanyingPeriod::STEP_CONFIRMED)
|
||||||
|
and $enteredEvent->getTransition()->getName() === 'confirm'
|
||||||
|
) {
|
||||||
$this->onPeriodConfirmed($enteredEvent->getSubject());
|
$this->onPeriodConfirmed($enteredEvent->getSubject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\AccompanyingPeriod\Lifecycle;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Cron\CronJobInterface;
|
||||||
|
use Chill\MainBundle\Entity\CronJobExecution;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
|
||||||
|
readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ClockInterface $clock,
|
||||||
|
private AccompanyingPeriodStepChangeRequestor $requestor,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canRun(?CronJobExecution $cronJobExecution): bool
|
||||||
|
{
|
||||||
|
$now = $this->clock->now();
|
||||||
|
|
||||||
|
if (null !== $cronJobExecution && $now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array((int) $now->format('H'), [1, 2, 3, 4, 5, 6], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getKey(): string
|
||||||
|
{
|
||||||
|
return 'accompanying-period-step-change';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
($this->requestor)();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\AccompanyingPeriod\Lifecycle;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
|
||||||
|
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||||
|
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||||
|
|
||||||
|
#[AsMessageHandler]
|
||||||
|
class AccompanyingPeriodStepChangeMessageHandler implements MessageHandlerInterface
|
||||||
|
{
|
||||||
|
private const LOG_PREFIX = '[accompanying period step change message handler] ';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private AccompanyingPeriodRepository $accompanyingPeriodRepository,
|
||||||
|
private AccompanyingPeriodStepChanger $changer,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(AccompanyingPeriodStepChangeRequestMessage $message): void
|
||||||
|
{
|
||||||
|
if (null === $period = $this->accompanyingPeriodRepository->find($message->getPeriodId())) {
|
||||||
|
throw new \RuntimeException(self::LOG_PREFIX . 'Could not find period with this id: '. $message->getPeriodId());
|
||||||
|
}
|
||||||
|
|
||||||
|
($this->changer)($period, $message->getTransition());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\AccompanyingPeriod\Lifecycle;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message which will request a change in the step of accompanying period
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodStepChangeRequestMessage
|
||||||
|
{
|
||||||
|
private int $periodId;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AccompanyingPeriod|int $period,
|
||||||
|
private string $transition,
|
||||||
|
) {
|
||||||
|
if (is_int($period)) {
|
||||||
|
$this->periodId = $period;
|
||||||
|
} else {
|
||||||
|
if (null !== $id = $period->getId()) {
|
||||||
|
$this->periodId = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \LogicException("This AccompanyingPeriod does not have and id yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPeriodId(): int
|
||||||
|
{
|
||||||
|
return $this->periodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransition(): string
|
||||||
|
{
|
||||||
|
return $this->transition;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\AccompanyingPeriod\Lifecycle;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodInfoRepositoryInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
use Symfony\Component\Messenger\MessageBusInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather all the accompanying period which needs a change in step
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodStepChangeRequestor
|
||||||
|
{
|
||||||
|
private \DateInterval $intervalForShortInactive;
|
||||||
|
|
||||||
|
private \DateInterval $intervalForLongInactive;
|
||||||
|
|
||||||
|
private bool $isMarkInactive;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private AccompanyingPeriodInfoRepositoryInterface $accompanyingPeriodInfoRepository,
|
||||||
|
private LoggerInterface $logger,
|
||||||
|
private MessageBusInterface $messageBus,
|
||||||
|
ParameterBagInterface $parameterBag,
|
||||||
|
) {
|
||||||
|
$config = $parameterBag->get('chill_person')['accompanying_period_lifecycle_delays'];
|
||||||
|
$this->isMarkInactive = $config['mark_inactive'];
|
||||||
|
$this->intervalForShortInactive = new \DateInterval($config['mark_inactive_short_after']);
|
||||||
|
$this->intervalForLongInactive = new \DateInterval($config['mark_inactive_long_after']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(): void
|
||||||
|
{
|
||||||
|
if (!$this->isMarkInactive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the oldest ones first
|
||||||
|
foreach (
|
||||||
|
$olders = $this->accompanyingPeriodInfoRepository->findAccompanyingPeriodIdInactiveAfter(
|
||||||
|
$this->intervalForLongInactive,
|
||||||
|
[AccompanyingPeriod::STEP_CONFIRMED, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT]
|
||||||
|
) as $accompanyingPeriodId
|
||||||
|
) {
|
||||||
|
$this->logger->debug('request mark period as inactive_short', ['period' => $accompanyingPeriodId]);
|
||||||
|
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_inactive_long'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// the newest
|
||||||
|
foreach (
|
||||||
|
$this->accompanyingPeriodInfoRepository->findAccompanyingPeriodIdInactiveAfter(
|
||||||
|
$this->intervalForShortInactive,
|
||||||
|
[AccompanyingPeriod::STEP_CONFIRMED]
|
||||||
|
) as $accompanyingPeriodId
|
||||||
|
) {
|
||||||
|
if (in_array($accompanyingPeriodId, $olders, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->debug('request mark period as inactive_long', ['period' => $accompanyingPeriodId]);
|
||||||
|
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_inactive_short'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// a new event has been created => remove inactive long, or short
|
||||||
|
foreach (
|
||||||
|
$this->accompanyingPeriodInfoRepository->findAccompanyingPeriodIdActiveSince(
|
||||||
|
$this->intervalForShortInactive,
|
||||||
|
[AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG]
|
||||||
|
) as $accompanyingPeriodId
|
||||||
|
) {
|
||||||
|
$this->logger->debug('request mark period as active', ['period' => $accompanyingPeriodId]);
|
||||||
|
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_active'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\AccompanyingPeriod\Lifecycle;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the step of an accompanying period
|
||||||
|
*
|
||||||
|
* This should be invoked through scripts (not in the in context of an http request, or an
|
||||||
|
* action from a user).
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodStepChanger
|
||||||
|
{
|
||||||
|
private const LOG_PREFIX = '[AccompanyingPeriodStepChanger] ';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private EntityManagerInterface $entityManager,
|
||||||
|
private LoggerInterface $logger,
|
||||||
|
private Registry $workflowRegistry,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(AccompanyingPeriod $period, string $transition, ?string $workflowName = null): void
|
||||||
|
{
|
||||||
|
$workflow = $this->workflowRegistry->get($period, $workflowName);
|
||||||
|
|
||||||
|
if (!$workflow->can($period, $transition)) {
|
||||||
|
$this->logger->info(self::LOG_PREFIX . 'not able to apply the transition on period', [
|
||||||
|
'period_id' => $period->getId(),
|
||||||
|
'transition' => $transition
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$workflow->apply($period, $transition);
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->logger->info(self::LOG_PREFIX . 'could apply a transition', [
|
||||||
|
'period_id' => $period->getId(),
|
||||||
|
'transition' => $transition
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@@ -13,7 +13,11 @@ namespace Chill\PersonBundle\Actions\Remove;
|
|||||||
|
|
||||||
use Chill\PersonBundle\Actions\ActionEvent;
|
use Chill\PersonBundle\Actions\ActionEvent;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||||
|
use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
||||||
|
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\PersonBundle\Entity\Relationships\Relationship;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||||
@@ -42,7 +46,7 @@ class PersonMove
|
|||||||
protected $eventDispatcher;
|
protected $eventDispatcher;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
EntityManagerInterface $em,
|
EntityManagerInterface $em,
|
||||||
EventDispatcherInterface $eventDispatcher
|
EventDispatcherInterface $eventDispatcher
|
||||||
) {
|
) {
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
@@ -84,8 +88,11 @@ class PersonMove
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($metadata->getAssociationMappings() as $field => $mapping) {
|
foreach ($metadata->getAssociationMappings() as $field => $mapping) {
|
||||||
if (Person::class === $mapping['targetEntity']) {
|
if (in_array($mapping['sourceEntity'], $this->getIgnoredEntities(), true)) {
|
||||||
if (in_array($metadata->getName(), $toDelete, true)) {
|
continue;
|
||||||
|
}
|
||||||
|
if (Person::class === $mapping['targetEntity'] and true === $mapping['isOwningSide']) {
|
||||||
|
if (in_array($mapping['sourceEntity'], $toDelete, true)) {
|
||||||
$sql = $this->createDeleteSQL($metadata, $from, $field);
|
$sql = $this->createDeleteSQL($metadata, $from, $field);
|
||||||
$event = new ActionEvent(
|
$event = new ActionEvent(
|
||||||
$from->getId(),
|
$from->getId(),
|
||||||
@@ -120,7 +127,7 @@ class PersonMove
|
|||||||
return $sqls;
|
return $sqls;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
|
private function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
|
||||||
{
|
{
|
||||||
$mapping = $metadata->getAssociationMapping($field);
|
$mapping = $metadata->getAssociationMapping($field);
|
||||||
|
|
||||||
@@ -137,26 +144,41 @@ class PersonMove
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
|
private function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
|
||||||
{
|
{
|
||||||
$mapping = $metadata->getAssociationMapping($field);
|
$mapping = $metadata->getAssociationMapping($field);
|
||||||
|
|
||||||
// Set part of the query, aka <here> in "UPDATE table SET <here> "
|
// Set part of the query, aka <here> in "UPDATE table SET <here> "
|
||||||
$sets = [];
|
$sets = [];
|
||||||
|
|
||||||
foreach ($mapping['joinColumns'] as $columns) {
|
|
||||||
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
$conditions = [];
|
$conditions = [];
|
||||||
|
$tableName = '';
|
||||||
|
|
||||||
foreach ($mapping['joinColumns'] as $columns) {
|
if (array_key_exists('joinTable', $mapping)) {
|
||||||
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
|
$tableName = (null !== ($mapping['joinTable']['schema'] ?? null) ? $mapping['joinTable']['schema'] . '.' : '')
|
||||||
|
. $mapping['joinTable']['name'];
|
||||||
|
|
||||||
|
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
|
||||||
|
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
|
||||||
|
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
|
||||||
|
}
|
||||||
|
} elseif (array_key_exists('joinColumns', $mapping)) {
|
||||||
|
$tableName = $this->getTableName($metadata);
|
||||||
|
foreach ($mapping['joinColumns'] as $columns) {
|
||||||
|
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($mapping['joinColumns'] as $columns) {
|
||||||
|
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'UPDATE %s SET %s WHERE %s',
|
'UPDATE %s SET %s WHERE %s',
|
||||||
$this->getTableName($metadata),
|
$tableName,
|
||||||
implode(' ', $sets),
|
implode(' ', $sets),
|
||||||
implode(' AND ', $conditions)
|
implode(' AND ', $conditions)
|
||||||
);
|
);
|
||||||
@@ -166,10 +188,23 @@ class PersonMove
|
|||||||
* return an array of classes where entities should be deleted
|
* return an array of classes where entities should be deleted
|
||||||
* instead of moved.
|
* instead of moved.
|
||||||
*/
|
*/
|
||||||
protected function getDeleteEntities(): array
|
private function getDeleteEntities(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
AccompanyingPeriod::class,
|
Person\PersonCenterHistory::class,
|
||||||
|
HouseholdMember::class,
|
||||||
|
AccompanyingPeriodParticipation::class,
|
||||||
|
AccompanyingPeriod\AccompanyingPeriodWork::class,
|
||||||
|
Relationship::class
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getIgnoredEntities(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Person\PersonCurrentAddress::class,
|
||||||
|
PersonHouseholdAddress::class,
|
||||||
|
Person\PersonCenterCurrent::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
|||||||
namespace Chill\PersonBundle;
|
namespace Chill\PersonBundle;
|
||||||
|
|
||||||
use Chill\PersonBundle\DependencyInjection\CompilerPass\AccompanyingPeriodTimelineCompilerPass;
|
use Chill\PersonBundle\DependencyInjection\CompilerPass\AccompanyingPeriodTimelineCompilerPass;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
use Chill\PersonBundle\Widget\PersonListWidgetFactory;
|
use Chill\PersonBundle\Widget\PersonListWidgetFactory;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
@@ -26,5 +27,7 @@ class ChillPersonBundle extends Bundle
|
|||||||
->addWidgetFactory(new PersonListWidgetFactory());
|
->addWidgetFactory(new PersonListWidgetFactory());
|
||||||
|
|
||||||
$container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass());
|
$container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass());
|
||||||
|
$container->registerForAutoconfiguration(AccompanyingPeriodInfoUnionQueryPartInterface::class)
|
||||||
|
->addTag('chill_person.accompanying_period_info_part');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -42,10 +42,18 @@ final class ImportSocialWorkMetadata extends Command
|
|||||||
|
|
||||||
protected function configure()
|
protected function configure()
|
||||||
{
|
{
|
||||||
|
$description = 'Imports a structured table containing social issues, social actions, objectives, results and evaluations.';
|
||||||
|
$help = 'File to csv format, no headers, semi-colon as delimiter, datas sorted by alphabetical order, column after column.'. PHP_EOL
|
||||||
|
. 'Columns are: social issues parent, social issues child, social actions parent, social actions child, goals, results, evaluations.'. PHP_EOL
|
||||||
|
. PHP_EOL
|
||||||
|
. 'See social_work_metadata.csv as example.'. PHP_EOL;
|
||||||
|
|
||||||
$this
|
$this
|
||||||
->setName('chill:person:import-socialwork')
|
->setName('chill:person:import-socialwork')
|
||||||
->addOption('filepath', 'f', InputOption::VALUE_REQUIRED, 'The file to import.')
|
->addOption('filepath', 'f', InputOption::VALUE_REQUIRED, 'The file to import.')
|
||||||
->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The default language');
|
->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The default language')
|
||||||
|
->setDescription($description)
|
||||||
|
->setHelp($help);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output)
|
protected function execute(InputInterface $input, OutputInterface $output)
|
||||||
|
@@ -247,7 +247,7 @@ class PersonDuplicateController extends Controller
|
|||||||
);
|
);
|
||||||
|
|
||||||
$duplicatePersons = $this->similarPersonMatcher->
|
$duplicatePersons = $this->similarPersonMatcher->
|
||||||
matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL);
|
matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL, false);
|
||||||
|
|
||||||
$notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person);
|
$notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person);
|
||||||
|
|
||||||
@@ -264,14 +264,14 @@ class PersonDuplicateController extends Controller
|
|||||||
|
|
||||||
$nb_activity = $em->getRepository(Activity::class)->findBy(['person' => $id]);
|
$nb_activity = $em->getRepository(Activity::class)->findBy(['person' => $id]);
|
||||||
$nb_document = $em->getRepository(PersonDocument::class)->findBy(['person' => $id]);
|
$nb_document = $em->getRepository(PersonDocument::class)->findBy(['person' => $id]);
|
||||||
$nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]);
|
// $nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]);
|
||||||
$nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person' => $id]);
|
$nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person' => $id]);
|
||||||
$person = $em->getRepository(Person::class)->findOneBy(['id' => $id]);
|
$person = $em->getRepository(Person::class)->findOneBy(['id' => $id]);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'nb_activity' => count($nb_activity),
|
'nb_activity' => count($nb_activity),
|
||||||
'nb_document' => count($nb_document),
|
'nb_document' => count($nb_document),
|
||||||
'nb_event' => count($nb_event),
|
// 'nb_event' => count($nb_event),
|
||||||
'nb_task' => $nb_task,
|
'nb_task' => $nb_task,
|
||||||
'nb_addresses' => count($person->getAddresses()),
|
'nb_addresses' => count($person->getAddresses()),
|
||||||
];
|
];
|
||||||
|
@@ -15,6 +15,7 @@ use Chill\MainBundle\DependencyInjection\MissingBundleException;
|
|||||||
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
|
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
|
||||||
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
|
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
|
||||||
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
|
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
@@ -1010,18 +1011,42 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
|||||||
],
|
],
|
||||||
'initial_marking' => 'DRAFT',
|
'initial_marking' => 'DRAFT',
|
||||||
'places' => [
|
'places' => [
|
||||||
'DRAFT',
|
AccompanyingPeriod::STEP_DRAFT,
|
||||||
'CONFIRMED',
|
AccompanyingPeriod::STEP_CONFIRMED,
|
||||||
'CLOSED',
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
|
||||||
|
AccompanyingPeriod::STEP_CLOSED,
|
||||||
],
|
],
|
||||||
'transitions' => [
|
'transitions' => [
|
||||||
'confirm' => [
|
'confirm' => [
|
||||||
'from' => 'DRAFT',
|
'from' => AccompanyingPeriod::STEP_DRAFT,
|
||||||
'to' => 'CONFIRMED',
|
'to' => AccompanyingPeriod::STEP_CONFIRMED,
|
||||||
|
],
|
||||||
|
'mark_inactive_short' => [
|
||||||
|
'from' => AccompanyingPeriod::STEP_CONFIRMED,
|
||||||
|
'to' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
|
||||||
|
],
|
||||||
|
'mark_inactive_long' => [
|
||||||
|
'from' => [
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED,
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT
|
||||||
|
],
|
||||||
|
'to' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
|
||||||
|
],
|
||||||
|
'mark_active' => [
|
||||||
|
'from' => [
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
|
||||||
|
],
|
||||||
|
'to' => AccompanyingPeriod::STEP_CONFIRMED
|
||||||
],
|
],
|
||||||
'close' => [
|
'close' => [
|
||||||
'from' => 'CONFIRMED',
|
'from' => [
|
||||||
'to' => 'CLOSED',
|
AccompanyingPeriod::STEP_CONFIRMED,
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
|
||||||
|
],
|
||||||
|
'to' => AccompanyingPeriod::STEP_CLOSED,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@@ -128,6 +128,15 @@ class Configuration implements ConfigurationInterface
|
|||||||
->info('Can we have more than one simultaneous accompanying period in the same time. Default false.')
|
->info('Can we have more than one simultaneous accompanying period in the same time. Default false.')
|
||||||
->defaultValue(false)
|
->defaultValue(false)
|
||||||
->end()
|
->end()
|
||||||
|
->arrayNode('accompanying_period_lifecycle_delays')
|
||||||
|
->addDefaultsIfNotSet()
|
||||||
|
->info('Delays before marking an accompanying period as inactive')
|
||||||
|
->children()
|
||||||
|
->booleanNode('mark_inactive')->defaultTrue()->end()
|
||||||
|
->scalarNode('mark_inactive_short_after')->defaultValue('P6M')->end()
|
||||||
|
->scalarNode('mark_inactive_long_after')->defaultValue('P2Y')->end()
|
||||||
|
->end()
|
||||||
|
->end() // end of 'accompanying_period_lifecycle_delays
|
||||||
->end() // children of 'root', parent = root
|
->end() // children of 'root', parent = root
|
||||||
;
|
;
|
||||||
|
|
||||||
|
@@ -109,6 +109,24 @@ class AccompanyingPeriod implements
|
|||||||
*/
|
*/
|
||||||
public const STEP_CONFIRMED = 'CONFIRMED';
|
public const STEP_CONFIRMED = 'CONFIRMED';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an accompanying period as confirmed, but inactive
|
||||||
|
*
|
||||||
|
* this means that the accompanying period **is**
|
||||||
|
* confirmed, but no activity (Activity, AccompanyingPeriod, ...)
|
||||||
|
* has been associated, or updated, within this accompanying period.
|
||||||
|
*/
|
||||||
|
public const STEP_CONFIRMED_INACTIVE_SHORT = 'CONFIRMED_INACTIVE_SHORT';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark an accompanying period as confirmed, but inactive
|
||||||
|
*
|
||||||
|
* this means that the accompanying period **is**
|
||||||
|
* confirmed, but no activity (Activity, AccompanyingPeriod, ...)
|
||||||
|
* has been associated, or updated, within this accompanying period.
|
||||||
|
*/
|
||||||
|
public const STEP_CONFIRMED_INACTIVE_LONG = 'CONFIRMED_INACTIVE_LONG';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark an accompanying period as "draft".
|
* Mark an accompanying period as "draft".
|
||||||
*
|
*
|
||||||
@@ -340,6 +358,7 @@ class AccompanyingPeriod implements
|
|||||||
/**
|
/**
|
||||||
* @ORM\Column(type="string", length=32, nullable=true)
|
* @ORM\Column(type="string", length=32, nullable=true)
|
||||||
* @Groups({"read"})
|
* @Groups({"read"})
|
||||||
|
* @var AccompanyingPeriod::STEP_*
|
||||||
*/
|
*/
|
||||||
private string $step = self::STEP_DRAFT;
|
private string $step = self::STEP_DRAFT;
|
||||||
|
|
||||||
@@ -712,11 +731,9 @@ class AccompanyingPeriod implements
|
|||||||
if ($this->getStep() === self::STEP_DRAFT) {
|
if ($this->getStep() === self::STEP_DRAFT) {
|
||||||
return [[self::STEP_DRAFT]];
|
return [[self::STEP_DRAFT]];
|
||||||
}
|
}
|
||||||
|
if (str_starts_with($this->getStep(), 'CONFIRM')) {
|
||||||
if ($this->getStep() === self::STEP_CONFIRMED) {
|
|
||||||
return [[self::STEP_DRAFT, self::STEP_CONFIRMED]];
|
return [[self::STEP_DRAFT, self::STEP_CONFIRMED]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->getStep() === self::STEP_CLOSED) {
|
if ($this->getStep() === self::STEP_CLOSED) {
|
||||||
return [[self::STEP_DRAFT, self::STEP_CONFIRMED, self::STEP_CLOSED]];
|
return [[self::STEP_DRAFT, self::STEP_CONFIRMED, self::STEP_CLOSED]];
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informations about AccompanyingPeriod
|
||||||
|
*
|
||||||
|
* This entity allow access to some basic information about the AccompanyingPeriod. It is
|
||||||
|
* populated from a SQL view, dynamically build from various sources.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
*
|
||||||
|
* - get the user involved with an accompanying period
|
||||||
|
*
|
||||||
|
* @ORM\Entity()
|
||||||
|
* @ORM\Table(name="view_chill_person_accompanying_period_info")
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodInfo
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
/**
|
||||||
|
* @var AccompanyingPeriod
|
||||||
|
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
|
||||||
|
*/
|
||||||
|
public readonly AccompanyingPeriod $accompanyingPeriod,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @ORM\Column(type="text")
|
||||||
|
* @ORM\Id
|
||||||
|
*/
|
||||||
|
public readonly string $relatedEntity,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @ORM\Id
|
||||||
|
*/
|
||||||
|
public readonly int $relatedEntityId,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
* @ORM\ManyToOne(targetEntity=User::class)
|
||||||
|
*/
|
||||||
|
public readonly ?User $user,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \DateTimeImmutable
|
||||||
|
* @ORM\Column(type="datetime_immutable")
|
||||||
|
*/
|
||||||
|
public readonly \DateTimeImmutable $infoDate,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
* @ORM\Column(type="json")
|
||||||
|
*/
|
||||||
|
public readonly array $metadata,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
* @ORM\Column(type="text")
|
||||||
|
*/
|
||||||
|
public readonly string $discriminator,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@@ -213,6 +213,10 @@ class Household
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
|
* @Serializer\SerializedName("current_composition")
|
||||||
|
*/
|
||||||
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
|
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
|
||||||
{
|
{
|
||||||
$at ??= new DateTimeImmutable('today');
|
$at ??= new DateTimeImmutable('today');
|
||||||
|
@@ -44,6 +44,7 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
|
|||||||
/**
|
/**
|
||||||
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||||
* @Assert\GreaterThanOrEqual(propertyPath="startDate", groups={"Default", "household_composition"})
|
* @Assert\GreaterThanOrEqual(propertyPath="startDate", groups={"Default", "household_composition"})
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?DateTimeImmutable $endDate = null;
|
private ?DateTimeImmutable $endDate = null;
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
|
|||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne(targetEntity=HouseholdCompositionType::class)
|
* @ORM\ManyToOne(targetEntity=HouseholdCompositionType::class)
|
||||||
* @ORM\JoinColumn(nullable=false)
|
* @ORM\JoinColumn(nullable=false)
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?HouseholdCompositionType $householdCompositionType = null;
|
private ?HouseholdCompositionType $householdCompositionType = null;
|
||||||
|
|
||||||
@@ -71,12 +73,14 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
|
|||||||
* @ORM\Column(type="integer", nullable=true, options={"default": null})
|
* @ORM\Column(type="integer", nullable=true, options={"default": null})
|
||||||
* @Assert\NotNull
|
* @Assert\NotNull
|
||||||
* @Assert\GreaterThanOrEqual(0, groups={"Default", "household_composition"})
|
* @Assert\GreaterThanOrEqual(0, groups={"Default", "household_composition"})
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?int $numberOfChildren = null;
|
private ?int $numberOfChildren = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="date_immutable", nullable=false)
|
* @ORM\Column(type="date_immutable", nullable=false)
|
||||||
* @Assert\NotNull(groups={"Default", "household_composition"})
|
* @Assert\NotNull(groups={"Default", "household_composition"})
|
||||||
|
* @Serializer\Groups({"docgen:read"})
|
||||||
*/
|
*/
|
||||||
private ?DateTimeImmutable $startDate = null;
|
private ?DateTimeImmutable $startDate = null;
|
||||||
|
|
||||||
|
@@ -98,7 +98,7 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
|
|||||||
'acp_geog_units'
|
'acp_geog_units'
|
||||||
);
|
);
|
||||||
|
|
||||||
$qb->andWhere($qb->expr()->eq('acp_geog_units.layer', ':acp_geog_unit_layer'));
|
$qb->andWhere($qb->expr()->in('acp_geog_units.layer', ':acp_geog_unit_layer'));
|
||||||
|
|
||||||
$qb->setParameter('acp_geog_unit_layer', $data['level']);
|
$qb->setParameter('acp_geog_unit_layer', $data['level']);
|
||||||
|
|
||||||
@@ -129,6 +129,8 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
|
|||||||
'class' => GeographicalUnitLayer::class,
|
'class' => GeographicalUnitLayer::class,
|
||||||
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
|
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
|
||||||
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
|
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
|
||||||
|
'multiple' => true,
|
||||||
|
'expanded' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -86,13 +86,19 @@ final class StepAggregator implements AggregatorInterface
|
|||||||
return function ($value): string {
|
return function ($value): string {
|
||||||
switch ($value) {
|
switch ($value) {
|
||||||
case AccompanyingPeriod::STEP_DRAFT:
|
case AccompanyingPeriod::STEP_DRAFT:
|
||||||
return $this->translator->trans('Draft');
|
return $this->translator->trans('course.draft');
|
||||||
|
|
||||||
case AccompanyingPeriod::STEP_CONFIRMED:
|
case AccompanyingPeriod::STEP_CONFIRMED:
|
||||||
return $this->translator->trans('Confirmed');
|
return $this->translator->trans('course.confirmed');
|
||||||
|
|
||||||
case AccompanyingPeriod::STEP_CLOSED:
|
case AccompanyingPeriod::STEP_CLOSED:
|
||||||
return $this->translator->trans('Closed');
|
return $this->translator->trans('course.closed');
|
||||||
|
|
||||||
|
case AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT:
|
||||||
|
return $this->translator->trans('course.inactive_short');
|
||||||
|
|
||||||
|
case AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG:
|
||||||
|
return $this->translator->trans('course.inactive_long');
|
||||||
|
|
||||||
case '_header':
|
case '_header':
|
||||||
return 'Step';
|
return 'Step';
|
||||||
|
@@ -101,6 +101,8 @@ class GeographicalUnitAggregator implements AggregatorInterface
|
|||||||
'class' => GeographicalUnitLayer::class,
|
'class' => GeographicalUnitLayer::class,
|
||||||
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
|
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
|
||||||
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
|
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
|
||||||
|
'multiple' => true,
|
||||||
|
'expanded' => true,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -41,6 +41,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Query\Expr\Join;
|
use Doctrine\ORM\Query\Expr\Join;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
use function strlen;
|
use function strlen;
|
||||||
|
|
||||||
class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
|
class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
|
||||||
@@ -100,6 +101,8 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
|
|||||||
|
|
||||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
private UserHelper $userHelper;
|
private UserHelper $userHelper;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -113,6 +116,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
|
|||||||
SocialIssueRepository $socialIssueRepository,
|
SocialIssueRepository $socialIssueRepository,
|
||||||
SocialIssueRender $socialIssueRender,
|
SocialIssueRender $socialIssueRender,
|
||||||
TranslatableStringHelperInterface $translatableStringHelper,
|
TranslatableStringHelperInterface $translatableStringHelper,
|
||||||
|
TranslatorInterface $translator,
|
||||||
RollingDateConverterInterface $rollingDateConverter,
|
RollingDateConverterInterface $rollingDateConverter,
|
||||||
UserHelper $userHelper
|
UserHelper $userHelper
|
||||||
) {
|
) {
|
||||||
@@ -126,6 +130,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
|
|||||||
$this->thirdPartyRender = $thirdPartyRender;
|
$this->thirdPartyRender = $thirdPartyRender;
|
||||||
$this->thirdPartyRepository = $thirdPartyRepository;
|
$this->thirdPartyRepository = $thirdPartyRepository;
|
||||||
$this->translatableStringHelper = $translatableStringHelper;
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
$this->translator = $translator;
|
||||||
$this->rollingDateConverter = $rollingDateConverter;
|
$this->rollingDateConverter = $rollingDateConverter;
|
||||||
$this->userHelper = $userHelper;
|
$this->userHelper = $userHelper;
|
||||||
}
|
}
|
||||||
@@ -250,6 +255,27 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case 'step':
|
||||||
|
return fn ($value) => match ($value) {
|
||||||
|
'_header' => 'export.list.acp.step',
|
||||||
|
null => '',
|
||||||
|
AccompanyingPeriod::STEP_DRAFT => $this->translator->trans('course.draft'),
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED => $this->translator->trans('course.confirmed'),
|
||||||
|
AccompanyingPeriod::STEP_CLOSED => $this->translator->trans('course.closed'),
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT => $this->translator->trans('course.inactive_short'),
|
||||||
|
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG => $this->translator->trans('course.inactive_long'),
|
||||||
|
default => $value,
|
||||||
|
};
|
||||||
|
|
||||||
|
case 'intensity':
|
||||||
|
return fn ($value) => match ($value) {
|
||||||
|
'_header' => 'export.list.acp.intensity',
|
||||||
|
null => '',
|
||||||
|
AccompanyingPeriod::INTENSITY_OCCASIONAL => $this->translator->trans('occasional'),
|
||||||
|
AccompanyingPeriod::INTENSITY_REGULAR => $this->translator->trans('regular'),
|
||||||
|
default => $value,
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return static function ($value) use ($key) {
|
return static function ($value) use ($key) {
|
||||||
if ('_header' === $value) {
|
if ('_header' === $value) {
|
||||||
|
@@ -107,7 +107,6 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface
|
|||||||
return $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.household.' . $key);
|
return $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.household.' . $key);
|
||||||
|
|
||||||
case 'compositionType':
|
case 'compositionType':
|
||||||
//dump($values);
|
|
||||||
return $this->translatableStringHelper->getLabel($key, $values, 'export.list.household.' . $key);
|
return $this->translatableStringHelper->getLabel($key, $values, 'export.list.household.' . $key);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Form\Type\DateIntervalType;
|
||||||
|
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||||
|
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||||
|
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
|
||||||
|
use Chill\PersonBundle\Export\Declarations;
|
||||||
|
use Doctrine\DBAL\Types\Types;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter accompanying course which have a row in AccompanyingPeriodInfo within the given
|
||||||
|
* interval
|
||||||
|
*/
|
||||||
|
final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private RollingDateConverterInterface $rollingDateConverter,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('start_date', PickRollingDateType::class, [
|
||||||
|
'label' => 'export.filter.course.having_info_within_interval.start_date',
|
||||||
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
|
])
|
||||||
|
->add('end_date', PickRollingDateType::class, [
|
||||||
|
'label' => 'export.filter.course.having_info_within_interval.end_date',
|
||||||
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.filter.course.having_info_within_interval.title';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string'): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'export.filter.course.having_info_within_interval.Only course with events between %startDate% and %endDate%',
|
||||||
|
[
|
||||||
|
'%startDate%' => $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y'),
|
||||||
|
'%endDate%' => $this->rollingDateConverter->convert($data['end_date'])->format('d-m-Y'),
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data): void
|
||||||
|
{
|
||||||
|
$ai = 'having_ai_within_interval_acc_info';
|
||||||
|
$as = 'having_ai_within_interval_start_date';
|
||||||
|
$ae = 'having_ai_within_interval_end_date';
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->exists(
|
||||||
|
'SELECT 1 FROM ' . AccompanyingPeriodInfo::class . " {$ai} WHERE {$ai}.infoDate BETWEEN :{$as} AND :{$ae} AND IDENTITY({$ai}.accompanyingPeriod) = acp.id"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->setParameter($as, $this->rollingDateConverter->convert($data['start_date']), Types::DATETIME_IMMUTABLE)
|
||||||
|
->setParameter($ae, $this->rollingDateConverter->convert($data['end_date']), Types::DATETIME_IMMUTABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::ACP_TYPE;
|
||||||
|
}
|
||||||
|
}
|
@@ -32,9 +32,11 @@ class StepFilter implements FilterInterface
|
|||||||
private const P = 'acp_step_filter_date';
|
private const P = 'acp_step_filter_date';
|
||||||
|
|
||||||
private const STEPS = [
|
private const STEPS = [
|
||||||
'Draft' => AccompanyingPeriod::STEP_DRAFT,
|
'course.draft' => AccompanyingPeriod::STEP_DRAFT,
|
||||||
'Confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
|
'course.confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
|
||||||
'Closed' => AccompanyingPeriod::STEP_CLOSED,
|
'course.closed' => AccompanyingPeriod::STEP_CLOSED,
|
||||||
|
'course.inactive_short' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
|
||||||
|
'course.inactive_long' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
|
||||||
];
|
];
|
||||||
|
|
||||||
private RollingDateConverterInterface $rollingDateConverter;
|
private RollingDateConverterInterface $rollingDateConverter;
|
||||||
@@ -96,7 +98,7 @@ class StepFilter implements FilterInterface
|
|||||||
'data' => self::DEFAULT_CHOICE,
|
'data' => self::DEFAULT_CHOICE,
|
||||||
])
|
])
|
||||||
->add('calc_date', PickRollingDateType::class, [
|
->add('calc_date', PickRollingDateType::class, [
|
||||||
'label' => 'export.acp.filter.by_step.date_calc',
|
'label' => 'export.filter.course.by_step.date_calc',
|
||||||
'data' => new RollingDate(RollingDate::T_TODAY),
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
|
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Export\Declarations;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter course where a user is "working" on it
|
||||||
|
*
|
||||||
|
* Makes use of AccompanyingPeriodInfo
|
||||||
|
*/
|
||||||
|
readonly class UserWorkingOnCourseFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
private const AI_ALIAS = 'user_working_on_course_filter_acc_info';
|
||||||
|
private const AI_USERS = 'user_working_on_course_filter_users';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private UserRender $userRender,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('users', PickUserDynamicType::class, [
|
||||||
|
'multiple' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.filter.course.by_user_working.title';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string'): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'export.filter.course.by_user_working.Filtered by user working on course: only %users%', [
|
||||||
|
'%users%' => implode(
|
||||||
|
', ',
|
||||||
|
array_map(
|
||||||
|
fn (User $u) => $this->userRender->renderString($u, []),
|
||||||
|
$data['users']
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data): void
|
||||||
|
{
|
||||||
|
$qb
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->exists(
|
||||||
|
"SELECT 1 FROM " . AccompanyingPeriod\AccompanyingPeriodInfo::class . " " . self::AI_ALIAS . " " .
|
||||||
|
"WHERE " . self::AI_ALIAS . ".user IN (:" . self::AI_USERS .") AND IDENTITY(" . self::AI_ALIAS . ".accompanyingPeriod) = acp.id"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->setParameter(self::AI_USERS, $data['users'])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::ACP_TYPE;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||||
|
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||||
|
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||||
|
use Chill\PersonBundle\Export\Declarations;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private RollingDateConverterInterface $rollingDateConverter,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('start_date', PickRollingDateType::class, [
|
||||||
|
'label' => 'export.filter.work.end_between_dates.start_date',
|
||||||
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
|
])
|
||||||
|
->add('end_date', PickRollingDateType::class, [
|
||||||
|
'label' => 'export.filter.work.end_between_dates.end_date',
|
||||||
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
|
])
|
||||||
|
->add('keep_null', CheckboxType::class, [
|
||||||
|
'label' => 'export.filter.work.end_between_dates.keep_null',
|
||||||
|
'help' => 'export.filter.work.end_between_dates.keep_null_help',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.filter.work.end_between_dates.title';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string'): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'export.filter.work.end_between_dates.Only where end date is between %endDate% and %endDate%',
|
||||||
|
[
|
||||||
|
'%startDate%' => null !== $data['start_date'] ? $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y') : '',
|
||||||
|
'%endDate%' => null !== $data['end_date'] ? $this->rollingDateConverter->convert($data['end_date'])->format('d-m-Y') : '',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data): void
|
||||||
|
{
|
||||||
|
$as = 'acc_pe_work_end_between_filter_start';
|
||||||
|
$ae = 'acc_pe_work_end_between_filter_end';
|
||||||
|
|
||||||
|
$start = match ($data['keep_null']) {
|
||||||
|
true => $qb->expr()->orX(
|
||||||
|
$qb->expr()->lte('acpw.endDate', ':'.$ae),
|
||||||
|
$qb->expr()->isNull('acpw.endDate')
|
||||||
|
),
|
||||||
|
false => $qb->expr()->andX(
|
||||||
|
$qb->expr()->lte('acpw.endDate', ':'.$ae),
|
||||||
|
$qb->expr()->isNotNull('acpw.endDate')
|
||||||
|
),
|
||||||
|
default => throw new \LogicException("This value is not supported"),
|
||||||
|
};
|
||||||
|
$end = match ($data['keep_null']) {
|
||||||
|
true => $qb->expr()->orX(
|
||||||
|
$qb->expr()->gt('acpw.endDate', ':'.$as),
|
||||||
|
$qb->expr()->isNull('acpw.endDate')
|
||||||
|
),
|
||||||
|
false => $qb->expr()->andX(
|
||||||
|
$qb->expr()->gt('acpw.endDate', ':'.$as),
|
||||||
|
$qb->expr()->isNotNull('acpw.endDate')
|
||||||
|
),
|
||||||
|
default => throw new \LogicException("This value is not supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (null !== $data['start_date']) {
|
||||||
|
$qb
|
||||||
|
->andWhere($start)
|
||||||
|
->setParameter($as, $this->rollingDateConverter->convert($data['start_date']));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $data['end_date']) {
|
||||||
|
$qb
|
||||||
|
->andWhere($end)
|
||||||
|
->setParameter($ae, $this->rollingDateConverter->convert($data['end_date']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::SOCIAL_WORK_ACTION_TYPE;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Export\FilterInterface;
|
||||||
|
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||||
|
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||||
|
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||||
|
use Chill\PersonBundle\Export\Declarations;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private RollingDateConverterInterface $rollingDateConverter,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder): void
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('start_date', PickRollingDateType::class, [
|
||||||
|
'label' => 'export.filter.work.start_between_dates.start_date',
|
||||||
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
|
])
|
||||||
|
->add('end_date', PickRollingDateType::class, [
|
||||||
|
'label' => 'export.filter.work.start_between_dates.end_date',
|
||||||
|
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||||
|
])
|
||||||
|
->add('keep_null', CheckboxType::class, [
|
||||||
|
'label' => 'export.filter.work.start_between_dates.keep_null',
|
||||||
|
'help' => 'export.filter.work.start_between_dates.keep_null_help',
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTitle(): string
|
||||||
|
{
|
||||||
|
return 'export.filter.work.start_between_dates.title';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function describeAction($data, $format = 'string'): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'export.filter.work.start_between_dates.Only where start date is between %startDate% and %endDate%',
|
||||||
|
[
|
||||||
|
'%startDate%' => null !== $data['start_date'] ? $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y') : '',
|
||||||
|
'%endDate%' => null !== $data['end_date'] ? $this->rollingDateConverter->convert($data['end_date'])->format('d-m-Y') : '',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRole(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function alterQuery(QueryBuilder $qb, $data): void
|
||||||
|
{
|
||||||
|
$as = 'acc_pe_work_start_between_filter_start';
|
||||||
|
$ae = 'acc_pe_work_start_between_filter_end';
|
||||||
|
|
||||||
|
$start = match ($data['keep_null']) {
|
||||||
|
true => $qb->expr()->orX(
|
||||||
|
$qb->expr()->lte('acpw.startDate', ':'.$ae),
|
||||||
|
$qb->expr()->isNull('acpw.startDate')
|
||||||
|
),
|
||||||
|
false => $qb->expr()->andX(
|
||||||
|
$qb->expr()->lte('acpw.startDate', ':'.$ae),
|
||||||
|
$qb->expr()->isNotNull('acpw.startDate')
|
||||||
|
),
|
||||||
|
default => throw new \LogicException("This value is not supported"),
|
||||||
|
};
|
||||||
|
$end = match ($data['keep_null']) {
|
||||||
|
true => $qb->expr()->orX(
|
||||||
|
$qb->expr()->gt('acpw.startDate', ':'.$as),
|
||||||
|
$qb->expr()->isNull('acpw.startDate')
|
||||||
|
),
|
||||||
|
false => $qb->expr()->andX(
|
||||||
|
$qb->expr()->gt('acpw.startDate', ':'.$as),
|
||||||
|
$qb->expr()->isNotNull('acpw.startDate')
|
||||||
|
),
|
||||||
|
default => throw new \LogicException("This value is not supported"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (null !== $data['start_date']) {
|
||||||
|
$qb
|
||||||
|
->andWhere($start)
|
||||||
|
->setParameter($as, $this->rollingDateConverter->convert($data['start_date']));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== $data['end_date']) {
|
||||||
|
$qb
|
||||||
|
->andWhere($end)
|
||||||
|
->setParameter($ae, $this->rollingDateConverter->convert($data['end_date']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function applyOn(): string
|
||||||
|
{
|
||||||
|
return Declarations::SOCIAL_WORK_ACTION_TYPE;
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,7 @@ use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
|||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
|
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
|
||||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Knp\Menu\MenuItem;
|
use Knp\Menu\MenuItem;
|
||||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
@@ -106,17 +107,19 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
->setExtras([
|
->setExtras([
|
||||||
'order' => 99999,
|
'order' => 99999,
|
||||||
]);
|
]);
|
||||||
/*
|
|
||||||
$menu->addChild($this->translator->trans('Person duplicate'), [
|
if ($this->security->isGranted(PersonVoter::DUPLICATE, $parameters['person'])) {
|
||||||
'route' => 'chill_person_duplicate_view',
|
$menu->addChild($this->translator->trans('Person duplicate'), [
|
||||||
'routeParameters' => [
|
'route' => 'chill_person_duplicate_view',
|
||||||
'person_id' => $parameters['person']->getId(),
|
'routeParameters' => [
|
||||||
],
|
'person_id' => $parameters['person']->getId(),
|
||||||
])
|
],
|
||||||
->setExtras([
|
])
|
||||||
'order' => 99999,
|
->setExtras([
|
||||||
]);
|
'order' => 99999,
|
||||||
*/
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
'visible' === $this->showAccompanyingPeriod
|
'visible' === $this->showAccompanyingPeriod
|
||||||
&& $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person'])
|
&& $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person'])
|
||||||
|
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
|
||||||
|
use DateInterval;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use LogicException;
|
||||||
|
use Symfony\Component\Clock\ClockInterface;
|
||||||
|
|
||||||
|
readonly class AccompanyingPeriodInfoRepository implements AccompanyingPeriodInfoRepositoryInterface
|
||||||
|
{
|
||||||
|
private EntityRepository $entityRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private ClockInterface $clock,
|
||||||
|
private EntityManagerInterface $em,
|
||||||
|
) {
|
||||||
|
$this->entityRepository = $em->getRepository($this->getClassName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAccompanyingPeriodIdInactiveAfter(DateInterval $interval, array $statuses = []): array
|
||||||
|
{
|
||||||
|
$query = $this->em->createQuery();
|
||||||
|
$baseDql = 'SELECT DISTINCT IDENTITY(ai.accompanyingPeriod) FROM '.AccompanyingPeriodInfo::class.' ai JOIN ai.accompanyingPeriod a WHERE NOT EXISTS
|
||||||
|
(SELECT 1 FROM ' . AccompanyingPeriodInfo::class . ' aiz WHERE aiz.infoDate > :after AND IDENTITY(aiz.accompanyingPeriod) = IDENTITY(ai.accompanyingPeriod))';
|
||||||
|
|
||||||
|
if ([] !== $statuses) {
|
||||||
|
$dql = $baseDql . ' AND a.step IN (:statuses)';
|
||||||
|
$query->setParameter('statuses', $statuses);
|
||||||
|
} else {
|
||||||
|
$dql = $baseDql;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->setDQL($dql)
|
||||||
|
->setParameter('after', $this->clock->now()->sub($interval))
|
||||||
|
->getSingleColumnResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAccompanyingPeriodIdActiveSince(DateInterval $interval, array $statuses = []): array
|
||||||
|
{
|
||||||
|
$query = $this->em->createQuery();
|
||||||
|
$baseDql = 'SELECT DISTINCT IDENTITY(ai.accompanyingPeriod) FROM ' . AccompanyingPeriodInfo::class . ' ai
|
||||||
|
JOIN ai.accompanyingPeriod a WHERE ai.infoDate > :after';
|
||||||
|
|
||||||
|
if ([] !== $statuses) {
|
||||||
|
$dql = $baseDql . ' AND a.step IN (:statuses)';
|
||||||
|
$query->setParameter('statuses', $statuses);
|
||||||
|
} else {
|
||||||
|
$dql = $baseDql;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->setDQL($dql)
|
||||||
|
->setParameter('after', $this->clock->now()->sub($interval))
|
||||||
|
->getSingleColumnResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?AccompanyingPeriodInfo
|
||||||
|
{
|
||||||
|
throw new LogicException("Calling an accompanying period info by his id does not make sense");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->entityRepository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?AccompanyingPeriodInfo
|
||||||
|
{
|
||||||
|
return $this->entityRepository->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodInfo::class;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template-extends ObjectRepository<AccompanyingPeriodInfo>
|
||||||
|
*/
|
||||||
|
interface AccompanyingPeriodInfoRepositoryInterface extends ObjectRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return a list of id for inactive accompanying periods
|
||||||
|
*
|
||||||
|
* @param \DateInterval $interval
|
||||||
|
* @param list<AccompanyingPeriod::STEP_*> $statuses
|
||||||
|
* @return list<int>
|
||||||
|
*/
|
||||||
|
public function findAccompanyingPeriodIdInactiveAfter(\DateInterval $interval, array $statuses = []): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DateInterval $interval
|
||||||
|
* @param list<AccompanyingPeriod::STEP_*> $statuses
|
||||||
|
* @return list<int>
|
||||||
|
*/
|
||||||
|
public function findAccompanyingPeriodIdActiveSince(\DateInterval $interval, array $statuses = []): array;
|
||||||
|
}
|
@@ -14,7 +14,7 @@
|
|||||||
<scopes></scopes>
|
<scopes></scopes>
|
||||||
<referrer></referrer>
|
<referrer></referrer>
|
||||||
<resources></resources>
|
<resources></resources>
|
||||||
<start-date v-if="accompanyingCourse.step === 'CONFIRMED'"></start-date>
|
<start-date v-if="accompanyingCourse.step.startsWith('CONFIRMED')"></start-date>
|
||||||
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
|
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
|
||||||
<confirm v-if="accompanyingCourse.step === 'DRAFT'"></confirm>
|
<confirm v-if="accompanyingCourse.step === 'DRAFT'"></confirm>
|
||||||
|
|
||||||
|
@@ -11,12 +11,22 @@
|
|||||||
{{ $t('course.step.draft') }}
|
{{ $t('course.step.draft') }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="accompanyingCourse.step === 'CONFIRMED'" class="text-md-end">
|
<span v-else-if="accompanyingCourse.step === 'CONFIRMED' || accompanyingCourse.step === 'CONFIRMED_INACTIVE_SHORT' || accompanyingCourse.step === 'CONFIRMED_INACTIVE_LONG'" class="text-md-end">
|
||||||
<span class="d-md-block mb-md-3">
|
<span v-if="accompanyingCourse.step === 'CONFIRMED'" class="d-md-block mb-md-3">
|
||||||
<span class="badge bg-primary">
|
<span class="badge bg-primary">
|
||||||
{{ $t('course.step.active') }}
|
{{ $t('course.step.active') }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
<span v-else-if="accompanyingCourse.step === 'CONFIRMED_INACTIVE_SHORT'" class="d-md-block mb-md-3">
|
||||||
|
<span class="badge bg-chill-yellow text-primary">
|
||||||
|
{{ $t('course.step.inactive_short') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span v-else-if="accompanyingCourse.step === 'CONFIRMED_INACTIVE_LONG'" class="d-md-block mb-md-3">
|
||||||
|
<span class="badge bg-chill-pink">
|
||||||
|
{{ $t('course.step.inactive_long') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
<span class="d-md-block">
|
<span class="d-md-block">
|
||||||
<span class="d-md-block ms-3 ms-md-0">
|
<span class="d-md-block ms-3 ms-md-0">
|
||||||
<i>{{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }}</i>
|
<i>{{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }}</i>
|
||||||
|
@@ -21,7 +21,9 @@ const appMessages = {
|
|||||||
step: {
|
step: {
|
||||||
draft: "Brouillon",
|
draft: "Brouillon",
|
||||||
active: "En file active",
|
active: "En file active",
|
||||||
closed: "Cloturé"
|
closed: "Cloturé",
|
||||||
|
inactive_short: "Hors file active",
|
||||||
|
inactive_long: "Pré-archivé",
|
||||||
},
|
},
|
||||||
open_at: "ouvert le ",
|
open_at: "ouvert le ",
|
||||||
by: "par ",
|
by: "par ",
|
||||||
|
@@ -16,11 +16,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="wh-col">
|
<div class="wh-col">
|
||||||
{% if period.step == 'DRAFT' %}
|
{% if period.step == 'DRAFT' %}
|
||||||
<span class="badge bg-secondary">{{- 'Draft'|trans|upper -}}</span>
|
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans|e('html_attr') }}">{{ 'course.draft'|trans }}</span>
|
||||||
{% elseif period.step == 'CONFIRMED' %}
|
{% elseif period.step == 'CLOSED' %}
|
||||||
<span class="badge bg-primary">{{- 'Confirmed'|trans|upper -}}</span>
|
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.closed'|trans|e('html_attr') }}">{{ 'course.closed'|trans }}</span>
|
||||||
{% else %}
|
{% elseif period.step == 'CONFIRMED_INACTIVE_SHORT' %}
|
||||||
<span class="badge bg-danger">{{- 'Closed'|trans|upper -}}</span>
|
<span class="badge bg-chill-yellow text-primary" style="font-size: 85%;" title="{{ 'course.inactive_short'|trans|e('html_attr') }}">{{ 'course.inactive_short'|trans }}</span>
|
||||||
|
{% elseif period.step == 'CONFIRMED_INACTIVE_LONG' %}
|
||||||
|
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.inactive_long'|trans|e('html_attr') }}">{{ 'course.inactive_long'|trans }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -52,11 +52,13 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if acp.step == 'DRAFT' %}
|
{% if acp.step == 'DRAFT' %}
|
||||||
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans }}">{{ 'course.draft'|trans }}</span>
|
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans|e('html_attr') }}">{{ 'course.draft'|trans }}</span>
|
||||||
{% endif %}
|
{% elseif acp.step == 'CLOSED' %}
|
||||||
|
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.closed'|trans|e('html_attr') }}">{{ 'course.closed'|trans }}</span>
|
||||||
{% if acp.step == 'CLOSED' %}
|
{% elseif acp.step == 'CONFIRMED_INACTIVE_SHORT' %}
|
||||||
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.closed'|trans }}">{{ 'course.closed'|trans }}</span>
|
<span class="badge bg-chill-yellow text-primary" style="font-size: 85%;" title="{{ 'course.inactive_short'|trans|e('html_attr') }}">{{ 'course.inactive_short'|trans }}</span>
|
||||||
|
{% elseif acp.step == 'CONFIRMED_INACTIVE_LONG' %}
|
||||||
|
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.inactive_long'|trans|e('html_attr') }}">{{ 'course.inactive_long'|trans }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -39,7 +39,7 @@
|
|||||||
<li><b>{{ person.counters.nb_activity }}</b> {{ (person.counters.nb_activity > 1)? 'échanges' : 'échange' }}</li>
|
<li><b>{{ person.counters.nb_activity }}</b> {{ (person.counters.nb_activity > 1)? 'échanges' : 'échange' }}</li>
|
||||||
<li><b>{{ person.counters.nb_task }}</b> {{ (person.counters.nb_task > 1)? 'tâches' : 'tâche' }}</li>
|
<li><b>{{ person.counters.nb_task }}</b> {{ (person.counters.nb_task > 1)? 'tâches' : 'tâche' }}</li>
|
||||||
<li><b>{{ person.counters.nb_document }}</b> {{ (person.counters.nb_document > 1)? 'documents' : 'document' }}</li>
|
<li><b>{{ person.counters.nb_document }}</b> {{ (person.counters.nb_document > 1)? 'documents' : 'document' }}</li>
|
||||||
<li><b>{{ person.counters.nb_event }}</b> {{ (person.counters.nb_event > 1)? 'événements' : 'événement' }}</li>
|
{# <li><b>{{ person.counters.nb_event }}</b> {{ (person.counters.nb_event > 1)? 'événements' : 'événement' }}</li>#}
|
||||||
<li><b>{{ person.counters.nb_addresses }}</b> {{ (person.counters.nb_addresses > 1)? 'adresses' : 'adresse' }}</li>
|
<li><b>{{ person.counters.nb_addresses }}</b> {{ (person.counters.nb_addresses > 1)? 'adresses' : 'adresse' }}</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
<h1>{{ 'Merge duplicate persons folders'|trans }}</h1>
|
<h1>{{ 'Merge duplicate persons folders'|trans }}</h1>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-11">
|
||||||
<p><b>{{ 'Old person'|trans }}</b>:
|
<p><b>{{ 'Old person'|trans }}</b>:
|
||||||
{{ 'Old person explain'|trans }}
|
{{ 'Old person explain'|trans }}
|
||||||
</p>
|
</p>
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-11">
|
||||||
<p><b>{{ 'New person'|trans }}</b>:
|
<p><b>{{ 'New person'|trans }}</b>:
|
||||||
{{ 'New person explain'|trans }}
|
{{ 'New person explain'|trans }}
|
||||||
</p>
|
</p>
|
||||||
@@ -63,10 +63,10 @@
|
|||||||
|
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
|
|
||||||
<div class="col-md-4 centered">
|
<div class="col-md-12 centered">
|
||||||
|
|
||||||
<div class="container-fluid" style="padding-top: 1em;">
|
<div class="container-fluid" style="padding-top: 1em;">
|
||||||
<div class="col-1 clear" style="padding-top: 10px;">
|
<div class="clear" style="padding-top: 10px;">
|
||||||
{{ form_widget(form.confirm) }}
|
{{ form_widget(form.confirm) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-11">
|
<div class="col-11">
|
||||||
|
@@ -3,6 +3,14 @@
|
|||||||
{{ 'workflow.SocialAction deleted'|trans }}
|
{{ 'workflow.SocialAction deleted'|trans }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
<div class="flex-table accompanying-course-work">
|
||||||
|
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
|
||||||
|
'w': work,
|
||||||
|
'displayAction': false,
|
||||||
|
'displayContent': 'short',
|
||||||
|
'itemBlocClass': 'bg-chill-light-gray'
|
||||||
|
} %}
|
||||||
|
</div>
|
||||||
{% if display_action is defined and display_action == true %}
|
{% if display_action is defined and display_action == true %}
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
|
@@ -18,6 +18,7 @@ use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
|||||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\Household\Household;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
use Symfony\Component\Security\Core\Security;
|
use Symfony\Component\Security\Core\Security;
|
||||||
@@ -119,6 +120,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
|
|||||||
->generate(self::class)
|
->generate(self::class)
|
||||||
->addCheckFor(null, [self::CREATE, self::REASSIGN_BULK])
|
->addCheckFor(null, [self::CREATE, self::REASSIGN_BULK])
|
||||||
->addCheckFor(AccompanyingPeriod::class, [self::TOGGLE_CONFIDENTIAL, ...self::ALL])
|
->addCheckFor(AccompanyingPeriod::class, [self::TOGGLE_CONFIDENTIAL, ...self::ALL])
|
||||||
|
->addCheckFor(Household::class, [self::SEE])
|
||||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
||||||
->addCheckFor(Center::class, [self::STATS])
|
->addCheckFor(Center::class, [self::STATS])
|
||||||
->build();
|
->build();
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a list of different Query parts into a single query
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodInfoQueryBuilder
|
||||||
|
{
|
||||||
|
private const BASE_QUERY = <<<'SQL'
|
||||||
|
SELECT
|
||||||
|
{period_id_column} AS accompanyingperiod_id,
|
||||||
|
'{related_entity_column_id}' AS relatedentity,
|
||||||
|
{related_entity_id_column_id} AS relatedentityid,
|
||||||
|
{user_id} AS user_id,
|
||||||
|
{datetime} AS infodate,
|
||||||
|
'{discriminator}' AS discriminator,
|
||||||
|
{metadata} AS metadata
|
||||||
|
FROM {from_statement}
|
||||||
|
{where_statement}
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
public function buildQuery(AccompanyingPeriodInfoUnionQueryPartInterface $query): string
|
||||||
|
{
|
||||||
|
return strtr(
|
||||||
|
self::BASE_QUERY,
|
||||||
|
[
|
||||||
|
'{period_id_column}' => $query->getAccompanyingPeriodIdColumn(),
|
||||||
|
'{related_entity_column_id}' => $query->getRelatedEntityColumn(),
|
||||||
|
'{related_entity_id_column_id}' => $query->getRelatedEntityIdColumn(),
|
||||||
|
'{user_id}' => $query->getUserIdColumn(),
|
||||||
|
'{datetime}' => $query->getDateTimeColumn(),
|
||||||
|
'{discriminator}' => $query->getDiscriminator(),
|
||||||
|
'{metadata}' => $query->getMetadataColumn(),
|
||||||
|
'{from_statement}' => $query->getFromStatement(),
|
||||||
|
'{where_statement}' => '' === $query->getWhereClause() ? '' : 'WHERE '.$query->getWhereClause(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodStartQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'a.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriod::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'a.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'a.openingDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_start';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return '\'{}\'::jsonb';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period a';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEndQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'w.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWork::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'w.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapwr.user_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'w.endDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_end';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work w
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return 'w.endDate IS NOT NULL';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEvaluationDocumentUpdateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapw.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluation::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.updatedby_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.updatedAt';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_evaluation_updated_at';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return 'e.updatedAt IS NOT NULL';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEvaluationMaxQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapw.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluation::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapwr.user_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.maxDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_evaluation_start';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return 'e.maxDate IS NOT NULL';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEvaluationStartQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapw.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluation::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapwr.user_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.startDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_evaluation_start';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEvaluationUpdateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapw.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluationDocument::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'doc.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'doc.updatedby_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'doc.updatedAt';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_evaluation_document_updated_at';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work_evaluation_document doc
|
||||||
|
JOIN chill_person_accompanying_period_work_evaluation e ON doc.accompanyingperiodworkevaluation_id = e.id
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return 'doc.updatedAt IS NOT NULL';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEvaluationWarningDateQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapw.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluation::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapwr.user_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'e.maxDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_evaluation_max';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work_evaluation e
|
||||||
|
JOIN chill_person_accompanying_period_work cpapw ON cpapw.id = e.accompanyingperiodwork_id
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr ON cpapw.id = cpapwr.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return 'e.maxDate IS NOT NULL';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoQueryPart;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
|
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkStartQueryPartForAccompanyingPeriodInfo implements AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'w.accompanyingperiod_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityColumn(): string
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWork::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'w.id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string
|
||||||
|
{
|
||||||
|
return 'cpapwr.user_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string
|
||||||
|
{
|
||||||
|
return 'w.startDate';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string
|
||||||
|
{
|
||||||
|
return "'{}'::jsonb";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDiscriminator(): string
|
||||||
|
{
|
||||||
|
return 'accompanying_period_work_start';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFromStatement(): string
|
||||||
|
{
|
||||||
|
return 'chill_person_accompanying_period_work w
|
||||||
|
LEFT JOIN chill_person_accompanying_period_work_referrer cpapwr on w.id = cpapwr.accompanyingperiodwork_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWhereClause(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo;
|
||||||
|
|
||||||
|
interface AccompanyingPeriodInfoUnionQueryPartInterface
|
||||||
|
{
|
||||||
|
public function getAccompanyingPeriodIdColumn(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string
|
||||||
|
*/
|
||||||
|
public function getRelatedEntityColumn(): string;
|
||||||
|
|
||||||
|
public function getRelatedEntityIdColumn(): string;
|
||||||
|
|
||||||
|
public function getUserIdColumn(): string;
|
||||||
|
|
||||||
|
public function getDateTimeColumn(): string;
|
||||||
|
|
||||||
|
public function getDiscriminator(): string;
|
||||||
|
|
||||||
|
public function getMetadataColumn(): string;
|
||||||
|
|
||||||
|
public function getFromStatement(): string;
|
||||||
|
|
||||||
|
public function getWhereClause(): string;
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Chill\PersonBundle\Service\EntityInfo;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Service\EntityInfo\ViewEntityInfoProviderInterface;
|
||||||
|
|
||||||
|
class AccompanyingPeriodViewEntityInfoProvider implements ViewEntityInfoProviderInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
/**
|
||||||
|
* @var AccompanyingPeriodInfoUnionQueryPartInterface[]
|
||||||
|
*/
|
||||||
|
private iterable $unions,
|
||||||
|
private AccompanyingPeriodInfoQueryBuilder $builder,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewQuery(): string
|
||||||
|
{
|
||||||
|
return implode(
|
||||||
|
' UNION ',
|
||||||
|
array_map(
|
||||||
|
fn (AccompanyingPeriodInfoUnionQueryPartInterface $part) => $this->builder->buildQuery($part),
|
||||||
|
iterator_to_array($this->unions)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getViewName(): string
|
||||||
|
{
|
||||||
|
return 'view_chill_person_accompanying_period_info';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace AccompanyingPeriod\Lifecycle;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\CronJobExecution;
|
||||||
|
use Chill\PersonBundle\AccompanyingPeriod\Lifecycle\AccompanyingPeriodStepChangeCronjob;
|
||||||
|
use Chill\PersonBundle\AccompanyingPeriod\Lifecycle\AccompanyingPeriodStepChangeRequestor;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Prophecy\PhpUnit\ProphecyTrait;
|
||||||
|
use Symfony\Component\Clock\MockClock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class AccompanyingPeriodStepChangeCronjobTest extends TestCase
|
||||||
|
{
|
||||||
|
use ProphecyTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideRunTimes
|
||||||
|
*/
|
||||||
|
public function testCanRun(string $datetime, \DateTimeImmutable $lastExecutionStart, bool $canRun): void
|
||||||
|
{
|
||||||
|
$requestor = $this->prophesize(AccompanyingPeriodStepChangeRequestor::class);
|
||||||
|
$clock = new MockClock($datetime);
|
||||||
|
|
||||||
|
$cronJob = new AccompanyingPeriodStepChangeCronjob($clock, $requestor->reveal());
|
||||||
|
$cronJobExecution = (new CronJobExecution($cronJob->getKey()))->setLastStart($lastExecutionStart);
|
||||||
|
|
||||||
|
$this->assertEquals($canRun, $cronJob->canRun($cronJobExecution));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideRunTimes(): iterable
|
||||||
|
{
|
||||||
|
// can run, during the night
|
||||||
|
yield ['2023-01-15T01:00:00+02:00', new \DateTimeImmutable('2023-01-14T00:00:00+02:00'), true];
|
||||||
|
|
||||||
|
// can not run, not during the night
|
||||||
|
yield ['2023-01-15T10:00:00+02:00', new \DateTimeImmutable('2023-01-14T00:00:00+02:00'), false];
|
||||||
|
|
||||||
|
// can not run: not enough elapsed time
|
||||||
|
yield ['2023-01-15T01:00:00+02:00', new \DateTimeImmutable('2023-01-15T00:30:00+02:00'), false];
|
||||||
|
}
|
||||||
|
}
|
@@ -40,6 +40,8 @@ final class StepFilterTest extends AbstractFilterTest
|
|||||||
return [
|
return [
|
||||||
['accepted_steps' => AccompanyingPeriod::STEP_DRAFT],
|
['accepted_steps' => AccompanyingPeriod::STEP_DRAFT],
|
||||||
['accepted_steps' => AccompanyingPeriod::STEP_CONFIRMED],
|
['accepted_steps' => AccompanyingPeriod::STEP_CONFIRMED],
|
||||||
|
['accepted_steps' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG],
|
||||||
|
['accepted_steps' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT],
|
||||||
['accepted_steps' => AccompanyingPeriod::STEP_CLOSED],
|
['accepted_steps' => AccompanyingPeriod::STEP_CLOSED],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -78,17 +78,13 @@ final class SocialWorkTypeFilterTest extends AbstractFilterTest
|
|||||||
$goals = array_unique($goals);
|
$goals = array_unique($goals);
|
||||||
$results = array_unique($results);
|
$results = array_unique($results);
|
||||||
|
|
||||||
$data = [
|
return [
|
||||||
[
|
[
|
||||||
'actionType' => implode(',', $actions),
|
'actionType' => implode(',', $actions),
|
||||||
'goal' => implode(',', $goals),
|
'goal' => implode(',', $goals),
|
||||||
'result' => implode(',', $results),
|
'result' => implode(',', $results),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
/// TODO ne fonctionne pas
|
|
||||||
var_dump($data);
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getQueryBuilders(): array
|
public function getQueryBuilders(): array
|
||||||
|
@@ -18,6 +18,7 @@ use Chill\PersonBundle\Entity\Person;
|
|||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
|
||||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +40,7 @@ final class HouseholdNormalizerTest extends KernelTestCase
|
|||||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNormalizationRecursive()
|
public function testNormalizationRecursive(): void
|
||||||
{
|
{
|
||||||
$person = new Person();
|
$person = new Person();
|
||||||
$person->setFirstName('ok')->setLastName('ok');
|
$person->setFirstName('ok')->setLastName('ok');
|
||||||
@@ -67,4 +68,53 @@ final class HouseholdNormalizerTest extends KernelTestCase
|
|||||||
$this->assertArrayHasKey('type', $normalized);
|
$this->assertArrayHasKey('type', $normalized);
|
||||||
$this->assertEquals('household', $normalized['type']);
|
$this->assertEquals('household', $normalized['type']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a household have old members (members which are not "current"),
|
||||||
|
* the indexes of the household must be reset to numerical and contiguous
|
||||||
|
* indexes. This ensure that it will be mapped as a list, not as an associative
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
public function testHouseholdDocGenNormalizationWithOldMembers(): void
|
||||||
|
{
|
||||||
|
$previousPerson = new Person();
|
||||||
|
$previousPerson->setFirstName('ok')->setLastName('ok');
|
||||||
|
$this->entityManager->persist($previousPerson);
|
||||||
|
$member = new HouseholdMember();
|
||||||
|
$household = new Household();
|
||||||
|
$position = (new Position())
|
||||||
|
->setShareHousehold(true)
|
||||||
|
->setAllowHolder(true);
|
||||||
|
|
||||||
|
$member->setPerson($previousPerson)
|
||||||
|
->setStartDate(new DateTimeImmutable('1 year ago'))
|
||||||
|
->setEndDate(new DateTimeImmutable('1 month ago'))
|
||||||
|
->setPosition($position);
|
||||||
|
|
||||||
|
$household->addMember($member);
|
||||||
|
|
||||||
|
$currentPerson1 = new Person();
|
||||||
|
$currentPerson1->setFirstName('p1')->setLastName('p1');
|
||||||
|
$this->entityManager->persist($currentPerson1);
|
||||||
|
$member = new HouseholdMember();
|
||||||
|
$member->setPerson($currentPerson1)
|
||||||
|
->setStartDate(new DateTimeImmutable('1 year ago'))
|
||||||
|
->setPosition($position);
|
||||||
|
$household->addMember($member);
|
||||||
|
|
||||||
|
$normalized = $this->normalizer->normalize(
|
||||||
|
$household,
|
||||||
|
'docgen',
|
||||||
|
[
|
||||||
|
AbstractNormalizer::GROUPS => ['docgen:read'],
|
||||||
|
'docgen:expects' => Household::class,
|
||||||
|
'docgen:person:with-household' => false,
|
||||||
|
'docgen:person:with-relations' => false,
|
||||||
|
'docgen:person:with-budget' => false,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertIsArray($normalized);
|
||||||
|
self::assertArrayHasKey(0, $normalized['currentMembers']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -98,3 +98,7 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
resource: '../Workflow/'
|
resource: '../Workflow/'
|
||||||
|
|
||||||
|
Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodViewEntityInfoProvider:
|
||||||
|
arguments:
|
||||||
|
$unions: !tagged_iterator chill_person.accompanying_period_info_part
|
||||||
|
@@ -25,6 +25,11 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\PersonBundle\AccompanyingPeriod\Lifecycle\:
|
||||||
|
resource: './../../AccompanyingPeriod/Lifecycle'
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
Chill\PersonBundle\AccompanyingPeriod\Events\UserRefEventSubscriber:
|
Chill\PersonBundle\AccompanyingPeriod\Events\UserRefEventSubscriber:
|
||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
@@ -128,6 +128,13 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: chill.export_filter, alias: accompanyingcourse_creator_job_filter }
|
- { name: chill.export_filter, alias: accompanyingcourse_creator_job_filter }
|
||||||
|
|
||||||
|
Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\UserWorkingOnCourseFilter:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_filter, alias: accompanyingcourse_user_working_on_filter }
|
||||||
|
|
||||||
|
Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\HavingAnAccompanyingPeriodInfoWithinDatesFilter:
|
||||||
|
tags:
|
||||||
|
- { name: chill.export_filter, alias: accompanyingcourse_info_within_filter }
|
||||||
|
|
||||||
## Aggregators
|
## Aggregators
|
||||||
chill.person.export.aggregator_referrer_scope:
|
chill.person.export.aggregator_referrer_scope:
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user