Compare commits

..

21 Commits

Author SHA1 Message Date
26a5de9958 Merge branch 'issue178_169_export_calendars' of gitlab.com:Chill-Projet/chill-bundles into issue178_169_export_calendars 2023-11-16 12:03:43 +01:00
4e35bcbd93 Format yaml file back to they way it was before 2023-11-16 12:03:35 +01:00
7587472785 fix export.yaml file: remove quotes 2023-11-16 12:03:35 +01:00
9ba33f3df4 style fixes for tests 2023-11-16 12:03:35 +01:00
e0de10b7a8 Add tests for new exports calendar 2023-11-16 12:03:35 +01:00
33235d3541 php cs fixes 2023-11-16 12:03:35 +01:00
817ca7e148 add missing variables to two exports 2023-11-16 12:03:35 +01:00
382f275719 add translations 2023-11-16 12:03:35 +01:00
f78c1c0512 adjust logic for calendar exports linked to person 2023-11-16 12:03:35 +01:00
c3ced7fb6e add center filter to calendar exports linked to acp 2023-11-16 12:03:35 +01:00
96a9be39c3 reorganize exports calendar/ refactor config yaml 2023-11-16 12:03:35 +01:00
792ad394c8 Format yaml file back to they way it was before 2023-11-16 11:14:38 +01:00
a7141ef771 fix export.yaml file: remove quotes 2023-11-16 10:43:47 +01:00
bc638e5eb9 style fixes for tests 2023-11-15 16:38:57 +01:00
b0171e3093 Add tests for new exports calendar 2023-11-15 16:34:08 +01:00
b317daf779 php cs fixes 2023-11-15 16:20:10 +01:00
27bf2893d0 add missing variables to two exports 2023-11-15 16:19:43 +01:00
e2a12968ce add translations 2023-11-15 16:15:24 +01:00
fe9ce1a356 adjust logic for calendar exports linked to person 2023-11-15 15:58:41 +01:00
9c04212c45 add center filter to calendar exports linked to acp 2023-11-15 15:40:36 +01:00
85504d72c2 reorganize exports calendar/ refactor config yaml 2023-11-15 15:31:18 +01:00
72 changed files with 923 additions and 862 deletions

View File

@@ -1,5 +0,0 @@
kind: Fixed
body: 'Export: fix list person with custom fields'
time: 2023-11-27T21:01:38.260730706+01:00
custom:
Issue: ""

View File

