Merge remote-tracking branch 'origin/111_exports_suite' into testing

This commit is contained in:
Julien Fastré 2022-10-17 09:16:39 +02:00
commit eb404404f9
44 changed files with 1179 additions and 208 deletions

View File

@ -17,12 +17,13 @@ These are alias conventions :
| | Scope::class | acp.scopes | acpscope |
| | SocialIssue::class | acp.socialIssues | acpsocialissue |
| | User::class | acp.user | acpuser |
| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories |
| AccompanyingPeriodWork::class | | | acpw |
| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval |
| | User::class | acpw.referrers | acpwuser |
| | SocialAction::class | acpw.socialAction | acpwsocialaction |
| | Goal::class | acpw.goals | goal |
| | Result::class | acpw.results | result |
| | Result::class | acpw.results | result |
| AccompanyingPeriodParticipation::class | | | acppart |
| | Person::class | acppart.person | partperson |
| AccompanyingPeriodWorkEvaluation::class | | | workeval |
@ -47,7 +48,7 @@ These are alias conventions :
| | HouseholdComposition::class | household.compositions | composition |
| Activity::class | | | activity |
| | Person::class | activity.person | actperson |
| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp |
| | AccompanyingPeriod::class | activity.accompanyingPeriod | acp |
| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity |
| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity |
| | ActivityType::class | activity.activityType | acttype |
@ -59,6 +60,7 @@ These are alias conventions :
| | User::class | activity.users | actusers |
| | ActivityReason::class | activity.reasons | actreasons |
| | Center::class | actperson.center | actcenter |
| | Person::class | activity.createdBy | actcreator |
| ActivityReason::class | | | actreasons |
| | ActivityReasonCategory::class | actreason.category | actreasoncat |
| Calendar::class | | | cal |

View File

@ -13,6 +13,10 @@ namespace Chill\ActivityBundle\Entity;
use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
@ -55,8 +59,12 @@ use Symfony\Component\Validator\Constraints as Assert;
* getUserFunction="getUser",
* path="scope")
*/
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCentersInterface, HasScopesInterface
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCentersInterface, HasScopesInterface, TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
public const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent';

View File

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

View File

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

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersAggregator implements AggregatorInterface
{
private UserRender $userRender;
private UserRepositoryInterface $userRepository;
public function __construct(UserRepositoryInterface $userRepository, UserRender $userRender)
{
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actusers', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.users', 'actusers');
}
$qb
->addSelect('actusers.id AS activity_users_aggregator')
->addGroupBy('activity_users_aggregator');
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add on the form
}
public function getLabels($key, array $values, $data)
{
return function ($value) {
if ('_header' === $value) {
return 'Activity users';
}
if (null === $value) {
return '';
}
$u = $this->userRepository->find($value);
return $this->userRender->renderString($u, []);
};
}
public function getQueryKeys($data)
{
return ['activity_users_aggregator'];
}
public function getTitle()
{
return 'Aggregate by activity users';
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorInterface
{
private TranslatableStringHelperInterface $translatableStringHelper;
private UserJobRepositoryInterface $userJobRepository;
public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->userJobRepository = $userJobRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actusers', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.users', 'actusers');
}
$qb
->addSelect('IDENTITY(actusers.userJob) AS activity_users_job_aggregator')
->addGroupBy('activity_users_job_aggregator');
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add in the form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Users \'s job';
}
if (null === $value) {
return '';
}
$j = $this->userJobRepository->find($value);
return $this->translatableStringHelper->localize(
$j->getLabel()
);
};
}
public function getQueryKeys($data): array
{
return ['activity_users_job_aggregator'];
}
public function getTitle()
{
return 'Aggregate by users job';
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\AggregatorInterface
{
private ScopeRepositoryInterface $scopeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actusers', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.users', 'actusers');
}
$qb
->addSelect('IDENTITY(actusers.mainScope) AS activity_users_main_scope_aggregator')
->addGroupBy('activity_users_main_scope_aggregator');
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add in the form
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Users \'s scope';
}
if (null === $value) {
return '';
}
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize(
$s->getName()
);
};
}
public function getQueryKeys($data): array
{
return ['activity_users_main_scope_aggregator'];
}
public function getTitle()
{
return 'Aggregate by users scope';
}
}

View File

@ -15,12 +15,10 @@ use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByUserFilter implements FilterInterface
class ByCreatorFilter implements FilterInterface
{
private UserRender $userRender;
@ -36,22 +34,11 @@ class ByUserFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
if (!in_array('actusers', $qb->getAllAliases(), true)) {
$qb->join('activity.users', 'actusers');
}
$clause = $qb->expr()->in('actusers.id', ':users');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('users', $data['accepted_users']);
$qb
->andWhere(
$qb->expr()->in('activity.createdBy', ':users')
)
->setParameter('users', $data['accepted_users']);
}
public function applyOn(): string
@ -74,13 +61,13 @@ class ByUserFilter implements FilterInterface
$users[] = $this->userRender->renderString($u, []);
}
return ['Filtered activity by linked users: only %users%', [
return ['Filtered activity by creator: only %users%', [
'%users%' => implode(', ou ', $users),
]];
}
public function getTitle(): string
{
return 'Filter activity by linked users';
return 'Filter activity by creator';
}
}

