Merge branch '244-record-closing-motive-on-acc-period-closing' into 'master'

[Export] Ajout de la comptabilisation du nombre de changements de statuts du parcours

Closes #244

See merge request Chill-Projet/chill-bundles!650
This commit is contained in:
Julien Fastré 2024-02-07 15:09:09 +00:00
commit 8be9fb6553
24 changed files with 1206 additions and 78 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: 'Add capability to generate export about change of steps of accompanying period, and generate exports for this'
time: 2024-01-29T13:33:19.190365565+01:00
custom:
Issue: "244"

View File

@ -6,7 +6,9 @@ Add condition with distinct alias on each export join clauses (Indicators + Filt
These are alias conventions : These are alias conventions :
| Entity | Join | Attribute | Alias | | Entity | Join | Attribute | Alias |
|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:---------------------------------------| |:----------------------------------------|:----------------------------------------|:-------------------------------------------|:-------------------------------------------|
| AccompanyingPeriodStepHistory::class | | | acpstephistory (contexte ACP_STEP_HISTORY) |
| | AccompanyingPeriod::class | acpstephistory.period | acp |
| AccompanyingPeriod::class | | | acp | | AccompanyingPeriod::class | | | acp |
| | AccompanyingPeriodWork::class | acp.works | acpw | | | AccompanyingPeriodWork::class | acp.works | acpw |
| | AccompanyingPeriodParticipation::class | acp.participations | acppart | | | AccompanyingPeriodParticipation::class | acp.participations | acppart |

View File

@ -62,7 +62,7 @@ class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle\Contr
$workflow = $this->registry->get($accompanyingCourse); $workflow = $this->registry->get($accompanyingCourse);
if ($workflow->can($accompanyingCourse, 'close')) { if ($workflow->can($accompanyingCourse, 'close')) {
$workflow->apply($accompanyingCourse, 'close'); $workflow->apply($accompanyingCourse, 'close', ['closing_motive' => $form['closingMotive']->getData()]);
$em->flush(); $em->flush();

View File

@ -98,6 +98,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$loader->load('services/exports_accompanying_course.yaml'); $loader->load('services/exports_accompanying_course.yaml');
$loader->load('services/exports_social_actions.yaml'); $loader->load('services/exports_social_actions.yaml');
$loader->load('services/exports_evaluation.yaml'); $loader->load('services/exports_evaluation.yaml');
$loader->load('services/exports_accompanying_period_step_history.yaml');
} }
$loader->load('services/exports_household.yaml'); $loader->load('services/exports_household.yaml');

View File

@ -1449,7 +1449,7 @@ class AccompanyingPeriod implements
return $this; return $this;
} }
public function setStep(string $step): self public function setStep(string $step, array $context = []): self
{ {
$previous = $this->step; $previous = $this->step;
@ -1464,7 +1464,7 @@ class AccompanyingPeriod implements
$history = new AccompanyingPeriodStepHistory(); $history = new AccompanyingPeriodStepHistory();
$history->setStep($this->step)->setStartDate(new \DateTimeImmutable('now')); $history->setStep($this->step)->setStartDate(new \DateTimeImmutable('now'));
$this->addStepHistory($history); $this->addStepHistory($history, $context);
} }
return $this; return $this;
@ -1507,11 +1507,14 @@ class AccompanyingPeriod implements
return $this; return $this;
} }
private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory): self private function addStepHistory(AccompanyingPeriodStepHistory $stepHistory, array $context = []): self
{ {
if (!$this->stepHistories->contains($stepHistory)) { if (!$this->stepHistories->contains($stepHistory)) {
$this->stepHistories[] = $stepHistory; $this->stepHistories[] = $stepHistory;
$stepHistory->setPeriod($this); $stepHistory->setPeriod($this);
if (($context['closing_motive'] ?? null) instanceof ClosingMotive) {
$stepHistory->setClosingMotive($context['closing_motive']);
}
$this->ensureStepContinuity(); $this->ensureStepContinuity();
} }

View File

@ -59,6 +59,13 @@ class AccompanyingPeriodStepHistory implements TrackCreationInterface, TrackUpda
*/ */
private string $step; private string $step;
/**
* @ORM\ManyToOne(targetEntity=ClosingMotive::class)
*
* @ORM\JoinColumn(nullable=true)
*/
private ?ClosingMotive $closingMotive = null;
public function getEndDate(): ?\DateTimeImmutable public function getEndDate(): ?\DateTimeImmutable
{ {
return $this->endDate; return $this->endDate;
@ -114,4 +121,16 @@ class AccompanyingPeriodStepHistory implements TrackCreationInterface, TrackUpda
return $this; return $this;
} }
public function getClosingMotive(): ?ClosingMotive
{
return $this->closingMotive;
}
public function setClosingMotive(?ClosingMotive $closingMotive): self
{
$this->closingMotive = $closingMotive;
return $this;
}
} }

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepositoryInterface;
use Chill\PersonBundle\Templating\Entity\ClosingMotiveRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ByClosingMotiveAggregator implements AggregatorInterface
{
private const KEY = 'acpstephistory_by_closing_motive_agg';
public function __construct(
private ClosingMotiveRepositoryInterface $closingMotiveRepository,
private ClosingMotiveRender $closingMotiveRender,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return function (int|string|null $value): string {
if ('_header' === $value) {
return 'export.aggregator.step_history.by_closing_motive.header';
}
if (null === $value || '' === $value || null === $closingMotive = $this->closingMotiveRepository->find((int) $value)) {
return '';
}
return $this->closingMotiveRender->renderString($closingMotive, []);
};
}
public function getQueryKeys($data)
{
return [
self::KEY,
];
}
public function getTitle()
{
return 'export.aggregator.step_history.by_closing_motive.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->addSelect('IDENTITY(acpstephistory.closingMotive) AS '.self::KEY)
->addGroupBy(self::KEY);
}
public function applyOn()
{
return Declarations::ACP_STEP_HISTORY;
}
}

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Enum\DateGroupingChoiceEnum;
use Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByDateAggregatorTest;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see ByDateAggregatorTest
*/
final readonly class ByDateAggregator implements AggregatorInterface
{
private const KEY = 'acpstephistory_by_date_agg';
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('frequency', ChoiceType::class, [
'choices' => array_combine(
array_map(fn (DateGroupingChoiceEnum $c) => 'export.enum.frequency.'.$c->value, DateGroupingChoiceEnum::cases()),
array_map(fn (DateGroupingChoiceEnum $c) => $c->value, DateGroupingChoiceEnum::cases()),
),
'label' => 'export.aggregator.course.by_opening_date.frequency',
'multiple' => false,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return ['frequency' => DateGroupingChoiceEnum::YEAR->value];
}
public function getLabels($key, array $values, mixed $data)
{
return function (?string $value): string {
if ('_header' === $value) {
return 'export.aggregator.step_history.by_date.header';
}
if (null === $value || '' === $value) {
return '';
}
return $value;
};
}
public function getQueryKeys($data)
{
return [self::KEY];
}
public function getTitle()
{
return 'export.aggregator.step_history.by_date.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::KEY;
$qb->addSelect(sprintf("TO_CHAR(acpstephistory.startDate, '%s') AS {$p}", $data['frequency']));
$qb->addGroupBy($p);
$qb->addOrderBy($p, 'DESC');
}
public function applyOn()
{
return Declarations::ACP_STEP_HISTORY;
}
}

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\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByStepAggregatorTest;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @see ByStepAggregatorTest
*/
final readonly class ByStepAggregator implements AggregatorInterface
{
private const KEY = 'acpstephistory_step_agg';
public function __construct(
private TranslatorInterface $translator
) {
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing in this form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return function (?string $step): string {
if ('_header' === $step) {
return 'export.aggregator.step_history.by_step.header';
}
if (null === $step || '' === $step) {
return '';
}
return $this->translator->trans('accompanying_period.'.$step);
};
}
public function getQueryKeys($data)
{
return [
self::KEY,
];
}
public function getTitle()
{
return 'export.aggregator.step_history.by_step.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->addSelect('acpstephistory.step AS '.self::KEY)
->addGroupBy(self::KEY);
}
public function applyOn()
{
return Declarations::ACP_STEP_HISTORY;
}
}

View File

@ -18,6 +18,8 @@ abstract class Declarations
{ {
final public const ACP_TYPE = 'accompanying_period'; final public const ACP_TYPE = 'accompanying_period';
final public const ACP_STEP_HISTORY = 'accompanying_period_step_history';
final public const EVAL_TYPE = 'evaluation'; final public const EVAL_TYPE = 'evaluation';
final public const HOUSEHOLD_TYPE = 'household'; final public const HOUSEHOLD_TYPE = 'household';

View File

@ -0,0 +1,139 @@
<?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\Export;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(AccompanyingPeriod::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder): void
{
// Nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.acp_closing.description';
}
public function getGroup(): string
{
return 'Exports of accompanying courses';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return fn ($value) => '_header' === $value ? $this->getTitle() : $value;
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.export.acp_closing.title';
}
public function getType(): string
{
return Declarations::ACP_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->em->createQueryBuilder()
->select('COUNT(DISTINCT acpstephistory.id) As export_result')
->from(AccompanyingPeriod\AccompanyingPeriodStepHistory::class, 'acpstephistory')
->join('acpstephistory.period', 'acp');
$qb
->leftJoin('acp.participations', 'acppart')
->leftJoin('acppart.person', 'person');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.PersonCenterHistory::class.' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;
}
public function requiredRole(): string
{
return AccompanyingPeriodVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
Declarations::ACP_STEP_HISTORY,
];
}
}

View File

@ -0,0 +1,96 @@
<?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\AccompanyingPeriodStepHistoryFilters;
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 src\Bundle\ChillPersonBundle\Tests\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByDateFilterTest;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Class ByDateFilter
* Implements the FilterInterface.
*
* This class represents a filter that filters data based on a date range on the start date.
* It allows the user to select a start date and an end date to filter the data.
*
* @see ByDateFilterTest
*/
final readonly class ByDateFilter implements FilterInterface
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter
) {
}
public function getTitle()
{
return 'export.filter.step_history.by_date.title';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('start_date', PickRollingDateType::class, [
'label' => 'export.filter.step_history.by_date.start_date_label',
])
->add('end_date', PickRollingDateType::class, [
'label' => 'export.filter.step_history.by_date.end_date_label',
]);
}
public function getFormDefaultData(): array
{
return [
'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'end_date' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string')
{
return [
'exports.filter.step_history.by_date.description',
[
'start_date' => $this->rollingDateConverter->convert($data['start_date']),
'end_date' => $this->rollingDateConverter->convert($data['end_date']),
],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$startDate = 'acp_step_history_by_date_start_filter';
$endDate = 'acp_step_history_by_date_end_filter';
$qb
->andWhere(
"acpstephistory.startDate >= :{$startDate} AND (acpstephistory.endDate < :{$endDate} OR acpstephistory.endDate IS NULL)"
)
->setParameter($startDate, $this->rollingDateConverter->convert($data['start_date']))
->setParameter($endDate, $this->rollingDateConverter->convert($data['end_date']));
}
public function applyOn()
{
return Declarations::ACP_STEP_HISTORY;
}
}

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\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class ByStepFilter implements FilterInterface
{
public function __construct(
private TranslatorInterface $translator,
) {
}
public function getTitle()
{
return 'export.filter.step_history.by_step.title';
}
public function buildForm(FormBuilderInterface $builder)
{
$steps = [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
AccompanyingPeriod::STEP_CLOSED,
];
$builder->add('steps', ChoiceType::class, [
'choices' => array_combine(
array_map(static fn (string $step): string => 'accompanying_period.'.$step, $steps),
$steps
),
'label' => 'export.filter.step_history.by_step.pick_steps',
'multiple' => true,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [
'steps' => [],
];
}
public function describeAction($data, $format = 'string')
{
return [
'export.filter.step_history.by_step.description',
[
'%steps%' => implode(', ', array_map(fn (string $step) => $this->translator->trans('accompanying_period.'.$step), $data['steps'])),
],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->andWhere('acpstephistory.step IN (:acpstephistory_by_step_filter_steps)')
->setParameter('acpstephistory_by_step_filter_steps', $data['steps']);
}
public function applyOn()
{
return Declarations::ACP_STEP_HISTORY;
}
}

View File

@ -312,4 +312,34 @@ final class AccompanyingPeriodTest extends \PHPUnit\Framework\TestCase
$this->assertNull($period->getRequestorPerson()); $this->assertNull($period->getRequestorPerson());
$this->assertNull($period->getRequestor()); $this->assertNull($period->getRequestor());
} }
public function testSetStep(): void
{
$period = new AccompanyingPeriod();
$period->setStep(AccompanyingPeriod::STEP_CONFIRMED);
self::assertEquals(AccompanyingPeriod::STEP_CONFIRMED, $period->getStep());
self::assertCount(1, $period->getStepHistories());
$period->setStep(AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT);
self::assertEquals(AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, $period->getStep());
self::assertCount(2, $period->getStepHistories());
$periodInactiveSteps = $period->getStepHistories()->filter(fn (AccompanyingPeriod\AccompanyingPeriodStepHistory $h) => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT === $h->getStep());
self::assertCount(1, $periodInactiveSteps);
$period->setStep(AccompanyingPeriod::STEP_CLOSED, ['closing_motive' => $closingMotive = new AccompanyingPeriod\ClosingMotive()]);
self::assertEquals(AccompanyingPeriod::STEP_CLOSED, $period->getStep());
self::assertCount(3, $period->getStepHistories());
$periodClosedSteps = $period->getStepHistories()->filter(fn (AccompanyingPeriod\AccompanyingPeriodStepHistory $h) => AccompanyingPeriod::STEP_CLOSED === $h->getStep());
self::assertCount(1, $periodClosedSteps);
$periodClosedStep = $periodClosedSteps->first();
self::assertSame($closingMotive, $periodClosedStep->getClosingMotive());
}
} }