@@ -1,9 +0,0 @@
## v2.13.0 - 2023-11-21
### Feature
* ([#173](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/173)) Allow user to add a phonenumber to their profile which will be included in automatically generated documents
### Fixed
* ([#211](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/211)) Export: fix loading of "Group activity by type"
* ([#190](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/190)) Export: fix loading of "group activity by reasons"
* ([#213](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/213)) Export: fix usage of some Collection returned instead of array in export filters
* ([#215](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/215)) Use only the string 'both' for gender (with a database migration)
* ([#212](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/212)) Clean the database to make working the "Group people by gender" aggregator

View File

@@ -1,8 +0,0 @@
## v2.14.0 - 2023-11-24
### Feature
* ([#161](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/161)) Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order
### Fixed
* ([#141](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/141)) Export: on filter "action by type goals, and results", restore the fields when editing a saved export
* ([#219](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/219)) Export: fix the list of accompanying period work, when the "calc date" is null
* ([#222](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/222)) Fix rendering of custom fields
* Fix various errors in custom fields administration

View File

@@ -6,56 +6,6 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v2.14.0 - 2023-11-24
### Feature
* ([#161](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/161)) Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order
### Fixed
* ([#141](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/141)) Export: on filter "action by type goals, and results", restore the fields when editing a saved export
* ([#219](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/219)) Export: fix the list of accompanying period work, when the "calc date" is null
* ([#222](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/222)) Fix rendering of custom fields
* Fix various errors in custom fields administration
## v2.13.0 - 2023-11-21
### Feature
* ([#173](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/173)) Allow user to add a phonenumber to their profile which will be included in automatically generated documents
### Fixed
* ([#211](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/211)) Export: fix loading of "Group activity by type"
* ([#190](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/190)) Export: fix loading of "group activity by reasons"
* ([#213](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/213)) Export: fix usage of some Collection returned instead of array in export filters
* ([#215](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/215)) Use only the string 'both' for gender (with a database migration)
* ([#212](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/212)) Clean the database to make working the "Group people by gender" aggregator
## v2.12.1 - 2023-11-16
### Fixed
* ([#208](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/208)) Export: fix loading of form for "filter action by type, goal and result"
## v2.12.0 - 2023-11-15
### Feature
* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add an aggregator "group activities by presence"
* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add a filter "filter activity by activity presence"
* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add an aggregator "group activities by person" (only for the activities saved in a person context)
* ([#199](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/199)) Export: add a new aggregator "group peoples by postal code"
* ([#200](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/200)) Export: split export about person on accompanying period work: one with the people associated with the work, another one with the people associated with the accompanying period
* ([#204](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/204)) Add 3 new filters and 3 new aggregators for work action creator (with jobs and scopes)
* ([#202](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/202)) Create export for the average duration of social work actions
* ([#206](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/206)) Export: add a export which count persons on accompanying period work
* ([#206](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/206)) Export: add an export which count persons on activity
* ([#203](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/203)) Export: add clauses on the social work start date and end date within the filter "Filter accompanying period by accompanying period work"
### Fixed
* Export: fix typo in filter "filter accompanying period work on end date"
* ([#189](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/189)) Export: Fix failure in export linked to household
* ([#205](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/205)) Fix loading of accompanying period work referrers
### Traduction francophone des principaux changements
* export: ajout d'un regroupement "grouper les échanges par présence de l'usager";
* export: ajout d'un filtre "filtre les échanges par présence de l'usager";
* export: ajout d'un regroupement "regrouper les échanges par personne" (seulement pour les échanges enregistrés dans le contexte de l'usager);
* export: ajout d'un regroupement "grouper les usagers par codes postaux"
* export: séparation des exports sur les actions: dans l'un, les filtres des usagers portent sur les usagers concernés par l'action, dans l'autre, les filtres portent sur les usagers concernés par le parcours de l'action;
* export: ajout de 3 nouveaux filtres et regroupements sur le créateur de l'action, son métier et son service;
* export: correction de l'export sur les ménages liés aux parcours;
* correction du chargement des actions d'accompagnement
## v2.11.0 - 2023-11-07
### Feature
* ([#194](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/194)) Export: add a filter "filter activity by creator job"

View File

@@ -15,7 +15,7 @@
"@symfony/webpack-encore": "^4.1.0",
"@tsconfig/node14": "^1.0.1",
"bindings": "^1.5.0",
"bootstrap": "^5.3.0",
"bootstrap": "^5.0.1",
"chokidar": "^3.5.1",
"fork-awesome": "^1.1.7",
"jquery": "^3.6.0",

View File

@@ -56,15 +56,20 @@ class ActivityTypeAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data): \Closure
{
return function (null|int|string $value): string {
// for performance reason, we load data from db only once
$this->activityTypeRepository->findBy(['id' => $values]);
return function ($value): string {
if ('_header' === $value) {
return 'Activity type';
}
if (null === $value || '' === $value || null === $t = $this->activityTypeRepository->find($value)) {
if (null === $value || '' === $value) {
return '';
}
$t = $this->activityTypeRepository->find($value);
return $this->translatableStringHelper->localize($t->getName());
};
}

View File

@@ -99,6 +99,12 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
public function getLabels($key, array $values, $data)
{
match ($data['level']) {
'reasons' => $this->activityReasonRepository->findBy(['id' => $values]),
'categories' => $this->activityReasonCategoryRepository->findBy(['id' => $values]),
default => throw new \RuntimeException(sprintf("The level data '%s' is invalid.", $data['level'])),
};
return function ($value) use ($data) {
if ('_header' === $value) {
return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason';

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Export\Export;
namespace Chill\CalendarBundle\Export\Export\LinkedToAcp;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
@@ -17,17 +17,25 @@ use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Exception\LogicException;
class CountCalendars implements ExportInterface, GroupedExportInterface
{
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly CalendarRepository $calendarRepository,
) {}
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
@@ -46,12 +54,12 @@ class CountCalendars implements ExportInterface, GroupedExportInterface
public function getDescription(): string
{
return 'Count calendars by various parameters.';
return 'export.export.count_calendar_linked_to_acp.description';
}
public function getGroup(): string
{
return 'Exports of calendar';
return 'export.export.calendar_linked_to_acp.group';
}
public function getLabels($key, array $values, $data)
@@ -78,7 +86,7 @@ class CountCalendars implements ExportInterface, GroupedExportInterface
public function getTitle(): string
{
return 'Count calendars';
return 'export.export.count_calendar_linked_to_acp.title';
}
public function getType(): string
@@ -98,6 +106,19 @@ class CountCalendars implements ExportInterface, GroupedExportInterface
$qb->select('COUNT(cal.id) AS export_result');
$qb->leftJoin('cal.accompanyingPeriod', 'acp');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Export\Export;
namespace Chill\CalendarBundle\Export\Export\LinkedToAcp;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
@@ -17,14 +17,24 @@ use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
{
public function __construct(private readonly CalendarRepository $calendarRepository) {}
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly CalendarRepository $calendarRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder): void
{
@@ -43,12 +53,12 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
public function getDescription(): string
{
return 'Get the average of calendar duration according to various filters';
return 'export.export.avg_duration_calendar_linked_to_acp.description';
}
public function getGroup(): string
{
return 'Exports of calendar';
return 'export.export.calendar_linked_to_acp.group';
}
public function getLabels($key, array $values, $data)
@@ -75,7 +85,7 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
public function getTitle(): string
{
return 'Average calendar duration';
return 'export.export.avg_duration_calendar_linked_to_acp.title';
}
public function getType(): string
@@ -85,11 +95,26 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('AVG(cal.endDate - cal.startDate) AS export_result');
$qb->join('cal.accompanyingPeriod', 'acp');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CalendarBundle\Export\Export;
namespace Chill\CalendarBundle\Export\Export\LinkedToAcp;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
@@ -17,14 +17,24 @@ use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
{
public function __construct(private readonly CalendarRepository $calendarRepository) {}
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly CalendarRepository $calendarRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder): void
{
@@ -43,12 +53,12 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
public function getDescription(): string
{
return 'Get the sum of calendar durations according to various filters';
return 'export.export.sum_duration_calendar_linked_to_acp.description';
}
public function getGroup(): string
{
return 'Exports of calendar';
return 'export.export.calendar_linked_to_acp.group';
}
public function getLabels($key, array $values, $data)
@@ -75,7 +85,7 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
public function getTitle(): string
{
return 'Sum of calendar durations';
return 'export.export.sum_duration_calendar_linked_to_acp.description';
}
public function getType(): string
@@ -85,11 +95,26 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('SUM(cal.endDate - cal.startDate) AS export_result');
$qb->join('cal.accompanyingPeriod', 'acp');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;

View File

@@ -0,0 +1,137 @@
<?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\CalendarBundle\Export\Export\LinkedToPerson;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Exception\LogicException;
class CountCalendars implements ExportInterface, GroupedExportInterface
{
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly CalendarRepository $calendarRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
// No form necessary
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.count_calendar_linked_to_person.description';
}
public function getGroup(): string
{
return 'export.export.calendar_linked_to_person.group';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
{
return ['export_result'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.export.count_calendar_linked_to_person.title';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
/**
* Initiate the query.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('COUNT(cal.id) AS export_result');
$qb->leftJoin('cal.person', 'person');
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'cal.startDate'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'cal.endDate')
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
// which role should we give here?
return PersonVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
}

View File

@@ -0,0 +1,132 @@
<?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\CalendarBundle\Export\Export\LinkedToPerson;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface
{
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly CalendarRepository $calendarRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder): void
{
// no form needed
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.stat_calendar_avg_duration_linked_to_person.description';
}
public function getGroup(): string
{
return 'export.export.calendar_linked_to_person.group';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static fn ($value) => $labels[$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.stat_calendar_avg_duration_linked_to_person.title';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('AVG(cal.endDate - cal.startDate) AS export_result');
$qb->join('cal.person', 'person');
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'cal.startDate'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'cal.endDate')
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
return AccompanyingPeriodVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
}

View File

@@ -0,0 +1,132 @@
<?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\CalendarBundle\Export\Export\LinkedToPerson;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface
{
private readonly bool $filterStatsByCenters;
public function __construct(
private readonly CalendarRepository $calendarRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder): void
{
// no form needed
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.stat_calendar_sum_duration_linked_to_person.description';
}
public function getGroup(): string
{
return 'export.export.calendar_linked_to_person.group';
}
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static fn ($value) => $labels[$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.stat_calendar_sum_duration_linked_to_person.title';
}
public function getType(): string
{
return Declarations::CALENDAR_TYPE;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->calendarRepository->createQueryBuilder('cal');
$qb->select('SUM(cal.endDate - cal.startDate) AS export_result');
$qb->join('cal.person', 'person');
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'cal.startDate'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'cal.endDate')
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
return AccompanyingPeriodVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::CALENDAR_TYPE,
];
}
}

View File

@@ -1,118 +1,139 @@
services:
## Indicators
chill.calendar.export.count_calendars:
class: Chill\CalendarBundle\Export\Export\CountCalendars
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: count_calendars }
## Indicators
chill.calendar.export.count_calendars_linked_to_acp:
class: Chill\CalendarBundle\Export\Export\LinkedToAcp\CountCalendars
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: count_calendars_linked_to_acp }
chill.calendar.export.average_duration_calendars:
class: Chill\CalendarBundle\Export\Export\StatCalendarAvgDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: average_duration_calendars }
chill.calendar.export.average_duration_calendars_linked_to_acp:
class: Chill\CalendarBundle\Export\Export\LinkedToAcp\StatCalendarAvgDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: average_duration_calendars_linked_to_acp }
chill.calendar.export.sum_duration_calendars:
class: Chill\CalendarBundle\Export\Export\StatCalendarSumDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: sum_duration_calendars }
chill.calendar.export.sum_duration_calendars_linked_to_acp:
class: Chill\CalendarBundle\Export\Export\LinkedToAcp\StatCalendarSumDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: sum_duration_calendars_linked_to_acp }
## Filters
chill.calendar.export.count_calendars_linked_to_person:
class: Chill\CalendarBundle\Export\Export\LinkedToPerson\CountCalendars
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: count_calendars_linked_to_person }
chill.calendar.export.agent_filter:
class: Chill\CalendarBundle\Export\Filter\AgentFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: agent_filter }
chill.calendar.export.average_duration_calendars_linked_to_person:
class: Chill\CalendarBundle\Export\Export\LinkedToPerson\StatCalendarAvgDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: average_duration_calendars_linked_to_person }
chill.calendar.export.job_filter:
class: Chill\CalendarBundle\Export\Filter\JobFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: job_filter }
chill.calendar.export.sum_duration_calendars_linked_to_person:
class: Chill\CalendarBundle\Export\Export\LinkedToPerson\StatCalendarSumDuration
autowire: true
autoconfigure: true
tags:
- { name: chill.export, alias: sum_duration_calendars_linked_to_person }
chill.calendar.export.scope_filter:
class: Chill\CalendarBundle\Export\Filter\ScopeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: scope_filter }
## Filters
chill.calendar.export.between_dates_filter:
class: Chill\CalendarBundle\Export\Filter\BetweenDatesFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: between_dates_filter }
chill.calendar.export.agent_filter:
class: Chill\CalendarBundle\Export\Filter\AgentFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: agent_filter }
chill.calendar.export.calendar_range_filter:
class: Chill\CalendarBundle\Export\Filter\CalendarRangeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: calendar_range_filter }
chill.calendar.export.job_filter:
class: Chill\CalendarBundle\Export\Filter\JobFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: job_filter }
## Aggregator
chill.calendar.export.scope_filter:
class: Chill\CalendarBundle\Export\Filter\ScopeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: scope_filter }
chill.calendar.export.agent_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\AgentAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: agent_aggregator }
chill.calendar.export.between_dates_filter:
class: Chill\CalendarBundle\Export\Filter\BetweenDatesFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: between_dates_filter }
chill.calendar.export.job_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\JobAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: job_aggregator }
chill.calendar.export.calendar_range_filter:
class: Chill\CalendarBundle\Export\Filter\CalendarRangeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: calendar_range_filter }
chill.calendar.export.scope_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\ScopeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: scope_aggregator }
## Aggregator
chill.calendar.export.location_type_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationTypeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_type_aggregator }
chill.calendar.export.agent_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\AgentAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: agent_aggregator }
chill.calendar.export.location_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_aggregator }
chill.calendar.export.job_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\JobAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: job_aggregator }
chill.calendar.export.cancel_reason_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\CancelReasonAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: cancel_reason_aggregator }
chill.calendar.export.scope_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\ScopeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: scope_aggregator }
chill.calendar.export.month_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\MonthYearAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: month_aggregator }
chill.calendar.export.location_type_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationTypeAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_type_aggregator }
chill.calendar.export.urgency_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\UrgencyAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: urgency_aggregator }
chill.calendar.export.location_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\LocationAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: location_aggregator }
chill.calendar.export.cancel_reason_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\CancelReasonAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: cancel_reason_aggregator }
chill.calendar.export.month_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\MonthYearAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: month_aggregator }
chill.calendar.export.urgency_aggregator:
class: Chill\CalendarBundle\Export\Aggregator\UrgencyAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: urgency_aggregator }

View File

@@ -0,0 +1,55 @@
<?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\CalendarBundle\Tests\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Export\Export\LinkedToAcp\CountCalendars as CountCalendarsLinkedToAcp;
use Chill\CalendarBundle\Export\Export\LinkedToPerson\CountCalendars as CountCalendarsLinkedToPerson;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest;
/**
* @internal
*
* @coversNothing
*/
class CountCalendarsTest extends AbstractExportTest
{
protected function setUp(): void
{
self::bootKernel();
}
public function getExport()
{
$repository = self::$container->get(CalendarRepository::class);
yield new CountCalendarsLinkedToAcp($repository, $this->getParameters(true));
yield new CountCalendarsLinkedToAcp($repository, $this->getParameters(false));
yield new CountCalendarsLinkedToPerson($repository, $this->getParameters(true));
yield new CountCalendarsLinkedToPerson($repository, $this->getParameters(false));
}
public function getFormData()
{
return [];
}
public function getModifiersCombination()
{
return [
[
Declarations::CALENDAR_TYPE,
]];
}
}

View File

@@ -0,0 +1,55 @@
<?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\CalendarBundle\Tests\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Export\Export\LinkedToAcp\StatCalendarAvgDuration as StatCalendarAvgDurationLinkedToAcp;
use Chill\CalendarBundle\Export\Export\LinkedToPerson\StatCalendarAvgDuration as StatCalendarAvgDurationLinkedToPerson;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest;
/**
* @internal
*
* @coversNothing
*/
class StatCalendarAvgDurationTest extends AbstractExportTest
{
protected function setUp(): void
{
self::bootKernel();
}
public function getExport()
{
$repository = self::$container->get(CalendarRepository::class);
yield new StatCalendarAvgDurationLinkedToAcp($repository, $this->getParameters(true));
yield new StatCalendarAvgDurationLinkedToAcp($repository, $this->getParameters(false));
yield new StatCalendarAvgDurationLinkedToPerson($repository, $this->getParameters(true));
yield new StatCalendarAvgDurationLinkedToPerson($repository, $this->getParameters(false));
}
public function getFormData()
{
return [];
}
public function getModifiersCombination()
{
return [
[
Declarations::CALENDAR_TYPE,
]];
}
}

View File

@@ -0,0 +1,55 @@
<?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\CalendarBundle\Tests\Export\Export;
use Chill\CalendarBundle\Export\Declarations;
use Chill\CalendarBundle\Export\Export\LinkedToAcp\StatCalendarSumDuration as StatCalendarSumDurationLinkedToAcp;
use Chill\CalendarBundle\Export\Export\LinkedToPerson\StatCalendarSumDuration as StatCalendarSumDurationLinkedToPerson;
use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest;
/**
* @internal
*
* @coversNothing
*/
class StatCalendarSumDurationTest extends AbstractExportTest
{
protected function setUp(): void
{
self::bootKernel();
}
public function getExport()
{
$repository = self::$container->get(CalendarRepository::class);
yield new StatCalendarSumDurationLinkedToAcp($repository, $this->getParameters(true));
yield new StatCalendarSumDurationLinkedToAcp($repository, $this->getParameters(false));
yield new StatCalendarSumDurationLinkedToPerson($repository, $this->getParameters(true));
yield new StatCalendarSumDurationLinkedToPerson($repository, $this->getParameters(false));
}
public function getFormData()
{
return [];
}
public function getModifiersCombination()
{
return [
[
Declarations::CALENDAR_TYPE,
]];
}
}

View File

@@ -112,6 +112,31 @@ Group calendars by month and year: Grouper les rendez-vous par mois et année
Group calendars by urgency: Grouper les rendez-vous par urgent ou non
export:
export:
calendar_linked_to_person:
group: Exports des rendez-vous liés à un usager
calendar_linked_to_acp:
group: Exports des rendez-vous liés à un parcours
stat_calendar_sum_duration_linked_to_person:
title: Somme de la durée des rendez-vous
description: Additionne la durée des rendez-vous en fonction de différents paramètres.
stat_calendar_avg_duration_linked_to_person:
title: Moyenne de la durée des rendez-vous
description: Moyenne de la durée des rendez-vous en fonction de différents paramètres.
count_calendar_linked_to_person:
title: Nombre des rendez-vous
description: Compte le nombre des rendez-vous enregistrés et liés à un usager en fonction de différents paramètres.
count_calendar_linked_to_acp:
title: Nombre des rendez-vous
description: Compte le nombre des rendez-vous enregistrés et liées à un parcours en fonction de différents paramètres.
avg_duration_calendar_linked_to_acp:
title: Moyenne de la durée des rendez-vous
description: Moyenne de la durée des rendez-vous en fonction de différents paramètres.
sum_duration_calendar_linked_to_acp:
title: Somme de la durée des rendez-vous
description: Additionne la durée des rendez-vous en fonction de différents paramètres.
aggregator.calendar:
agent_job:
Group calendars by agent job: Grouper les rendez-vous par métier de l'agent

View File

@@ -30,7 +30,7 @@ class CustomFieldController extends AbstractController
/**
* Creates a new CustomField entity.
*
* @Route("/{_locale}/admin/customfield/new", name="customfield_create")
* @Route("/{_locale}/admin/customfield/new", name="customfield_new")
*/
public function createAction(Request $request)
{

View File

@@ -232,7 +232,7 @@ class CustomFieldsGroupController extends AbstractController
/**
* Finds and displays a CustomFieldsGroup entity.
*
* @Route("/{_locale}/admin/customfieldsgroup/{id}/show", name="customfieldsgroup_show")
* @Route("/{_locale}/admin/customfieldsgroup/{id}/show", name="customfieldsgroup/show")
*/
public function showAction(mixed $id)
{
@@ -256,7 +256,7 @@ class CustomFieldsGroupController extends AbstractController
/**
* Edits an existing CustomFieldsGroup entity.
*
* @Route("/{_locale}/admin/customfieldsgroup/{id}/update", name="customfieldsgroup_update")
* @Route("/{_locale}/admin/customfieldsgroup/{id}/update", name="customfieldsgroup/update")
*/
public function updateAction(Request $request, mixed $id)
{
@@ -383,7 +383,7 @@ class CustomFieldsGroupController extends AbstractController
$em = $this->getDoctrine()->getManager();
$customFieldsGroupIds = $em->createQuery('SELECT g.id FROM '
.CustomFieldsDefaultGroup::class.' d '
.'ChillCustomFieldsBundle:CustomFieldsDefaultGroup d '
.'JOIN d.customFieldsGroup g')
->getResult(Query::HYDRATE_SCALAR);

View File

@@ -280,7 +280,7 @@ class CustomFieldChoice extends AbstractCustomField
$template = '@ChillCustomFields/CustomFieldsRendering/choice.html.twig';
if ('csv' === $documentType) {
$template = '@ChillCustomFields/CustomFieldsRendering/choice.csv.twig';
$template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice.csv.twig';
}
return $this->templating

View File

@@ -68,7 +68,7 @@ class CustomFieldDate extends AbstractCustomField
{
$validatorFunction = static function ($value, ExecutionContextInterface $context) {
try {
$date = new \DateTime((string) $value);
$date = new \DateTime($value);
} catch (\Exception) {
$context->buildViolation('The expression "%expression%" is invalid', [
'%expression%' => $value,
@@ -125,7 +125,7 @@ class CustomFieldDate extends AbstractCustomField
return $date->format('Y-m-d');
default:
$template = '@ChillCustomFields/CustomFieldsRendering/date.'
$template = 'ChillCustomFieldsBundle:CustomFieldsRendering:date.'
.$documentType.'.twig';
return $this->templating

View File

@@ -96,7 +96,7 @@ class CustomFieldLongChoice extends AbstractCustomField
public function render($value, CustomField $customField, $documentType = 'html')
{
$option = $this->deserialize($value, $customField);
$template = '@ChillCustomFields/CustomFieldsRendering/choice_long.'
$template = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice_long.'
.$documentType.'.twig';
return $this->templating

View File

@@ -95,7 +95,7 @@ class CustomFieldNumber extends AbstractCustomField
public function render($value, CustomField $customField, $documentType = 'html')
{
$template = '@ChillCustomFields/CustomFieldsRendering/number.'
$template = 'ChillCustomFieldsBundle:CustomFieldsRendering:number.'
.$documentType.'.twig';
$options = $customField->getOptions();

View File

@@ -89,7 +89,7 @@ class CustomFieldText extends AbstractCustomField
$template = '@ChillCustomFields/CustomFieldsRendering/text.html.twig';
if ('csv' === $documentType) {
$template = '@ChillCustomFields/CustomFieldsRendering/text.csv.twig';
$template = 'ChillCustomFieldsBundle:CustomFieldsRendering:text.csv.twig';
}
return $this->templating

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\CustomFieldsBundle\Form;
use Chill\CustomFieldsBundle\Entity\CustomFieldsGroup;
use Chill\CustomFieldsBundle\Form\DataTransformer\CustomFieldsGroupToIdTransformer;
use Chill\CustomFieldsBundle\Service\CustomFieldProvider;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
@@ -46,7 +45,7 @@ class CustomFieldType extends AbstractType
if ('entity' === $options['group_widget']) {
$builder->add('customFieldsGroup', EntityType::class, [
'class' => CustomFieldsGroup::class,
'class' => 'ChillCustomFieldsBundle:CustomFieldsGroup',
'choice_label' => fn ($g) => $this->translatableStringHelper->localize($g->getName()),
]);
} elseif ('hidden' === $options['group_widget']) {

View File

@@ -1,8 +1,4 @@
services:
_defaults:
autowire: true
autoconfigure: true
Chill\CustomFieldsBundle\Controller\:
resource: '../../Controller'
tags: ['controller.service_arguments']

View File

@@ -11,14 +11,14 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Serializer\Helper;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class NormalizeNullValueHelper
{
public function __construct(private readonly NormalizerInterface $normalizer, private readonly ?string $discriminatorType = null, private readonly ?string $discriminatorValue = null) {}
public function normalize(array $attributes, string $format = 'docgen', ?array $context = [], ClassMetadataInterface $classMetadata = null)
public function normalize(array $attributes, string $format = 'docgen', ?array $context = [], ClassMetadata $classMetadata = null)
{
$data = [];
$data['isNull'] = true;
@@ -44,7 +44,7 @@ class NormalizeNullValueHelper
return $data;
}
private function getContextForAttribute(string $key, array $initialContext, ?ClassMetadataInterface $classMetadata): array
private function getContextForAttribute(string $key, array $initialContext, ?ClassMetadata $classMetadata): array
{
if (null === $classMetadata) {
return $initialContext;

View File

@@ -19,7 +19,6 @@ use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
@@ -53,15 +52,12 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
}
$metadata = $this->classMetadataFactory->getMetadataFor($classMetadataKey);
if (!$metadata instanceof ClassMetadata) {
throw new \LogicException('ClassMetadata should be the only one implementation for ClassMetadataInterface. See https://github.com/symfony/symfony/pull/17114');
}
$expectedGroups = \array_key_exists(AbstractNormalizer::GROUPS, $context) ?
\is_array($context[AbstractNormalizer::GROUPS]) ? $context[AbstractNormalizer::GROUPS] : [$context[AbstractNormalizer::GROUPS]]
: [];
$attributes = \array_filter(
$metadata->getAttributesMetadata(),
static function (AttributeMetadataInterface $a) use ($expectedGroups) {
static function (AttributeMetadata $a) use ($expectedGroups) {
foreach ($a->getGroups() as $g) {
if (\in_array($g, $expectedGroups, true)) {
return true;
@@ -123,7 +119,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
return $type->getName();
}
if ($type instanceof \ReflectionIntersectionType) {
foreach (array_map(fn (\ReflectionType $t) => $t->getName(), $type->getTypes()) as $classString) {
foreach (array_map(fn (\ReflectionNamedType $t) => $t->getName(), $type->getTypes()) as $classString) {
if (ReadableCollection::class === $classString) {
return ReadableCollection::class;
}
@@ -215,7 +211,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
}
/**
* @param array<AttributeMetadata> $attributes
* @param array|AttributeMetadata[] $attributes
*
* @return array
*

View File

@@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Form\UserPhonenumberType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\Routing\Annotation\Route;
class UserProfileController extends AbstractController
{
public function __construct(
private readonly TranslatorInterface $translator,
) {}
/**
* User profile that allows editing of phonenumber and visualization of certain data.
*
* @Route("/{_locale}/main/user/my-profile", name="chill_main_user_profile")
*/
public function __invoke(Request $request)
{
$user = $this->getUser();
$editForm = $this->createPhonenumberEditForm($user);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$phonenumber = $editForm->get('phonenumber')->getData();
$user->setPhonenumber($phonenumber);
$this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->translator->trans('user.profile.Phonenumber successfully updated!'));
return $this->redirectToRoute('chill_main_user_profile');
}
return $this->render('@ChillMain/User/profile.html.twig', [
'user' => $user,
'form' => $editForm->createView(),
]);
}
private function createPhonenumberEditForm(UserInterface $user): FormInterface
{
return $this->createForm(
UserPhonenumberType::class,
$user,
)
->add('submit', SubmitType::class, ['label' => $this->translator->trans('Save')]);
}
}

View File

@@ -18,11 +18,9 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;
use libphonenumber\PhoneNumber;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
/**
* User.
@@ -163,15 +161,6 @@ class User implements UserInterface, \Stringable
*/
private ?string $usernameCanonical = null;
/**
* The user's mobile phone number.
*
* @ORM\Column(type="phone_number", nullable=true)
*
* @PhonenumberConstraint()
*/
private ?PhoneNumber $phonenumber = null;
/**
* User constructor.
*/
@@ -430,11 +419,6 @@ class User implements UserInterface, \Stringable
}
}
public function getPhonenumber(): ?PhoneNumber
{
return $this->phonenumber;
}
/**
* @throws \RuntimeException if the groupCenter is not in the collection
*/
@@ -655,11 +639,4 @@ class User implements UserInterface, \Stringable
return $this;
}
public function setPhonenumber(?PhoneNumber $phonenumber): self
{
$this->phonenumber = $phonenumber;
return $this;
}
}

View File

@@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserPhonenumberType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('phonenumber', ChillPhoneNumberType::class, [
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@@ -16,7 +16,6 @@ use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Form\Type\PickCivilityType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
@@ -45,9 +44,6 @@ class UserType extends AbstractType
->add('email', EmailType::class, [
'required' => true,
])
->add('phonenumber', ChillPhoneNumberType::class, [
'required' => false,
])
->add('label', TextType::class)
->add('civility', PickCivilityType::class, [
'required' => false,

View File

@@ -11,7 +11,6 @@
// 3. Include remainder of required Bootstrap stylesheets
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
// 4. Include any default map overrides here
@import "custom/_maps";

View File

@@ -1,58 +0,0 @@
{#
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends "@ChillMain/layout.html.twig" %}
{% block title %}{{"My profile"|trans}}{% endblock %}
{% block content %}
<div class="justify-content-center col-10">
<h1>{{ 'user.profile.title'|trans }}</h1>
<dl>
<dt>{{ 'Job'|trans }}</dt>
{% if user.getUserJob is not null %}
<dd>{{ user.getUserJob.label|localize_translatable_string }}</dd>
{% else %}
<dd class="chill-no-data-statement">{{ 'user.profile.no job'|trans }}</dd>
{% endif %}
<dt>{{ 'Scope'|trans }}</dt>
{% if user.getMainScope is not null %}
<dd>{{ user.getMainScope.name|localize_translatable_string }}</dd>
{% else %}
<dd class="chill-no-data-statement">{{ 'user.profile.no scope'|trans }}</dd>
{% endif %}
</dl>
<div>
{{ form_start(form) }}
{{ form_row(form.phonenumber) }}
<ul class="record_actions">
<li>
{{ form_widget(form.submit, { 'attr': { 'class': 'btn btn-save' } } ) }}
</li>
</ul>
{{ form_end(form) }}
</div>
</div>
{% endblock %}

View File

@@ -29,14 +29,6 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
$user = $this->security->getUser();
if ($user instanceof User) {
$menu->addChild($this->translator->trans('user.profile.title'), [
'route' => 'chill_main_user_profile',
])
->setExtras([
'order' => -11_111_111,
'icon' => 'user',
]);
if (null !== $user->getCurrentLocation()) {
$locationTextMenu = $user->getCurrentLocation()->getName();
} else {

View File

@@ -18,7 +18,6 @@ use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Templating\Entity\UserRender;
use libphonenumber\PhoneNumber;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
@@ -35,7 +34,6 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
'text_without_absent' => '',
'label' => '',
'email' => '',
'isAbsent' => false,
];
public function __construct(private readonly UserRender $userRender) {}
@@ -63,13 +61,9 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
$context,
['docgen:expects' => Civility::class, 'groups' => 'docgen:read']
);
$phonenumberContext = array_merge(
$context,
['docgen:expects' => PhoneNumber::class, 'groups' => 'docgen:read']
);
if (null === $object && 'docgen' === $format) {
return [...self::NULL_USER, 'phonenumber' => $this->normalizer->normalize(null, $format, $phonenumberContext), 'civility' => $this->normalizer->normalize(null, $format, $civilityContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'current_location' => $this->normalizer->normalize(null, $format, $locationContext), 'main_location' => $this->normalizer->normalize(null, $format, $locationContext)];
return [...self::NULL_USER, 'civility' => $this->normalizer->normalize(null, $format, $civilityContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'current_location' => $this->normalizer->normalize(null, $format, $locationContext), 'main_location' => $this->normalizer->normalize(null, $format, $locationContext)];
}
$data = [
@@ -80,7 +74,6 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
'text_without_absent' => $this->userRender->renderString($object, ['absence' => false]),
'label' => $object->getLabel(),
'email' => (string) $object->getEmail(),
'phonenumber' => $this->normalizer->normalize($object->getPhonenumber(), $format, $phonenumberContext),
'user_job' => $this->normalizer->normalize($object->getUserJob(), $format, $userJobContext),
'main_center' => $this->normalizer->normalize($object->getMainCenter(), $format, $centerContext),
'main_scope' => $this->normalizer->normalize($object->getMainScope(), $format, $scopeContext),

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Controller;
use Chill\MainBundle\Test\PrepareClientTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* @internal
*
* @coversNothing
*/
final class UserProfileControllerTest extends WebTestCase
{
use PrepareClientTrait;
public function testPage()
{
$client = $this->getClientAuthenticated();
$client->request('GET', '/fr/main/user/my-profile');
$this->assertResponseIsSuccessful('Request GET /main/user/my-profile was successful');
}
}

View File

@@ -1,140 +0,0 @@
<?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 Serializer\Normalizer;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Serializer\Normalizer\UserNormalizer;
use Chill\MainBundle\Templating\Entity\UserRender;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberUtil;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* @internal
*
* @coversNothing
*/
final class UserNormalizerTest extends TestCase
{
use ProphecyTrait;
/**
* @throws NumberParseException
*/
public function dataProviderUserNormalizer()
{
$user = new User();
$userNoPhone = new User();
$user
->setUsername('SomeUser')
->setLabel('SomeUser')
->setPhonenumber(PhoneNumberUtil::getInstance()->parse('+32475928635'))
->setEmail('some.user@chill.com');
$userNoPhone
->setUsername('AnotherUser')
->setLabel('AnotherUser');
yield [$user, 'docgen', ['docgen:expects' => User::class],
[
'id' => $user->getId(), // id
'type' => 'user', // type
'username' => 'SomeUser', // username
'email' => 'some.user@chill.com', // email
'text' => 'SomeUser', // text
'label' => 'SomeUser', // label
'phonenumber' => ['context' => PhoneNumber::class], // phonenumber
'main_scope' => ['context' => Scope::class], // scope
'user_job' => ['context' => UserJob::class], // user job
'current_location' => ['context' => Location::class], // curent location
'main_location' => ['context' => Location::class], // main location
'civility' => ['context' => Civility::class], // civility
'text_without_absent' => 'SomeUser',
'isAbsent' => false,
'main_center' => ['context' => Center::class],
]];
yield [$userNoPhone, 'docgen', ['docgen:expects' => User::class],
[
'id' => $user->getId(), // id
'type' => 'user', // type
'username' => 'AnotherUser', // username
'email' => '', // email
'text' => 'AnotherUser', // text
'label' => 'AnotherUser', // label
'phonenumber' => ['context' => PhoneNumber::class], // phonenumber
'main_scope' => ['context' => Scope::class], // scope
'user_job' => ['context' => UserJob::class], // user job
'current_location' => ['context' => Location::class], // curent location
'main_location' => ['context' => Location::class], // main location
'civility' => ['context' => Civility::class], // civility
'text_without_absent' => 'AnotherUser',
'isAbsent' => false,
'main_center' => ['context' => Center::class],
]];
yield [null, 'docgen', ['docgen:expects' => User::class], [
'id' => '', // id
'type' => 'user', // type
'username' => '', // username
'email' => '', // email
'text' => '', // text
'label' => '', // label
'phonenumber' => ['context' => PhoneNumber::class], // phonenumber
'main_scope' => ['context' => Scope::class], // scope
'user_job' => ['context' => UserJob::class], // user job
'current_location' => ['context' => Location::class], // curent location
'main_location' => ['context' => Location::class], // main location
'civility' => ['context' => Civility::class], // civility
'text_without_absent' => '',
'isAbsent' => false,
'main_center' => ['context' => Center::class],
]];
}
/**
* @dataProvider dataProviderUserNormalizer
*
* @throws ExceptionInterface
*/
public function testNormalize(null|User $user, mixed $format, mixed $context, mixed $expected)
{
$userRender = $this->prophesize(UserRender::class);
$userRender->renderString(Argument::type(User::class), Argument::type('array'))->willReturn($user ? $user->getLabel() : '');
$normalizer = new UserNormalizer($userRender->reveal());
$normalizer->setNormalizer(new class () implements NormalizerInterface {
public function normalize($object, string $format = null, array $context = [])
{
return ['context' => $context['docgen:expects'] ?? null];
}
public function supportsNormalization($data, string $format = null)
{
return true;
}
});
$this->assertEquals($expected, $normalizer->normalize($user, $format, $context));
}
}

View File

@@ -1,37 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add phonenumber to user profile.
*/
final class Version20231020075524 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add phonenumber to user profile';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD phonenumber VARCHAR(35) DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN users.phonenumber IS \'(DC2Type:phone_number)\'');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE users DROP phonenumber');
}
}

View File

@@ -44,13 +44,6 @@ address_fields: Données liées à l'adresse
Datas: Données
No title: Aucun titre
user:
profile:
title: Mon profil
Phonenumber successfully updated!: Numéro de téléphone mis à jour!
no job: Pas de métier assigné
no scope: Pas de cercle assigné
inactive: inactif
Edit: Modifier

View File

@@ -39,13 +39,6 @@ Last updated by: Laatste update door
on: "op "
Last updated on: Laatste update op
by_user: "door "
lifecycleUpdate: Updates en creatie gebeurtenissen
address_fields: Gegevens gelinked aan het adres
Datas: Gegevens
No title: Geen titel
User profile: Mijn gebruikersprofiel
Phonenumber successfully updated!: Telefoonnummer bijgewerkt!
Edit: Bewerken
Update: Updaten

View File

@@ -70,7 +70,7 @@ class ChildrenNumberAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data)
{
return static function (null|int|string $value): string {
return static function ($value): string {
if ('_header' === $value) {
return 'Number of children';
}
@@ -79,7 +79,7 @@ class ChildrenNumberAggregator implements AggregatorInterface
return '';
}
return (string) $value;
return $value;
};
}

View File

@@ -206,7 +206,6 @@ class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements L
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY);
$qb = $this->entityManager->createQueryBuilder();
@@ -221,7 +220,7 @@ class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements L
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
// get participants at the given date
->andWhere('acppart.startDate <= :calc_date AND (acppart.endDate > :calc_date OR acppart.endDate IS NULL)')
->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate));
->setParameter('calc_date', $this->rollingDateConverter->convert($data['calc_date']));
if ($this->filterStatsByCenters) {
$qb
@@ -237,7 +236,7 @@ class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements L
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($calcDate));
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
return $qb;
}

View File

@@ -206,7 +206,6 @@ class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ListInterface,
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY);
$qb = $this->entityManager->createQueryBuilder();
@@ -232,7 +231,7 @@ class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ListInterface,
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($calcDate));
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
return $qb;
}

View File

@@ -332,18 +332,13 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
if (null === $value) {
return '';
}
$decoded = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
if ('_header' === $value) {
$label = $cfType->getChoices($cf)[$slugChoice];
return $this->translatableStringHelper->localize($cf->getName())
.' | '.$label;
}
try {
$decoded = json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new \RuntimeException(sprintf('unable to decode json: %s, %s', json_last_error(), json_last_error_msg()), $e->getCode(), $e);
.' | '.$label;
}
if ('_other' === $slugChoice && $cfType->isChecked($cf, $slugChoice, $decoded)) {

View File

@@ -15,7 +15,6 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -60,13 +59,7 @@ class CreatorFilter implements FilterInterface
{
return [
'Filtered by creator: only %creators%', [
'%creators%' => implode(
', ',
array_map(
static fn (User $u) => $u->getLabel(),
$data['accepted_creators'] instanceof Collection ? $data['accepted_creators']->toArray() : $data['accepted_creators']
)
),
'%creators%' => implode(', ', array_map(static fn (User $u) => $u->getLabel(), $data['accepted_creators'])),
], ];
}

View File

@@ -23,7 +23,6 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -69,10 +68,7 @@ class GeographicalUnitStatFilter implements FilterInterface
'acp_geog_filter_date',
$this->rollingDateConverter->convert($data['date_calc'])
)
->setParameter('acp_geog_filter_units', array_map(
static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id,
$data['units'] instanceof Collection ? $data['units']->toArray() : $data['units']
));
->setParameter('acp_geog_filter_units', array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $data['units']));
}
public function applyOn(): string

View File

@@ -16,7 +16,6 @@ use Chill\PersonBundle\Export\Declarations;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -53,10 +52,7 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface
[
'%3parties%' => implode(
', ',
array_map(
fn (ThirdParty $thirdParty) => $this->thirdPartyRender->renderString($thirdParty, []),
$data['handling_3parties'] instanceof Collection ? $data['handling_3parties']->toArray() : $data['handling_3parties']
)
array_map(fn (ThirdParty $thirdParty) => $this->thirdPartyRender->renderString($thirdParty, []), $data['handling_3parties'])
),
],
];

View File

@@ -21,7 +21,6 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -116,7 +115,7 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface
', ',
array_map(
fn (UserJob $userJob) => $this->translatableStringHelper->localize($userJob->getLabel()),
$data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
$data['jobs']
)
),
'%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'),

View File

@@ -21,7 +21,6 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -111,7 +110,7 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface
', ',
array_map(
fn (Scope $scope) => $this->translatableStringHelper->localize($scope->getName()),
$data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes']
$data['scopes']
)
),
'%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'),

View File

@@ -17,7 +17,6 @@ use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -99,10 +98,7 @@ class StepFilterBetweenDates implements FilterInterface
public function describeAction($data, $format = 'string')
{
$steps = array_map(
fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]),
$data['accepted_steps_multi'] instanceof Collection ? $data['accepted_steps_multi']->toArray() : $data['accepted_steps_multi']
);
$steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']);
return ['export.filter.course.by_step.Filtered by steps: only %step% and between %date_from% and %date_to%', [
'%step%' => implode(', ', $steps),

View File

@@ -17,7 +17,6 @@ use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -102,10 +101,7 @@ class StepFilterOnDate implements FilterInterface
public function describeAction($data, $format = 'string')
{
$steps = array_map(
fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]),
$data['accepted_steps_multi'] instanceof Collection ? $data['accepted_steps_multi']->toArray() : $data['accepted_steps_multi']
);
$steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']);
return ['Filtered by steps: only %step%', [
'%step%' => implode(', ', $steps),

View File

@@ -20,7 +20,6 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -73,7 +72,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface
', ',
array_map(
fn (User $u) => $this->userRender->renderString($u, []),
$data['users'] instanceof Collection ? $data['users']->toArray() : $data['users']
$data['users']
)
),
'%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'),

View File

@@ -21,7 +21,6 @@ use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepositoryInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@@ -85,7 +84,7 @@ class ByHouseholdCompositionFilter implements FilterInterface
{
$compos = array_map(
fn (HouseholdCompositionType $compositionType) => $this->translatableStringHelper->localize($compositionType->getLabel()),
$data['compositions'] instanceof Collection ? $data['compositions']->toArray() : $data['compositions']
$data['compositions']->toArray()
);
return ['export.filter.person.by_composition.Filtered by composition at %date%: only %compositions%', [

View File

@@ -77,7 +77,6 @@ class GenderFilter implements
'Woman' => Person::FEMALE_GENDER,
'Man' => Person::MALE_GENDER,
'Both' => Person::BOTH_GENDER,
'Unknown' => Person::NO_INFORMATION,
'Not given' => 'null',
],
'multiple' => true,

View File

@@ -20,7 +20,6 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -103,7 +102,7 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface
', ',
array_map(
fn (SimpleGeographicalUnitDTO $item) => $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()).' > '.$item->unitName,
$data['units'] instanceof Collection ? $data['units']->toArray() : $data['units']
$data['units']
)
),
],

View File

@@ -15,7 +15,6 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -56,13 +55,7 @@ class CreatorFilter implements FilterInterface
{
return [
'export.filter.work.by_creator.Filtered by creator: only %creators%', [
'%creators%' => implode(
', ',
array_map(
static fn (User $u) => $u->getLabel(),
$data['creators'] instanceof Collection ? $data['creators']->toArray() : $data['creators']
)
),
'%creators%' => implode(', ', array_map(static fn (User $u) => $u->getLabel(), $data['creators'])),
],
];
}

View File

@@ -144,9 +144,6 @@ class SocialWorkTypeFilter implements FilterInterface
$ids = [];
foreach ($asIterable as $value) {
if (null === $value) {
continue;
}
$ids[] = $value->getId();
}

View File

@@ -1,4 +1,5 @@
<template>
<teleport to="#export_filters_social_work_type_filter_form">
<fieldset class="mb-3" id="actionType">
<div class="row">
@@ -13,7 +14,7 @@
:multiple="true"
:close-on-select="false"
:placeholder="$t('action.placeholder')"
:custom-label="formatSocialAction"
label="text"
track-by="id"
:searchable="true"
></VueMultiselect>
@@ -67,6 +68,8 @@
</div>
</div>
</fieldset>
</teleport>
</template>
<script>
@@ -147,118 +150,72 @@ export default {
}
},
},
async mounted() {
await this.getSocialActionsList();
mounted() {
this.getSocialActionsList();
if ('' !== this.actions.hiddenField.value) {
const actionIds = this.actions.hiddenField.value.split(',');
for (const aid of actionIds) {
let action = this.actions.options.find(a => Number.parseInt(aid) === a.id);
if (undefined !== action) {
this.action.push(action);
await this.selectAction(action);
}
}
}
this.actions.hiddenField.value = '';
this.goals.hiddenField.value = '';
this.results.hiddenField.value = '';
if ('' !== this.goals.hiddenField.value) {
const goalsIds = this.goals.hiddenField.value.split(',').map(s => Number.parseInt(s));
for (const gid of goalsIds) {
let goal = this.goals.options.find(g => gid === g.id);
if (undefined !== goal) {
this.goal.push(goal);
await this.selectGoal(goal);
}
}
}
if ('' !== this.results.hiddenField.value) {
const resultsIds = this.results.hiddenField.value.split(',').map(s => Number.parseInt(s));
for (const rid of resultsIds) {
let result = this.results.options.find(r => rid === r.id);
if (undefined !== result) {
this.result.push(result);
}
}
}
//console.log(this.actions.hiddenField, this.goals.hiddenField, this.results.hiddenField);
},
methods: {
async getSocialActionsList() {
let actions = await getSocialActions();
this.actions.options = actions.toSorted(function (a, b) {
if (a.issue.ordering === b.issue.ordering) {
if (a.ordering === b.ordering) {
return 0;
}
if (a.ordering < b.ordering) {
return -1;
}
return 1;
}
if (a.issue.ordering < b.issue.ordering) {
return -1;
}
return 1;
})
return Promise.resolve();
this.actions.options = await getSocialActions();
},
formatSocialAction({text, issue}) {
return text + ' (' + issue.text + ')';
},
/**
* Select/unselect in Action Multiselect
* @param value
*/
async selectAction(value) {
selectAction(value) {
//console.log('----'); console.log('select action', value.id);
let children = this.getChildrensFromParent(value);
this.addSelectedElement('actions', children);
let parentAndChildren = [...[value], ...children];
const promises = [];
parentAndChildren.forEach(elem => {
promises.push(getGoalByAction(elem.id).then(goals => {
this.addElementInData('goals', goals);
return Promise.resolve();
}));
promises.push(getResultByAction(elem.id).then(results => {
this.addElementInData('results', results);
return Promise.resolve();
}));
getGoalByAction(elem.id).then(response => new Promise((resolve, reject) => {
this.addElementInData('goals', response.results);
resolve();
})).catch;
getResultByAction(elem.id).then(response => new Promise((resolve, reject) => {
this.addElementInData('results', response.results);
resolve();
})).catch;
});
await Promise.all(promises);
return Promise.resolve();
},
unselectAction(value) {
getGoalByAction(value.id).then(goals => {
[this.results.options, this.results.value ] = this.removeElementInData('goals', goals);
});
getResultByAction(value.id).then(results => {
[this.results.options, this.results.value ] = this.removeElementInData('results', results);
});
//console.log('----'); console.log('unselect action', value.id);
getGoalByAction(value.id).then(response => new Promise((resolve, reject) => {
[ this.goals.options, this.goals.value ] = this.removeElementInData('goals', response.results);
resolve();
})).catch;
getResultByAction(value.id).then(response => new Promise((resolve, reject) => {
[ this.results.options, this.results.value ] = this.removeElementInData('results', response.results);
resolve();
})).catch;
},
/**
* Select/unselect in Goal Multiselect
* @param value
*/
async selectGoal(value) {
return getResultByGoal(value.id).then(results => {
this.addElementInData('results', results);
})
selectGoal(value) {
//console.log('----'); console.log('select goal', value.id);
getResultByGoal(value.id).then(response => new Promise((resolve, reject) => {
this.addElementInData('results', response.results);
resolve();
})).catch;
},
unselectGoal(value) {
getResultByGoal(value.id).then(results => {
[ this.results.options, this.results.value ] = this.removeElementInData('results', results);
}).catch;
//console.log('----'); console.log('unselect goal', value.id);
getResultByGoal(value.id).then(response => new Promise((resolve, reject) => {
[ this.results.options, this.results.value ] = this.removeElementInData('results', response.results);
resolve();
})).catch;
},
/**
@@ -306,7 +263,6 @@ export default {
if (dump.length > 0) {
//console.log('push ' + dump.length + ' elems in', target, dump);
}
data.options.sort();
},
/**

View File

@@ -8,17 +8,29 @@ const getSocialActions = () => fetchResults(
const getGoalByAction = (id) => {
let url = `/api/1.0/person/social-work/goal/by-social-action/${id}.json`;
return fetchResults(url);
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getResultByAction = (id) => {
let url = `/api/1.0/person/social-work/result/by-social-action/${id}.json`;
return fetchResults(url);
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getResultByGoal = (id) => {
let url = `/api/1.0/person/social-work/result/by-goal/${id}.json`;
return fetchResults(url);
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
export {
@@ -26,4 +38,4 @@ export {
getGoalByAction,
getResultByAction,
getResultByGoal,
}
}

View File

@@ -4,13 +4,11 @@ import App from './App.vue';
if (null !== document.getElementById('export_filters_social_work_type_filter_enabled')) {
const i18n = _createI18n({});
const form = document.getElementById('export_filters_social_work_type_filter_form');
const after = form.appendChild(document.createElement('div'));
const app = createApp({
template: `<app></app>`,
})
.use(i18n)
.component('app', App)
.mount(after);
.mount('#export_export');
}

View File

@@ -12,7 +12,7 @@ const visMessages = {
Holder: 'Titulaire',
Legend: 'Calques',
concerned: 'concerné',
// both: 'neutre, non binaire',
both: 'neutre, non binaire',
woman: 'féminin',
man: 'masculin',
undefined: "genre non précisé",
@@ -64,9 +64,8 @@ const visMessages = {
placeholder: "Choisissez le genre de l'usager",
woman: "Féminin",
man: "Masculin",
both: "Neutre, non binaire",
undefined: "Non renseigné",
unknown: "Non renseigné"
neuter: "Neutre, non binaire",
undefined: "Non renseigné"
}
},
error_only_one_person: "Une seule personne peut être sélectionnée !",

View File

@@ -153,8 +153,6 @@ const getGender = (gender) => {
return visMessages.fr.visgraph.woman
case 'man':
return visMessages.fr.visgraph.man
case 'unknown':
return visMessages.fr.visgraph.unknown
default:
return visMessages.fr.visgraph.undefined
}

View File

@@ -81,7 +81,7 @@
<li v-else-if="options.addNoData">
<i class="fa fa-li fa-map-marker"></i><p class="chill-no-data-statement">{{ $t('renderbox.no_data') }}</p>
</li>
<template v-if="this.showResidentialAddresses && (person.current_residential_addresses || []).length > 0">
<li v-for="(addr, i) in person.current_residential_addresses" :key="i">
<i class="fa fa-li fa-map-marker"></i>
@@ -223,13 +223,13 @@ export default {
}
},
getGenderIcon: function () {
return this.person.gender === 'woman' ? 'fa-venus' : this.person.gender === 'man' ? 'fa-mars' : this.person.gender === 'both' ? 'fa-neuter' : 'fa-genderless';
return this.person.gender === 'woman' ? 'fa-venus' : this.person.gender === 'man' ? 'fa-mars' : this.person.gender === 'neuter' ? 'fa-neuter' : 'fa-genderless';
},
getGenderTranslation: function () {
return this.person.gender === 'woman' ? 'renderbox.birthday.woman' : 'renderbox.birthday.man';
},
getGender() {
return this.person.gender === 'woman' ? 'person.gender.woman' : this.person.gender === 'man' ? 'person.gender.man' : this.person.gender === 'both' ? 'person.gender.both' : 'person.gender.undefined';
return this.person.gender === 'woman' ? 'person.gender.woman' : this.person.gender === 'man' ? 'person.gender.man' : this.person.gender === 'neuter' ? 'person.gender.neuter' : 'person.gender.undefined';
},
birthdate: function () {
if (this.person.birthdate !== null || this.person.birthdate === "undefined") {

View File

@@ -82,7 +82,7 @@
<option selected disabled >{{ $t('person.gender.placeholder') }}</option>
<option value="woman">{{ $t('person.gender.woman') }}</option>
<option value="man">{{ $t('person.gender.man') }}</option>
<option value="both">{{ $t('person.gender.both') }}</option>
<option value="neuter">{{ $t('person.gender.neuter') }}</option>
</select>
<label>{{ $t('person.gender.title') }}</label>
</div>
@@ -291,12 +291,8 @@ export default {
return 'fa-venus';
case 'man':
return 'fa-mars';
case 'both':
case 'neuter':
return 'fa-neuter';
case 'unknown':
return 'fa-genderless';
default:
return 'fa-genderless';
}
},
genderTranslation() {
@@ -305,12 +301,8 @@ export default {
return 'person.gender.woman';
case 'man':
return 'person.gender.man';
case 'both':
return 'person.gender.both';
case 'unknown':
return 'person.gender.unknown';
default:
return 'person.gender.unknown';
case 'neuter':
return 'person.gender.neuter';
}
},
feminized() {

View File

@@ -36,8 +36,7 @@ const personMessages = {
placeholder: "Choisissez le genre de l'usager",
woman: "Féminin",
man: "Masculin",
both: "Neutre, non binaire",
unknown: "Non renseigné",
neuter: "Neutre, non binaire",
undefined: "Non renseigné"
},
civility: {

View File

@@ -86,9 +86,9 @@
</div>
{%- if options['addInfo'] -%}
{% set gender = (person.gender == 'woman') ? 'fa-venus' :
(person.gender == 'man') ? 'fa-mars' : (person.gender == 'both') ? 'fa-neuter' : 'fa-genderless' %}
(person.gender == 'man') ? 'fa-mars' : (person.gender == 'neuter') ? 'fa-neuter' : 'fa-genderless' %}
{% set genderTitle = (person.gender == 'woman') ? 'woman' :
(person.gender == 'man') ? 'man' : (person.gender == 'both') ? 'both' : 'Not given'|trans %}
(person.gender == 'man') ? 'man' : (person.gender == 'neuter') ? 'neuter' : 'Not given'|trans %}
<p class="moreinfo">
<i class="fa fa-fw {{ gender }}" title="{{ genderTitle|trans }}"></i>

View File

@@ -35,7 +35,6 @@ class SocialIssueNormalizer implements ContextAwareNormalizerInterface, Normaliz
'children_ids' => $socialIssue->getChildren()->map(static fn (SocialIssue $si) => $si->getId()),
'title' => $socialIssue->getTitle(),
'text' => $this->render->renderString($socialIssue, []),
'ordering' => $socialIssue->getOrdering(),
];
case 'docgen':

View File

@@ -1,33 +0,0 @@
<?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;
/**
* Change gender instances of 'neuter' to 'both'.
*/
final class Version20231121070151 extends AbstractMigration
{
public function getDescription(): string
{
return 'Change gender instances of "neuter" to "both"';
}
public function up(Schema $schema): void
{
$this->addSql("UPDATE chill_person_person SET gender = 'both' WHERE chill_person_person.gender = 'neuter'");
}
public function down(Schema $schema): void {}
}

View File

@@ -61,10 +61,10 @@ Spoken languages': 'Langues parlées'
'Unknown spoken languages': 'Langues parlées inconnues'
Male: Homme
Female: Femme
#Both: Neutre
Neuter: Neutre
man: Homme
woman: Femme
#both: Neutre
neuter: Neutre
Man: Homme
Woman: Femme
both: Indéterminé

View File

@@ -61,14 +61,14 @@ Remove phone: Verwijderen
'Unknown spoken languages': 'Gesproken talen ongekend'
Male: Man
Female: Vrouw
Both: Non-binair
Neuter: Non-binair
man: Man
woman: Vrouw
both: Non-binair
neuter: Non-binair
Man: Man
Woman: Vrouw
#both: Onbepaald
#Both: Onbepaald
both: Onbepaald
Both: Onbepaald
Divorced: Gescheiden
Separated: Uit elkaar
Widow: Weduwe/weduwnaar