View File

@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ActivityUsersFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
{
$this->userRender = $userRender;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$orX = $qb->expr()->orX();
foreach ($data['accepted_users'] as $key => $user) {
$orX->add($qb->expr()->isMemberOf(':activity_users_filter_u' . $key, 'activity.users'));
$qb->setParameter('activity_users_filter_u' . $key, $user);
}
$qb->andWhere($orX);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_users', PickUserDynamicType::class, [
'multiple' => true,
'label' => 'Users',
]);
}
public function describeAction($data, $format = 'string')
{
$users = [];
foreach ($data['accepted_users'] as $u) {
$users[] = $this->userRender->renderString($u, []);
}
return ['Filtered activity by users: only %users%', [
'%users%' => implode(', ', $users),
]];
}
public function getTitle(): string
{
return 'Filter activity by users';
}
}

View File

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

View File

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

View File

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

View File

@ -55,6 +55,10 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_date_filter' }
Chill\ActivityBundle\Export\Filter\ActivityUsersFilter:
tags:
- { name: chill.export_filter, alias: 'activity_users_filter' }
chill.activity.export.reason_filter:
class: Chill\ActivityBundle\Export\Filter\PersonFilters\ActivityReasonFilter
tags:
@ -77,10 +81,9 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_locationtype_filter' }
chill.activity.export.byuser_filter: # TMS (M2M)
class: Chill\ActivityBundle\Export\Filter\ACPFilters\ByUserFilter
Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter:
tags:
- { name: chill.export_filter, alias: 'activity_byuser_filter' }
- { name: chill.export_filter, alias: 'activity_bycreator_filter' }
chill.activity.export.emergency_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter
@ -138,10 +141,9 @@ services:
tags:
- { name: chill.export_aggregator, alias: activity_date_aggregator }
chill.activity.export.byuser_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByUserAggregator
Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_byuser_aggregator }
- { name: chill.export_aggregator, alias: activity_by_creator_aggregator }
chill.activity.export.bythirdparty_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator
@ -158,7 +160,18 @@ services:
tags:
- { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator }
chill.activity.export.userscope_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\UserScopeAggregator
Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_userscope_aggregator }
- { name: chill.export_aggregator, alias: activity_creator_scope_aggregator }
Chill\ActivityBundle\Export\Aggregator\ActivityUsersAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_users_aggregator }
Chill\ActivityBundle\Export\Aggregator\ActivityUsersScopeAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_users_scope_aggregator }
Chill\ActivityBundle\Export\Aggregator\ActivityUsersJobAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_users_job_aggregator }

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Activity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221014130554 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE activity DROP updatedAt');
$this->addSql('ALTER TABLE activity DROP createdAt');
$this->addSql('ALTER TABLE activity DROP updatedBy_id');
$this->addSql('ALTER TABLE activity DROP createdBy_id');
// rename some indexes on activity
$this->addSql('ALTER INDEX idx_ac74095a217bbb47 RENAME TO idx_55026b0c217bbb47');
$this->addSql('ALTER INDEX idx_ac74095a682b5931 RENAME TO idx_55026b0c682b5931');
$this->addSql('ALTER INDEX idx_ac74095aa76ed395 RENAME TO idx_55026b0ca76ed395');
$this->addSql('ALTER INDEX idx_ac74095ac54c8c93 RENAME TO idx_55026b0cc54c8c93');
}
public function getDescription(): string
{
return 'Track update and create on activity';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE activity ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE activity ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE activity ADD updatedBy_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE activity ADD createdBy_id INT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN activity.updatedAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN activity.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE activity ADD CONSTRAINT FK_AC74095A3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_AC74095A65FF1AEC ON activity (updatedBy_id)');
$this->addSql('CREATE INDEX IDX_AC74095A3174800F ON activity (createdBy_id)');
// rename some indexes on activity
$this->addSql('ALTER INDEX idx_55026b0cc54c8c93 RENAME TO IDX_AC74095AC54C8C93');
$this->addSql('ALTER INDEX idx_55026b0c217bbb47 RENAME TO IDX_AC74095A217BBB47');
$this->addSql('ALTER INDEX idx_55026b0c682b5931 RENAME TO IDX_AC74095A682B5931');
$this->addSql('ALTER INDEX idx_55026b0ca76ed395 RENAME TO IDX_AC74095AA76ED395');
$this->addSql('UPDATE activity SET updatedBy_id=user_id, createdBy_id=user_id, createdAt="date", updatedAt="date"');
}
}

View File