View File

@ -0,0 +1,68 @@
<?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\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByClosingMotiveAggregator;
use Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepositoryInterface;
use Chill\PersonBundle\Templating\Entity\ClosingMotiveRender;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ByClosingMotiveAggregatorTest extends AbstractAggregatorTest
{
private ClosingMotiveRender $closingMotiveRender;
private ClosingMotiveRepositoryInterface $closingMotiveRepository;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->closingMotiveRender = self::$container->get(ClosingMotiveRender::class);
$this->closingMotiveRepository = self::$container->get(ClosingMotiveRepositoryInterface::class);
}
public function getAggregator()
{
return new ByClosingMotiveAggregator(
$this->closingMotiveRepository,
$this->closingMotiveRender
);
}
public function getFormData()
{
return [
[],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$qb = $em->createQueryBuilder()
->select('COUNT(DISTINCT acpstephistory.id) As export_result')
->from(AccompanyingPeriodStepHistory::class, 'acpstephistory')
->join('acpstephistory.period', 'acp');
return [
$qb,
];
}
}

View File

@ -0,0 +1,61 @@
<?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\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Export\Enum\DateGroupingChoiceEnum;
use Doctrine\ORM\EntityManagerInterface;
use Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByDateAggregator;
/**
* @internal
*
* @coversNothing
*/
class ByDateAggregatorTest extends AbstractAggregatorTest
{
public function getAggregator()
{
return new ByDateAggregator();
}
public function getFormData()
{
return [
[
'frequency' => DateGroupingChoiceEnum::YEAR->value,
],
[
'frequency' => DateGroupingChoiceEnum::WEEK->value,
],
[
'frequency' => DateGroupingChoiceEnum::MONTH->value,
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$qb = $em->createQueryBuilder()
->select('COUNT(DISTINCT acpstephistory.id) As export_result')
->from(AccompanyingPeriodStepHistory::class, 'acpstephistory')
->join('acpstephistory.period', 'acp');
return [
$qb,
];
}
}

View File

@ -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\Tests\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByStepAggregator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class ByStepAggregatorTest extends AbstractAggregatorTest
{
public function getAggregator()
{
$translator = new class () implements TranslatorInterface {
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null)
{
return $id;
}
};
return new ByStepAggregator($translator);
}
public function getFormData()
{
return [[]];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$qb = $em->createQueryBuilder()
->select('COUNT(DISTINCT acpstephistory.id) As export_result')
->from(AccompanyingPeriodStepHistory::class, 'acpstephistory')
->join('acpstephistory.period', 'acp');
return [
$qb,
];
}
}

View File

@ -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 src\Bundle\ChillPersonBundle\Tests\Export\Export;
use Chill\MainBundle\Test\Export\AbstractExportTest;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Export\CountAccompanyingCourseStepHistory;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
final class CountAccompanyingCourseStepHistoryTest extends AbstractExportTest
{
protected function setUp(): void
{
self::bootKernel();
}
public function getExport()
{
$em = self::$container->get(EntityManagerInterface::class);
yield new CountAccompanyingCourseStepHistory($em, $this->getParameters(true));
yield new CountAccompanyingCourseStepHistory($em, $this->getParameters(false));
}
public function getFormData(): array
{
return [[]];
}
public function getModifiersCombination()
{
return [[Declarations::ACP_TYPE], [Declarations::ACP_TYPE, Declarations::ACP_STEP_HISTORY]];
}
}

View File

@ -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 src\Bundle\ChillPersonBundle\Tests\Export\Filter\AccompanyingPeriodStepHistoryFilters;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByDateFilter;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ByDateFilterTest extends AbstractFilterTest
{
private RollingDateConverterInterface $rollingDateConverter;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->rollingDateConverter = self::$container->get(RollingDateConverterInterface::class);
}
public function getFilter()
{
return new ByDateFilter($this->rollingDateConverter);
}
public function getFormData()
{
return [
[
'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'end_date' => new RollingDate(RollingDate::T_TODAY),
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$qb = $em->createQueryBuilder()
->select('COUNT(DISTINCT acpstephistory.id) As export_result')
->from(AccompanyingPeriodStepHistory::class, 'acpstephistory')
->join('acpstephistory.period', 'acp');
return [
$qb,
];
}
}

View File

@ -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 src\Bundle\ChillPersonBundle\Tests\Export\Filter\AccompanyingPeriodStepHistoryFilters;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodStepHistory;
use Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByStepFilter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class ByStepFilterTest extends AbstractFilterTest
{
public function getFilter()
{
$translator = new class () implements TranslatorInterface {
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null)
{
return $id;
}
};
return new ByStepFilter($translator);
}
public function getFormData()
{
return [
[
'steps' => [AccompanyingPeriod::STEP_CONFIRMED, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG],
],
[
'steps' => [AccompanyingPeriod::STEP_CLOSED],
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$qb = $em->createQueryBuilder()
->select('COUNT(DISTINCT acpstephistory.id) As export_result')
->from(AccompanyingPeriodStepHistory::class, 'acpstephistory')
->join('acpstephistory.period', 'acp');
return [
$qb,
];
}
}

View File

@ -0,0 +1,31 @@
services:
_defaults:
autowire: true
autoconfigure: true
# exports
Chill\PersonBundle\Export\Export\CountAccompanyingCourseStepHistory:
tags:
- { name: chill.export, alias: count_acpstephistory }
# filters
Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByDateFilter:
tags:
- { name: chill.export_filter, alias: acpstephistory_filter_by_date }
Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters\ByStepFilter:
tags:
- { name: chill.export_filter, alias: acpstephistory_filter_by_step }
# aggregators
Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByClosingMotiveAggregator:
tags:
- { name: chill.export_aggregator, alias: acpstephistory_agg_by_closing_motive }
Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByDateAggregator:
tags:
- { name: chill.export_aggregator, alias: acpstephistory_agg_by_date }
Chill\PersonBundle\Export\Aggregator\AccompanyingPeriodStepHistoryAggregators\ByStepAggregator:
tags:
- { name: chill.export_aggregator, alias: acpstephistory_agg_by_step }

View File

@ -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\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20240123161457 extends AbstractMigration
{
public function getDescription(): string
{
return 'Store closing motive when closing a course';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD closingMotive_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT FK_84D514AC504CB38D FOREIGN KEY (closingMotive_id) REFERENCES chill_person_accompanying_period_closingmotive (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history ADD CONSTRAINT FK_84D514AC65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql(<<<'EOF'
WITH last_step AS (
SELECT * FROM (
SELECT *, rank() OVER (partition by period_id ORDER BY startdate DESC, id DESC) AS r FROM chill_person_accompanying_period_step_history cpapsh
) as sq
WHERE r = 1
)
UPDATE chill_person_accompanying_period_step_history
SET closingMotive_id = chill_person_accompanying_period.closingmotive_id
FROM last_step, chill_person_accompanying_period
WHERE last_step.period_id = chill_person_accompanying_period_step_history.period_id AND chill_person_accompanying_period.id = chill_person_accompanying_period_step_history.period_id
AND last_step.step = 'CLOSED';
EOF);
$this->addSql('CREATE INDEX IDX_84D514AC504CB38D ON chill_person_accompanying_period_step_history (closingMotive_id)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history DROP CONSTRAINT FK_84D514AC504CB38D');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history DROP CONSTRAINT FK_84D514AC65FF1AEC');
$this->addSql('DROP INDEX IDX_84D514AC504CB38D');
$this->addSql('ALTER TABLE chill_person_accompanying_period_step_history DROP closingMotive_id');
}
}

View File

@ -136,14 +136,20 @@ exports:
Filtered by person\'s geographical unit (based on address) computed at date, only units: Filtered by person\'s geographical unit (based on address) computed at date, only units:
"Filtré par zone géographique sur base de l'adresse, calculé à {datecalc, date, short}, seulement les zones suivantes: {units}" "Filtré par zone géographique sur base de l'adresse, calculé à {datecalc, date, short}, seulement les zones suivantes: {units}"
filter: filter:
work:
by_treating_agent:
Filtered by treating agent at date: >-
Les agents traitant au { agent_at, date, medium }, seulement {agents}
course: course:
not_having_address_reference: not_having_address_reference:
describe: >- describe: >-
Uniquement les parcours qui ne sont pas localisés à une adresse de référence, à la date du {date_calc, date, medium} Uniquement les parcours qui ne sont pas localisés à une adresse de référence, à la date du {date_calc, date, medium}
work:
by_treating_agent:
Filtered by treating agent at date: >-
Les agents traitant au { agent_at, date, medium }, seulement {agents}
step_history:
by_date:
description: >-
Changements de statuts filtrés par date: après le { start_date, date, medium } (inclus), avant le { end_date, date, medium }
Les agents traitants au { agent_at, date, medium }, seulement {agents} Les agents traitants au { agent_at, date, medium }, seulement {agents}

View File

@ -785,7 +785,9 @@ accompanying_period:
dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date% dates_from_%opening_date%_to_%closing_date%: Ouvert du %opening_date% au %closing_date%
DRAFT: Brouillon DRAFT: Brouillon
CONFIRMED: Confirmé CONFIRMED: Confirmé
CLOSED: Clotûré CLOSED: Clôturé
CONFIRMED_INACTIVE_SHORT: Hors file active
CONFIRMED_INACTIVE_LONG: Pré-archivé
emergency: Urgent emergency: Urgent
occasional: ponctuel occasional: ponctuel
regular: régulier regular: régulier
@ -985,6 +987,9 @@ export:
YYYY-MM: par mois YYYY-MM: par mois
YYYY: par année YYYY: par année
export: export:
acp_closing:
title: Nombre de changements de statuts de parcours
description: Compte le nombre de changements de statuts de parcours. Cet export est indiqué pour obtenir le nombre de parcours ouverts ou fermés pendant une période de temps (un parcours pouvant être clôturé, puis ré-ouvert pendant la période de temps indiquée)
acp_stats: acp_stats:
avg_duration: Moyenne de la durée de participation de chaque usager concerné avg_duration: Moyenne de la durée de participation de chaque usager concerné
count_participations: Nombre de participations distinctes count_participations: Nombre de participations distinctes
@ -1037,6 +1042,18 @@ export:
at_date: Date de calcul de l'adresse at_date: Date de calcul de l'adresse
header: Code postal header: Code postal
step_history:
by_step:
title: Grouper les changements de statut du parcours par étape
header: Nouveau statut du parcours
by_date:
title: Grouper les changements de statut du parcours par date
header: Date du changement de statut du parcours
date_grouping_label: Grouper par
by_closing_motive:
title: Grouper les changements de statut du parcours par motif de clôture
header: Motif de clôture
course: course:
by-user: by-user:
title: Grouper les parcours par usager participant title: Grouper les parcours par usager participant
@ -1128,6 +1145,16 @@ export:
by_geog_unit: by_geog_unit:
Filtered by person's geographical unit (based on address) computed at %datecalc%, only %units%: Filtré par unité géographique (sur base de l'adresse), calculé le %datecalc%, seulement %units% Filtered by person's geographical unit (based on address) computed at %datecalc%, only %units%: Filtré par unité géographique (sur base de l'adresse), calculé le %datecalc%, seulement %units%
step_history:
by_step:
title: Filtrer les changements de statut du parcours par étape
pick_steps: Nouvelles étapes
description: "Filtré par étape: seulement %steps%"
by_date:
title: Filtrer les changements de statut du parcours par date
start_date_label: Changements après le
end_date_label: Changements avant le
person: person:
by_composition: by_composition:
Filter by household composition: Filtrer les usagers par composition du ménage Filter by household composition: Filtrer les usagers par composition du ménage