@ -252,8 +252,6 @@ Filter by activity type: Filtrer les activités par type
Filter activity by locationtype: Filtrer les activités par type de localisation
'Filtered activity by locationtype: only %types%': "Filtré par type de localisation: uniquement %types%"
Accepted locationtype: Types de localisation
Filter activity by linked users: Filtrer les activités par TMS
'Filtered activity by linked users: only %users%': "Filtré par TMS: uniquement %users%"
Accepted users: TMS(s)
Filter activity by emergency: Filtrer les activités par urgence
'Filtered activity by emergency: only %emergency%': "Filtré par urgence: uniquement si %emergency%"
@ -269,7 +267,11 @@ Filter activity by linked socialaction: Filtrer les activités par action liée
Filter activity by linked socialissue: Filtrer les activités par problématique liée
'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%"
Filter activity by user: Filtrer les activités par créateur
'Filtered activity by user: only %users%': "Filtré par créateur: uniquement %users%"
Filter activity by users: Filtrer les activités par utilisateur participant
Filter activity by creator: Filtrer les activités par créateur de l'échange
'Filtered activity by user: only %users%': "Filtré par référent: uniquement %users%"
'Filtered activity by users: only %users%': "Filtré par utilisateurs participants: uniquement %users%"
'Filtered activity by creator: only %users%': "Filtré par créateur: uniquement %users%"
Creators: Créateurs
Filter activity by userscope: Filtrer les activités par service du créateur
'Filtered activity by userscope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%"
@ -282,9 +284,14 @@ By reason: Par sujet
By category of reason: Par catégorie de sujet
Reason's level: Niveau du sujet
Group by reasons: Sujet d'activité
Aggregate by activity user: Grouper les activités par utilisateur
Aggregate by activity user: Grouper les activités par référent
Aggregate by activity users: Grouper les activités par utilisateurs participants
Aggregate by activity type: Grouper les activités par type
Aggregate by activity reason: Grouper les activités par sujet
Aggregate by users scope: Grouper les activités par service principal de l'utilisateur
Users 's scope: Service principal des utilisateurs participants à l'activité
Aggregate by users job: Grouper les activités par métier des utilisateurs participants
Users 's job: Métier des utilisateurs participants à l'activité
Group activity by locationtype: Grouper les activités par type de localisation
Group activity by date: Grouper les activités par date
@ -294,7 +301,8 @@ by week: Par semaine
for week: Semaine
by year: Par année
in year: En
Group activity by linked users: Grouper les activités par TMS impliqué
Group activity by creator: Grouper les activités par créateur de l'échange
Group activity by creator scope: Grouper les activités par service du créateur de l'échange
Group activity by linked thirdparties: Grouper les activités par tiers impliqué
Accepted thirdparty: Tiers impliqué
Group activity by linked socialaction: Grouper les activités par action liée

View File

@ -1,14 +1,14 @@
<?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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\User;

View File

@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -39,9 +40,11 @@ class SocialWorkEvaluationApiController extends AbstractController
*/
public function listEvaluationBySocialAction(SocialAction $action): Response
{
$pagination = $this->paginatorFactory->create($action->getEvaluations()->count());
$evaluations = $action->getEvaluations()->filter(static fn (Evaluation $eval) => $eval->isActive());
$evaluations = $action->getEvaluations()->slice(
$pagination = $this->paginatorFactory->create($evaluations->count());
$evaluations = $evaluations->slice(
$pagination->getCurrentPageFirstItemNumber(),
$pagination->getItemsPerPage()
);

View File

@ -22,6 +22,7 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodLocationHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Comment;
@ -341,6 +342,12 @@ class AccompanyingPeriod implements
*/
private string $step = self::STEP_DRAFT;
/**
* @ORM\OneToMany(targetEntity=AccompanyingPeriodStepHistory::class,
* mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private Collection $stepHistories;
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
*/
@ -395,7 +402,6 @@ class AccompanyingPeriod implements
*/
public function __construct(?DateTime $dateOpening = null)
{
$this->setOpeningDate($dateOpening ?? new DateTime('now'));
$this->calendars = new ArrayCollection();
$this->participations = new ArrayCollection();
$this->scopes = new ArrayCollection();
@ -405,6 +411,8 @@ class AccompanyingPeriod implements
$this->resources = new ArrayCollection();
$this->userHistories = new ArrayCollection();
$this->locationHistories = new ArrayCollection();
$this->stepHistories = new ArrayCollection();
$this->setOpeningDate($dateOpening ?? new DateTime('now'));
}
/**
@ -995,6 +1003,11 @@ class AccompanyingPeriod implements
return $this->step;
}
public function getStepHistories(): Collection
{
return $this->stepHistories;
}
public function getUser(): ?User
{
return $this->user;
@ -1263,7 +1276,11 @@ class AccompanyingPeriod implements
*/
public function setOpeningDate($openingDate)
{
$this->openingDate = $openingDate;
if ($this->openingDate !== $openingDate) {
$this->openingDate = $openingDate;
$this->ensureStepContinuity();
}
return $this;
}
@ -1362,6 +1379,14 @@ class AccompanyingPeriod implements
$this->bootPeriod();
}
if (self::STEP_DRAFT !== $this->step && $previous !== $step) {
// we create a new history
$history = new AccompanyingPeriodStepHistory();
$history->setStep($this->step)->setStartDate(new DateTimeImmutable('now'));
$this->addStepHistory($history);
}
return $this;
}
@ -1402,6 +1427,17 @@ class AccompanyingPeriod implements
return $this;
}
private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory): self
{
if (!$this->stepHistories->contains($stepHistory)) {
$this->stepHistories[] = $stepHistory;
$stepHistory->setPeriod($this);
$this->ensureStepContinuity();
}
return $this;
}
private function bootPeriod(): void
{
// first location history
@ -1413,6 +1449,43 @@ class AccompanyingPeriod implements
$this->addLocationHistory($locationHistory);
}
private function ensureStepContinuity(): void
{
// ensure continuity of histories
$criteria = new Criteria();
$criteria->orderBy(['startDate' => Criteria::ASC, 'id' => Criteria::ASC]);
/** @var Iterator $steps */
$steps = $this->getStepHistories()->matching($criteria)->getIterator();
$steps->rewind();
// we set the start date of the first step as the opening date, only if it is
// not greater than the end date
/** @var AccompanyingPeriodStepHistory $current */
$current = $steps->current();
if (null === $current) {
return;
}
if ($this->getOpeningDate()->format('Y-m-d') !== $current->getStartDate()->format('Y-m-d')
&& ($this->getOpeningDate() <= $current->getEndDate() || null === $current->getEndDate())) {
$current->setStartDate(DateTimeImmutable::createFromMutable($this->getOpeningDate()));
}
// then we set all the end date to the start date of the next one
do {
/** @var AccompanyingPeriodStepHistory $current */
$current = $steps->current();
$steps->next();
if ($steps->valid()) {
$next = $steps->current();
$current->setEndDate($next->getStartDate());
}
} while ($steps->valid());
}
private function setRequestorPerson(?Person $requestorPerson = null): self
{
$this->requestorPerson = $requestorPerson;

View File

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

View File

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

View File

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

View File

@ -12,15 +12,20 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
final class ReferrerAggregator implements AggregatorInterface
{
private const A = 'acp_ref_agg_uhistory';
private const P = 'acp_ref_agg_date';
private UserRender $userRender;
private UserRepository $userRepository;
@ -40,12 +45,23 @@ final class ReferrerAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acpuser', $qb->getAllAliases(), true)) {
$qb->leftJoin('acp.user', 'acpuser');
}
$qb->addSelect('acpuser.id AS referrer_aggregator');
$qb->addGroupBy('referrer_aggregator');
$qb
->addSelect('IDENTITY(' . self::A . '.user) AS referrer_aggregator')
->addGroupBy('referrer_aggregator')
->leftJoin('acp.userHistories', self::A)
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull(self::A),
$qb->expr()->andX(
$qb->expr()->lte(self::A . '.startDate', ':' . self::P),
$qb->expr()->orX(
$qb->expr()->isNull(self::A . '.endDate'),
$qb->expr()->gt(self::A . '.endDate', ':' . self::P)
)
)
)
)
->setParameter(self::P, $data['date_calc']);
}
public function applyOn(): string
@ -55,7 +71,13 @@ final class ReferrerAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
// no form
$builder
->add('date_calc', ChillDateType::class, [
'input' => 'datetime_immutable',
'data' => new DateTimeImmutable('now'),
'label' => 'export.aggregator.course.by_referrer.Computation date for referrer',
'required' => true,
]);
}
public function getLabels($key, array $values, $data)

View File

@ -12,20 +12,21 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
//use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
final class StepAggregator implements AggregatorInterface //, FilterInterface
final class StepAggregator implements AggregatorInterface
{
private const A = 'acpstephistories';
private const P = 'acp_step_agg_date';
private TranslatorInterface $translator;
public function __construct(
@ -41,30 +42,26 @@ final class StepAggregator implements AggregatorInterface //, FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->addSelect('acp.step AS step_aggregator');
$qb->addGroupBy('step_aggregator');
/*
// add date in where clause
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->andX(
$qb->expr()->lte('acp.openingDate', ':ondate'),
$qb->expr()->orX(
$qb->expr()->gt('acp.closingDate', ':ondate'),
$qb->expr()->isNull('acp.closingDate')
)
);
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
if (!in_array(self::A, $qb->getAllAliases(), true)) {
$qb->leftJoin('acp.stepHistories', self::A);
}
$qb->add('where', $where);
$qb->setParameter('ondate', $data['on_date'], Types::DATE_MUTABLE);
*/
$qb
->addSelect(self::A . '.step AS step_aggregator')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull(self::A . '.step'),
$qb->expr()->andX(
$qb->expr()->lte(self::A . '.startDate', ':' . self::P),
$qb->expr()->orX(
$qb->expr()->isNull(self::A . '.endDate'),
$qb->expr()->lt(self::A . '.endDate', ':' . self::P)
)
)
)
)
->setParameter(self::P, $data['on_date'])
->addGroupBy('step_aggregator');
}
public function applyOn(): string
@ -95,8 +92,11 @@ final class StepAggregator implements AggregatorInterface //, FilterInterface
case '_header':
return 'Step';
case null:
return '';
default:
throw new LogicException(sprintf('The value %s is not valid', $value));
return $value;
}
};
}

View File

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

View File

@ -99,6 +99,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
$qb = $this->repository->createQueryBuilder('acp');
$qb
->andWhere('acp.step != :count_acp_step')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
@ -107,6 +108,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
'
)
)
->setParameter('count_acp_step', AccompanyingPeriod::STEP_DRAFT)
->setParameter('authorized_centers', $centers);
$qb->select('COUNT(DISTINCT acp.id) AS export_result');

View File

@ -15,7 +15,7 @@ use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Andx;
use Chill\PersonBundle\Repository\SocialWork\EvaluationRepositoryInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
@ -23,11 +23,15 @@ use function in_array;
class EvaluationFilter implements FilterInterface
{
private EvaluationRepositoryInterface $evaluationRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
EvaluationRepositoryInterface $evaluationRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->evaluationRepository = $evaluationRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
@ -50,16 +54,8 @@ class EvaluationFilter implements FilterInterface
$qb->join('workeval.evaluation', 'eval');
}
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('eval.id', ':evaluations');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->andWhere($clause);
$qb->setParameter('evaluations', $data['accepted_evaluations']);
}
@ -72,11 +68,13 @@ class EvaluationFilter implements FilterInterface
{
$builder->add('accepted_evaluations', EntityType::class, [
'class' => Evaluation::class,
'choices' => $this->evaluationRepository->findAllActive(),
'choice_label' => function (Evaluation $ev) {
return $this->translatableStringHelper->localize($ev->getTitle());
},
'multiple' => true,
'expanded' => true,
'expanded' => false,
'attr' => ['class' => 'select2'],
]);
}

View File

@ -11,17 +11,23 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Andx;
use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class ReferrerFilter implements FilterInterface
{
private const A = 'acp_referrer_filter_uhistory';
private const P = 'acp_referrer_filter_date';
private const PU = 'acp_referrer_filter_users';
private UserRender $userRender;
public function __construct(UserRender $userRender)
@ -36,17 +42,22 @@ class ReferrerFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('acp.user', ':referrers');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('referrers', $data['accepted_referrers']);
$qb
->join('acp.userHistories', self::A)
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte(self::A . '.startDate', ':' . self::P),
$qb->expr()->orX(
$qb->expr()->isNull(self::A . '.endDate'),
$qb->expr()->gt(self::A . '.endDate', ':' . self::P)
)
)
)
->andWhere(
$qb->expr()->in(self::A . '.user', ':' . self::PU)
)
->setParameter(self::PU, $data['accepted_referrers'])
->setParameter(self::P, $data['date_calc']);
}
public function applyOn(): string
@ -56,14 +67,16 @@ class ReferrerFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_referrers', EntityType::class, [
'class' => User::class,
'choice_label' => function (User $u) {
return $this->userRender->renderString($u, []);
},
'multiple' => true,
'expanded' => true,
]);
$builder
->add('accepted_referrers', PickUserDynamicType::class, [
'multiple' => true,
])
->add('date_calc', ChillDateType::class, [
'input' => 'datetime_immutable',
'data' => new DateTimeImmutable('now'),
'label' => 'export.filter.course.by_referrer.Computation date for referrer',
'required' => true,
]);
}
public function describeAction($data, $format = 'string'): array

View File

@ -14,9 +14,11 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
@ -24,6 +26,14 @@ use Symfony\Component\Security\Core\Security;
class UserJobFilter implements FilterInterface
{
private const A = 'acp_ujob_filter_uhistory';
private const AU = 'acp_ujob_filter_uhistory_user';
private const P = 'acp_ujob_filter_date';
private const PJ = 'acp_ujob_filter_job';
private Security $security;
private TranslatableStringHelper $translatableStringHelper;
@ -48,10 +58,22 @@ class UserJobFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->join('acp.userHistories', self::A)
->andWhere(
$qb->expr()->in('acp.job', ':acp_user_job_filter_j')
$qb->expr()->andX(
$qb->expr()->lte(self::A . '.startDate', ':' . self::P),
$qb->expr()->orX(
$qb->expr()->isNull(self::A . '.endDate'),
$qb->expr()->gt(self::A . '.endDate', ':' . self::P)
)
)
)
->setParameter('acp_user_job_filter_j', $data['jobs']);
->setParameter(self::P, $data['date_calc'])
->join(self::A . '.user', self::AU)
->andWhere(
$qb->expr()->in(self::AU . '.userJob', ':' . self::PJ)
)
->setParameter(self::PJ, $data['jobs']);
}
public function applyOn()
@ -61,14 +83,21 @@ class UserJobFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('jobs', EntityType::class, [
'class' => UserJob::class,
'choices' => $this->userJobRepository->findAllActive(),
'multiple' => true,
'expanded' => true,
'choice_label' => fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
'label' => 'Job',
]);
$builder
->add('jobs', EntityType::class, [
'class' => UserJob::class,
'choices' => $this->userJobRepository->findAllActive(),
'multiple' => true,
'expanded' => true,
'choice_label' => fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
'label' => 'Job',
])
->add('date_calc', ChillDateType::class, [
'input' => 'datetime_immutable',
'data' => new DateTimeImmutable('now'),
'label' => 'export.filter.course.by_user_scope.Computation date for referrer',
'required' => true,
]);
}
public function describeAction($data, $format = 'string')

View File

@ -14,9 +14,11 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
@ -24,6 +26,14 @@ use Symfony\Component\Security\Core\Security;
class UserScopeFilter implements FilterInterface
{
private const A = 'acp_uscope_filter_uhistory';
private const AU = 'acp_uscope_filter_uhistory_user';
private const P = 'acp_uscope_filter_date';
private const PS = 'acp_uscope_filter_scopes';
private ScopeRepositoryInterface $scopeRepository;
private Security $security;
@ -47,14 +57,23 @@ class UserScopeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
foreach ($data['scopes'] as $key => $scope) {
/** @var Scope $scope */
$qb
->andWhere(
$qb->expr()->isMemberOf(':acp_scope_filter_s_' . $key, 'acp.scopes')
$qb
->join('acp.userHistories', self::A)
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte(self::A . '.startDate', ':' . self::P),
$qb->expr()->orX(
$qb->expr()->isNull(self::A . '.endDate'),
$qb->expr()->gt(self::A . '.endDate', ':' . self::P)
)
)
->setParameter('acp_scope_filter_s_' . $key, $scope);
}
)
->setParameter(self::P, $data['date_calc'])
->join(self::A . '.user', self::AU)
->andWhere(
$qb->expr()->in(self::AU . '.mainScope', ':' . self::PS)
)
->setParameter(self::PS, $data['scopes']);
}
public function applyOn()
@ -64,13 +83,20 @@ class UserScopeFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('scopes', EntityType::class, [
'class' => Scope::class,
'choices' => $this->scopeRepository->findAllActive(),
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
'multiple' => true,
'expanded' => true,
]);
$builder
->add('scopes', EntityType::class, [
'class' => Scope::class,
'choices' => $this->scopeRepository->findAllActive(),
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
'multiple' => true,
'expanded' => true,
])
->add('date_calc', ChillDateType::class, [
'input' => 'datetime_immutable',
'data' => new DateTimeImmutable('now'),
'label' => 'export.filter.course.by_user_scope.Computation date for referrer',
'required' => true,
]);
}
public function describeAction($data, $format = 'string')

View File

@ -35,7 +35,7 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface
$where = $qb->getDQLPart('where');
$min = null !== $data['min_age'] ? $data['min_age'] : 0;
$max = null !== $data['max_age'] ? $data['max_age'] : 3000;
$max = null !== $data['max_age'] ? $data['max_age'] : 150;
$calc = $data['date_calc'];
$minDate = $calc->sub(new DateInterval('P' . $max . 'Y'));

View File

@ -12,12 +12,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\MaritalStatus;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\ORM\Query\Expr\Andx;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class MaritalStatusFilter implements FilterInterface
@ -37,25 +34,10 @@ class MaritalStatusFilter implements FilterInterface
public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->andX(
$qb->expr()->in('person.maritalStatus', ':maritalStatus'),
$qb->expr()->orX(
$qb->expr()->eq('person.maritalStatusDate', ':calc_date'),
$qb->expr()->isNull('person.maritalStatusDate')
)
$qb->andWhere(
$qb->expr()->in('person.maritalStatus', ':maritalStatus')
);
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('maritalStatus', $data['maritalStatus']);
$qb->setParameter('calc_date', $data['calc_date']);
}
public function applyOn()
@ -75,11 +57,6 @@ class MaritalStatusFilter implements FilterInterface
'multiple' => true,
'expanded' => true,
]);
$builder->add('calc_date', ChillDateType::class, [
'label' => 'Marital status at this time',
'data' => new DateTime(),
]);
}
public function describeAction($data, $format = 'string')

View File

@ -16,6 +16,7 @@ use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@ -52,6 +53,14 @@ class EvaluationType extends AbstractType
->add('notificationDelay', DateIntervalType::class, [
'label' => 'evaluation.notificationDelay',
'required' => false,
])
->add('active', ChoiceType::class, [
'label' => 'active',
'choices' => [
'active' => true,
'inactive' => false,
],
'required' => true,
]);
}

View File

@ -14,9 +14,8 @@ namespace Chill\PersonBundle\Repository\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
final class EvaluationRepository implements ObjectRepository
final class EvaluationRepository implements EvaluationRepositoryInterface
{
private EntityRepository $repository;
@ -38,6 +37,11 @@ final class EvaluationRepository implements ObjectRepository
return $this->repository->findAll();
}
public function findAllActive(): array
{
return $this->findBy(['active' => true]);
}
/**
* @param mixed|null $limit
* @param mixed|null $offset

View File

@ -0,0 +1,45 @@
<?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\SocialWork;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Doctrine\Persistence\ObjectRepository;
interface EvaluationRepositoryInterface extends ObjectRepository
{
public function find($id, ?int $lockMode = null, ?int $lockVersion = null): ?Evaluation;
/**
* @return array<int, Evaluation>
*/
public function findAll(): array;
/**
* @return array<int, Evaluation>
*/
public function findAllActive(): array;
/**
* @param mixed|null $limit
* @param mixed|null $offset
*
* @return array<int, Evaluation>
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
public function findOneBy(array $criteria, ?array $orderBy = null): ?Evaluation;
/**
* @return class-string
*/
public function getClassName(): string;
}

View File

@ -63,13 +63,9 @@ final class GoalRepository implements ObjectRepository
}
/**
* @param mixed|null $orderBy
* @param mixed|null $limit
* @param mixed|null $offset
*
* @return Goal[]
*/
public function findBySocialActionWithDescendants(SocialAction $action, $orderBy = null, $limit = null, $offset = null): array
public function findBySocialActionWithDescendants(SocialAction $action, array $orderBy = [], ?int $limit = null, ?int $offset = null): array
{
$qb = $this->buildQueryBySocialActionWithDescendants($action);
$qb->select('g');

View File

@ -96,13 +96,9 @@ final class ResultRepository implements ObjectRepository
}
/**
* @param mixed|null $orderBy
* @param mixed|null $limit
* @param mixed|null $offset
*
* @return Result[]
*/
public function findBySocialActionWithDescendants(SocialAction $action, $orderBy = null, $limit = null, $offset = null): array
public function findBySocialActionWithDescendants(SocialAction $action, array $orderBy = [], ?int $limit = null, ?int $offset = null): array
{
$qb = $this->buildQueryBySocialActionWithDescendants($action);
$qb->select('r');

View File

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Controller;
use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @internal
* @coversNothing
*/
final class SocialWorkEvaluationApiControllerTest extends WebTestCase
{
use PrepareClientTrait;
private EntityManagerInterface $em;
private ?Evaluation $evaluationToReset = null;
protected function tearDown(): void
{
if (null === $this->evaluationToReset) {
return;
}
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$evaluation = $em->find(Evaluation::class, $this->evaluationToReset->getId());
$evaluation->setActive(true);
$em->flush();
}
public function dataGenerateSocialActionWithEvaluations(): iterable
{
self::bootKernel();
$this->em = self::$container->get(EntityManagerInterface::class);
/** @var SocialAction $socialAction */
$socialAction = $this->em->createQuery(
'SELECT s FROM ' . SocialAction::class . ' s WHERE SIZE(s.evaluations) >= 2'
)
->setMaxResults(1)
->getSingleResult();
// set the first evaluation as inactive and save
$this->evaluationToReset = $socialAction->getEvaluations()->first();
$this->evaluationToReset->setActive(false);
$this->em->flush();
yield [$socialAction, $this->evaluationToReset];
}
/**
* @dataProvider dataGenerateSocialActionWithEvaluations
*/
public function testListEvaluationBySocialAction(SocialAction $action, Evaluation $inactiveEvaluation): void
{
$client = $this->getClientAuthenticated();
$client->request('GET', sprintf('/api/1.0/person/social-work/evaluation/by-social-action/%d.json', $action->getId()));
$this->assertResponseIsSuccessful();
$content = json_decode($client->getResponse()->getContent(), true);
$ids = array_map(static fn (array $item) => $item['id'], $content['results']);
$this->assertNotContains($inactiveEvaluation->getId(), $ids);
}
}

View File

@ -29,6 +29,38 @@ use function count;
*/
final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
{
public function testChangeStepKeepHistory()
{
$period = new AccompanyingPeriod();
$this->assertCount(0, $period->getStepHistories(), 'at initialization, period should not have any step history');
$period->setStep(AccompanyingPeriod::STEP_DRAFT);
$this->assertCount(0, $period->getStepHistories(), 're applying a draft should not create a history');
$period->setStep(AccompanyingPeriod::STEP_CONFIRMED);
$this->assertCount(1, $period->getStepHistories());
$this->assertEquals(AccompanyingPeriod::STEP_CONFIRMED, $period->getStepHistories()->first()->getStep());
$period->setOpeningDate($aMonthAgo = new DateTime('1 month ago'));
$this->assertCount(1, $period->getStepHistories());
$this->assertEquals($aMonthAgo, $period->getStepHistories()->first()->getStartDate(), 'when changing the opening date, the start date of the first history should change');
$period->setOpeningDate($tenDaysAgo = new DateTime('10 days ago'));
$this->assertCount(1, $period->getStepHistories());
$this->assertEquals($tenDaysAgo, $period->getStepHistories()->first()->getStartDate(), 'when changing the opening date, the start date of the first history should change');
$period->setStep(AccompanyingPeriod::STEP_CLOSED);
$this->assertCount(2, $period->getStepHistories());
$period->setOpeningDate($tomorrow = new DateTime('tomorrow'));
$this->assertEquals($tenDaysAgo, $period->getStepHistories()->first()->getStartDate(), 'when changing the opening date to a later one and no history after, start date should change');
}
public function testClosingEqualOpening()
{
$datetime = new DateTime('now');

View File

@ -39,7 +39,9 @@ final class DurationAggregatorTest extends AbstractAggregatorTest
public function getFormData(): array
{
return [
[],
['precision' => 'day'],
['precision' => 'week'],
['precision' => 'month'],
];
}

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregato
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\GeographicalUnitStatAggregator;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
/**
@ -38,8 +39,9 @@ final class GeographicalUnitStatAggregatorTest extends AbstractAggregatorTest
public function getFormData(): array
{
// TODO: add geographical unit stat into fixtures and provide a level
return [
[],
['date_calc' => new DateTimeImmutable('today'), 'level' => null],
];
}

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregato
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ReferrerAggregator;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
/**
@ -39,7 +40,7 @@ final class ReferrerAggregatorTest extends AbstractAggregatorTest
public function getFormData(): array
{
return [
[],
['date_calc' => new DateTimeImmutable('now')],
];
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221013131221 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_social_work_evaluation DROP active');
}
public function getDescription(): string
{
return 'Add an active column on evaluation';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_social_work_evaluation ADD active BOOLEAN DEFAULT true NOT NULL');
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20221014115500 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('DROP SEQUENCE chill_person_accompanying_period_step_history_id_seq CASCADE');
$this->addSql('DROP TABLE chill_person_accompanying_period_step_history');
}
public function getDescription(): string
{
return 'Add step history on accompanying periods';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE chill_person_accompanying_period_step_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_person_accompanying_period_step_history (id INT NOT NULL, period_id INT DEFAULT NULL,
endDate DATE DEFAULT NULL, startDate DATE NOT NULL, step TEXT NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdBy_id INT DEFAULT NULL,
updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))
');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CHECK (startDate <= endDate)');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT ' .
'chill_internal_acp_steps_not_overlaps EXCLUDE USING GIST(
-- extension btree_gist required to include comparaison with integer
period_id WITH =,
daterange(startDate, endDate, \'[)\') WITH &&
)
INITIALLY DEFERRED');
$this->addSql('CREATE INDEX IDX_84D514ACEC8B7ADE ON chill_person_accompanying_period_step_history (period_id)');
$this->addSql('CREATE INDEX IDX_84D514AC3174800F ON chill_person_accompanying_period_step_history (createdBy_id)');
$this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_step_history.endDate IS \'(DC2Type:date_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_step_history.startDate IS \'(DC2Type:date_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_step_history.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_step_history.updatedAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT FK_84D514ACEC8B7ADE FOREIGN KEY (period_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT FK_84D514AC3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_84D514AC65FF1AEC ON chill_person_accompanying_period_step_history (updatedBy_id)');
// fill the tables with current state
$this->addSql(
'INSERT INTO chill_person_accompanying_period_step_history (id, period_id, startDate, endDate, step, createdAt, updatedAt)
SELECT nextval(\'chill_person_accompanying_period_step_history_id_seq\'), id, openingDate, null, step, NOW(), NOW() FROM chill_person_accompanying_period WHERE step = \'CONFIRMED\'
UNION
SELECT nextval(\'chill_person_accompanying_period_step_history_id_seq\'), id, openingDate, closingDate, \'CONFIRMED\', NOW(), NOW() FROM chill_person_accompanying_period WHERE step = \'CLOSED\'
UNION
SELECT nextval(\'chill_person_accompanying_period_step_history_id_seq\'), id, closingDate, null, \'CLOSED\', NOW(), NOW() FROM chill_person_accompanying_period WHERE step = \'CLOSED\'
'
);
}
}

View File

@ -947,6 +947,8 @@ notification:
export:
aggregator:
course:
by_referrer:
Computation date for referrer: Date à laquelle le référent était actif
by_user_scope:
Group course by referrer's scope: Grouper les parcours par service du référent
Computation date for referrer: Date à laquelle le référent était actif
@ -956,3 +958,10 @@ export:
week: Durée du parcours en semaines
month: Durée du parcours en mois
Precision: Unité de la durée
filter:
course:
by_user_scope:
Computation date for referrer: Date à laquelle le référent était actif
by_referrer:
Computation date for referrer: Date à laquelle le référent était actif