Compare commits

..

51 Commits

Author SHA1 Message Date
dab80a84d8 Merge branch '235-add-date-filter-course-by-activity' into 'master'
Add dates in the filter "filter course by activity type"

Closes #235

See merge request Chill-Projet/chill-bundles!632
2023-12-07 15:25:09 +00:00
68e00dc42f Merge branch '234-fix-date-validation-aside-activity-filter-by-date' into 'master'
Fix "filter aside activity by date"

Closes #234

See merge request Chill-Projet/chill-bundles!631
2023-12-07 15:24:16 +00:00
5fae49821f Merge branch '191-export-number-of-household-by-activities' into 'master'
Export: add export "number of household associated with an activity"

Closes #191

See merge request Chill-Projet/chill-bundles!629
2023-12-07 15:23:43 +00:00
0e599a99a7 Merge branch '233-fix-filter-by-evaluation-type' into 'master'
Fix filter evaluation by evaluation type

Closes #233

See merge request Chill-Projet/chill-bundles!628
2023-12-07 15:23:24 +00:00
fa3b305ab9 Merge branch 'issue214_fix_error_empty_comment' into 'master'
Notification: avoid the comment's content to be null

Closes #214

See merge request Chill-Projet/chill-bundles!623
2023-12-05 16:26:06 +00:00
b26b7a2706 Notification: avoid the comment's content to be null 2023-12-05 16:26:06 +00:00
fe54e51362 Add dates in the filter "filter course by activity type" 2023-12-04 18:17:00 +01:00
179e3e92ed Fix "filter aside activity by date" 2023-12-04 17:30:40 +01:00
c00c6066a9 Merge branch 'fix-rector-issues-20231204' into 'master'
Fix issues with new version of rector

See merge request Chill-Projet/chill-bundles!630
2023-12-04 16:11:38 +00:00
f424c5464f Fix issues with new version of rector 2023-12-04 16:46:48 +01:00
b17b2a8cfb Export: add export "number of household associated with an activity" 2023-12-04 16:39:01 +01:00
00de657cae Fix filter evaluation by evaluation type 2023-12-01 09:46:02 +01:00
673518e0eb Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2023-11-30 12:15:47 +01:00
4700a0fef7 update URL for postal code FR import 2023-11-30 12:15:22 +01:00
f4d258882f Release 2.14.1 2023-11-29 22:23:42 +01:00
fffc4a9c33 Fix error on listEvaluation
- force default calcDate
- Export / UserHelper: handle case when a single user is given, when we expect more than one user
2023-11-29 22:22:06 +01:00
a344f36592 Merge branch 'issue100_add_paginator_budget_admin' into 'master'
add paginator to budget admin pages

See merge request Chill-Projet/chill-bundles!624
2023-11-29 20:21:42 +00:00
eef93b8a0d add changie 2023-11-29 11:33:26 +01:00
75b78d3c99 add paginator to budget admin pages 2023-11-28 17:27:26 +01:00
3d4c439be4 Merge branch 'fix-list-person-custom-field' into 'master'
Fix "PersonList" with custom fields

See merge request Chill-Projet/chill-bundles!622
2023-11-28 11:03:26 +00:00
4727a57825 Export: ListPerson: avoid json_decoding the string '_header' 2023-11-27 21:02:39 +01:00
5f441eb5ac Fix path to csv template 2023-11-27 21:01:01 +01:00
2e4e5ee79a Release v2.14.0 2023-11-24 12:39:13 +01:00
1467c708f2 Merge branch '222-fix-custom-fields' into 'master'
Fix errors in custom field rendering and administration

See merge request Chill-Projet/chill-bundles!620
2023-11-24 11:38:07 +00:00
170bb9586d Fix errors in custom field rendering and administration 2023-11-24 12:30:18 +01:00
c704ffa379 Merge branch '161-fix-ordering-filter-social-action-type' into 'master'
Export: Fix vue app in "filter action by type, goals and result"

Closes #219, #141, and #161

See merge request Chill-Projet/chill-bundles!616
2023-11-23 15:50:57 +00:00
947b7b90e2 Fix SocialWorkTypeFilter: allow null value in date, and avoid vuejs module ExportFormActionGoal to override the export form content 2023-11-23 16:02:55 +01:00
992f7761bb Fix cs 2023-11-21 15:17:02 +01:00
35170e1f7c release 2.13.0 2023-11-21 14:14:11 +01:00
7132dfa3f6 Merge branch '211-errors-exports' into 'master'
Resolve multiple errors in filters and aggregators

Closes #213 and #211

See merge request Chill-Projet/chill-bundles!617
2023-11-21 12:53:27 +00:00
7e09e0ea54 Merge branch 'issue215_gender_neuter_fix' into 'master'
change all instances of neuter to both

See merge request Chill-Projet/chill-bundles!618
2023-11-21 12:53:13 +00:00
ccf8cc4d6e Merge branch 'fix-phpstan-2023-11-21' into 'master'
Fix type-hinting of DocGenObjectNormalizer and related

See merge request Chill-Projet/chill-bundles!619
2023-11-21 12:48:30 +00:00
63124f8f92 Fix type-hinting of DocGenObjectNormalizer and related 2023-11-21 13:37:23 +01:00
75d80ebd98 Fix migration syntax 2023-11-21 12:13:27 +01:00
0ea6f36297 styles fixes 2023-11-21 11:59:03 +01:00
975ea417b7 Further update gender options 2023-11-21 11:37:19 +01:00
af8e02f76b Migration to change instances of gender neuter to both 2023-11-21 08:13:46 +01:00
cbaeb3d7e8 fix double translation keys 2023-11-21 08:13:25 +01:00
f609ddb315 change all instances of neuter to both 2023-11-21 07:58:45 +01:00
d0bceb59dc Export: fix usage of some Collection returned instead of array in export filters 2023-11-20 18:15:53 +01:00
2883e085ed DX: Fix aggregator: "group activity by reason" 2023-11-20 18:07:06 +01:00
b05ed86d1e DX: Fix aggregator: "group activity by type" 2023-11-20 18:06:55 +01:00
c855d0badc Merge branch 'add_phonenumber_to_user' into 'master'
Add phonenumber to user

See merge request Chill-Projet/chill-bundles!600
2023-11-16 15:12:12 +00:00
be57c96a2f Add phonenumber to user 2023-11-16 15:12:12 +00:00
eb01c7c203 Export: on filter "action by type goals, and results", restore the fields when editing a saved export 2023-11-16 14:22:52 +01:00
53b4747697 fixup! Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order 2023-11-16 11:55:15 +01:00
89e19502d3 Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order 2023-11-16 11:53:57 +01:00
ff344dbb0c Release 2.12.1 2023-11-16 11:07:34 +01:00
8719b4dedd Merge branch '208-export-fix-loading-by-type-goal-result' into 'master'
Resolve "Export: le chargement de l'app vue de "filtrer les actions par type, objectif et résultat" ne se charge pas sur certains exports"

Closes #208

See merge request Chill-Projet/chill-bundles!615
2023-11-16 10:05:56 +00:00
d8fa743bc9 Export: fix loading of form for "filter action by type, goal and result" 2023-11-16 10:57:59 +01:00
eaa40d6725 DX: remove some unnecessary console.log 2023-11-16 10:57:32 +01:00
89 changed files with 1394 additions and 260 deletions

View File

@@ -0,0 +1,5 @@
kind: Feature
body: Add export "number of household associate with an exchange"
time: 2023-12-04T16:29:23.341485328+01:00
custom:
Issue: "191"

View File

@@ -0,0 +1,5 @@
kind: Feature
body: 'Export: add dates on the filter "filter course by activity type"'
time: 2023-12-04T18:11:56.906524311+01:00
custom:
Issue: "235"

View File

@@ -0,0 +1,5 @@
kind: Fixed
body: Fix error when posting an empty comment on an accompanying period.
time: 2023-11-29T11:34:58.986983057+01:00
custom:
Issue: "214"

View File

@@ -0,0 +1,5 @@
kind: Fixed
body: Fix "filter evaluation by evaluation type" (and add select2 to the list of evaluation types to pick)
time: 2023-12-01T09:45:10.744382859+01:00
custom:
Issue: "233"

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: |
Fix "filter aside activity by date"
time: 2023-12-04T17:17:16.553597209+01:00
custom:
Issue: "234"

View File

@@ -15,7 +15,7 @@
* 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 différents changements
### 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);

3
.changes/v2.12.1.md Normal file
View File

@@ -0,0 +1,3 @@
## 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"

9
.changes/v2.13.0.md Normal file
View File

@@ -0,0 +1,9 @@
## 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

8
.changes/v2.14.0.md Normal file
View File

@@ -0,0 +1,8 @@
## 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

5
.changes/v2.14.1.md Normal file
View File

@@ -0,0 +1,5 @@
## v2.14.1 - 2023-11-29
### Fixed
* Export: fix list person with custom fields
* ([#100](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/100)) Add a paginator to budget elements (resource and charge types) in the admin
* Fix error in ListEvaluation when "handling agents" are alone

View File

@@ -6,6 +6,56 @@ 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

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

View File

@@ -99,12 +99,6 @@ 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

@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
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\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class CountHouseholdOnActivity implements ExportInterface, GroupedExportInterface
{
private EntityRepository $repository;
private bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.count_household_on_activity.description';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity.header' : $value;
}
public function getQueryKeys($data): array
{
return ['export_count_activity'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.export.count_household_on_activity.title';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository
->createQueryBuilder('activity')
->join('activity.persons', 'person')
->join('activity.accompanyingPeriod', 'acp')
->join(
HouseholdMember::class,
'householdmember',
Query\Expr\Join::WITH,
'person.id = IDENTITY(householdmember.person) AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)'
)
->join('householdmember.household', 'household');
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);
$qb->select('COUNT(DISTINCT household.id) as export_count_activity');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
PersonDeclarations::ACP_TYPE,
PersonDeclarations::PERSON_TYPE,
PersonDeclarations::HOUSEHOLD_TYPE,
];
}
}

View File

@@ -0,0 +1,138 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class CountHouseholdOnActivity implements ExportInterface, GroupedExportInterface
{
private bool $filterStatsByCenters;
public function __construct(
private ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
{
return 'export.export.count_household_on_activity_person.description';
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity_person.header' : $value;
}
public function getQueryKeys($data)
{
return ['export_count_activity'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'export.export.count_household_on_activity_person.title';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->activityRepository
->createQueryBuilder('activity')
->join('activity.person', 'person')
->join(
HouseholdMember::class,
'householdmember',
Query\Expr\Join::WITH,
'person = householdmember.person AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)'
)
->join('householdmember.household', 'household');
$qb->select('COUNT(DISTINCT household.id) as export_count_activity');
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers()
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
PersonDeclarations::PERSON_TYPE,
PersonDeclarations::HOUSEHOLD_TYPE,
];
}
}

View File

@@ -15,17 +15,22 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class ActivityTypeFilter implements FilterInterface
final readonly class ActivityTypeFilter implements FilterInterface
{
private const BASE_EXISTS = 'SELECT 1 FROM '.Activity::class.' act_type_filter_activity WHERE act_type_filter_activity.accompanyingPeriod = acp';
public function __construct(
private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
private readonly TranslatableStringHelperInterface $translatableStringHelper
private ActivityTypeRepositoryInterface $activityTypeRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
private RollingDateConverterInterface $rollingDateConverter,
) {}
public function addRole(): ?string
@@ -35,13 +40,26 @@ class ActivityTypeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.Activity::class.' act_type_filter_activity
WHERE act_type_filter_activity.activityType IN (:act_type_filter_activity_types) AND act_type_filter_activity.accompanyingPeriod = acp'
)
);
$qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']);
$exists = self::BASE_EXISTS;
if (count($data['accepted_activitytypes']) > 0) {
$exists .= ' AND act_type_filter_activity.activityType IN (:act_type_filter_activity_types)';
$qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']);
}
if (null !== $data['date_after']) {
$exists .= ' AND act_type_filter_activity.date >= :act_type_filter_activity_date_after';
$qb->setParameter('act_type_filter_activity_date_after', $this->rollingDateConverter->convert($data['date_after']));
}
if (null !== $data['date_before']) {
$exists .= ' AND act_type_filter_activity.date >= :act_type_filter_activity_date_before';
$qb->setParameter('act_type_filter_activity_date_before', $this->rollingDateConverter->convert($data['date_before']));
}
if (self::BASE_EXISTS !== $exists) {
$qb->andWhere($qb->expr()->exists($exists));
}
}
public function applyOn()
@@ -60,11 +78,27 @@ class ActivityTypeFilter implements FilterInterface
'multiple' => true,
'expanded' => true,
]);
$builder->add('date_after', PickRollingDateType::class, [
'label' => 'export.filter.activity.acp_by_activity_type.activity after',
'help' => 'export.filter.activity.acp_by_activity_type.activity after help',
'required' => false,
]);
$builder->add('date_before', PickRollingDateType::class, [
'label' => 'export.filter.activity.acp_by_activity_type.activity before',
'help' => 'export.filter.activity.acp_by_activity_type.activity before help',
'required' => false,
]);
}
public function getFormDefaultData(): array
{
return [];
return [
'accepted_activitytypes' => [],
'date_after' => null,
'date_before' => null,
];
}
public function describeAction($data, $format = 'string'): array
@@ -75,8 +109,12 @@ class ActivityTypeFilter implements FilterInterface
$types[] = $this->translatableStringHelper->localize($aty->getName());
}
return ['export.filter.activity.acp_by_activity_type.acp_containing_at_least_one_%activitytypes%', [
'%activitytypes%' => implode(', ', $types),
return ['export.filter.activity.acp_by_activity_type.acp_containing_at_least_one_activitytypes', [
'activitytypes' => implode(', ', $types),
'has_date_after' => null !== $data['date_after'] ? 1 : 0,
'date_after' => $this->rollingDateConverter->convert($data['date_after']),
'has_date_before' => null !== $data['date_before'] ? 1 : 0,
'date_before' => $this->rollingDateConverter->convert($data['date_before']),
]];
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Export\Export\LinkedToACP\CountHouseholdOnActivity;
use Chill\MainBundle\Test\Export\AbstractExportTest;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class CountHouseholdOnActivityTest extends AbstractExportTest
{
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
}
public function getExport()
{
yield new CountHouseholdOnActivity($this->entityManager, $this->getParameters(true));
yield new CountHouseholdOnActivity($this->entityManager, $this->getParameters(false));
}
public function getFormData()
{
return [
[],
];
}
public function getModifiersCombination()
{
return [
[
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
PersonDeclarations::ACP_TYPE,
PersonDeclarations::PERSON_TYPE,
PersonDeclarations::HOUSEHOLD_TYPE,
],
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Export\Export\LinkedToPerson\CountHouseholdOnActivity;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
/**
* @internal
*
* @coversNothing
*/
class CountHouseholdOnActivityTest extends AbstractExportTest
{
private ActivityRepository $activityRepository;
protected function setUp(): void
{
self::bootKernel();
$this->activityRepository = self::$container->get(ActivityRepository::class);
}
public function getExport()
{
yield new CountHouseholdOnActivity($this->activityRepository, $this->getParameters(true));
yield new CountHouseholdOnActivity($this->activityRepository, $this->getParameters(false));
}
public function getFormData()
{
return [
[],
];
}
public function getModifiersCombination()
{
return [
[
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
PersonDeclarations::PERSON_TYPE,
PersonDeclarations::HOUSEHOLD_TYPE,
],
];
}
}

View File

@@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\Common\Collections\ArrayCollection;
@@ -55,8 +56,30 @@ final class ActivityTypeFilterTest extends AbstractFilterTest
$data = [];
foreach ($array as $a) {
$data[] = [
'accepted_activitytypes' => [],
'date_after' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'date_before' => new RollingDate(RollingDate::T_TODAY),
];
$data[] = [
'accepted_activitytypes' => new ArrayCollection([$a]),
'date_after' => null,
'date_before' => null,
];
$data[] = [
'accepted_activitytypes' => [$a],
'date_after' => null,
'date_before' => null,
];
$data[] = [
'accepted_activitytypes' => [$a],
'date_after' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'date_before' => new RollingDate(RollingDate::T_TODAY),
];
$data[] = [
'accepted_activitytypes' => [],
'date_after' => null,
'date_before' => null,
];
}

View File

@@ -20,6 +20,14 @@ services:
tags:
- { name: chill.export, alias: 'count_person_on_activity' }
Chill\ActivityBundle\Export\Export\LinkedToACP\CountHouseholdOnActivity:
tags:
- { name: chill.export, alias: 'count_household_on_activity_acp' }
Chill\ActivityBundle\Export\Export\LinkedToPerson\CountHouseholdOnActivity:
tags:
- { name: chill.export, alias: 'count_household_on_activity_person' }
chill.activity.export.count_activity_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity
tags:

View File

@@ -3,7 +3,12 @@ export:
activity:
course_having_activity_between_date:
Only course having an activity between from and to: Seulement les parcours ayant reçu au moins un échange entre le {from, date, short} et le {to, date, short}
person_between_dates:
acp_by_activity_type:
'acp_containing_at_least_one_activitytypes': >-
Parcours filtrés: uniquement ceux qui contiennent au moins un échange d'un des types suivants: {activitytypes}
{has_date_after, select, 1 {, après le {date_after, date}} other {}}
{has_date_before, select, 1 {, avant le {date_before, date}} other {}}
describe_action_with_no_subject: >-
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}
describe_action_with_subject: >-

View File

@@ -337,6 +337,14 @@ export:
title: Nombre d'usagers concernés par les échanges
description: Compte le nombre d'usagers concernés par les échanges. Si un usager est présent dans plusieurs échanges, il n'est comptabilisé qu'une seule fois.
header: Nombre d'usagers concernés par des échanges
count_household_on_activity:
title: Nombre de ménages concernés par les échanges
description: Compte le nombre de ménages concernés par les échanges. Si un ménage est présent dans plusieurs échanges, il n'est comptabilisé qu'une seule fois. Les usagers sans ménages ne sont pas comptabilisés.
header: Nombre de ménage concernés par des échanges
count_household_on_activity_person:
title: Nombre de ménages concernés par les échanges
description: Compte le nombre de ménages concernés par les échanges. Si un ménage est présent dans plusieurs échanges, il n'est comptabilisé qu'une seule fois. Les usagers sans ménages ne sont pas comptabilisés. Lorsqu'un usager change de ménage, chaque ménage est comptabilisé une fois.
header: Nombre de ménage concernés par des échanges
list:
activity:
users name: Nom des utilisateurs
@@ -371,7 +379,10 @@ export:
Receiving an activity after: Ayant reçu un échange après le
Receiving an activity before: Ayant reçu un échange avant le
acp_by_activity_type:
'acp_containing_at_least_one_%activitytypes%': 'Parcours filtrés: uniquement ceux qui contiennent au moins un échange d''un des types suivants: %activitytypes%'
'activity after': Échanges après le
activity after help: Si laissé vide, ne sera pas pris en compte
activity before: Echanges avant le
activity before help: Si laissé vide, ne sera pas pris en compte
person_between_dates:
Implied in an activity after this date: Impliqué dans un échange après cette date
Implied in an activity before this date: Impliqué dans un échange avant cette date

View File

@@ -13,15 +13,11 @@ namespace Chill\AsideActivityBundle\Export\Filter;
use Chill\AsideActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Contracts\Translation\TranslatorInterface;
class ByDateFilter implements FilterInterface
@@ -66,51 +62,14 @@ class ByDateFilter implements FilterInterface
->add('date_to', PickRollingDateType::class, [
'label' => 'export.filter.Aside activities before this date',
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var \Symfony\Component\Form\FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
if (true === $enabled) {
// if the filter is enabled, add some validation
$form = $event->getForm();
$date_from = $form->get('date_from')->getData();
$date_to = $form->get('date_to')->getData();
// check that fields are not empty
if (null === $date_from) {
$form->get('date_from')->addError(new FormError(
$this->translator->trans('This field '
.'should not be empty')
));
}
if (null === $date_to) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This field '
.'should not be empty')
));
}
// check that date_from is before date_to
if (
(null !== $date_from && null !== $date_to)
&& $date_from >= $date_to
) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('export.filter.This date should be after '
.'the date given in "Implied in an aside activity after '
.'this date" field')
));
}
}
});
}
public function getFormDefaultData(): array
{
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
return [
'date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'date_to' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string'): array

View File

@@ -23,6 +23,6 @@ class ChargeKindController extends CRUDController
/* @var QueryBuilder $query */
$query->addOrderBy('e.ordering', 'ASC');
return $query;
return parent::orderQuery($action, $query, $request, $paginator);
}
}

View File

@@ -23,6 +23,6 @@ class ResourceKindController extends CRUDController
/* @var QueryBuilder $query */
$query->addOrderBy('e.ordering', 'ASC');
return $query;
return parent::orderQuery($action, $query, $request, $paginator);
}
}

View File

@@ -30,7 +30,7 @@ class CustomFieldController extends AbstractController
/**
* Creates a new CustomField entity.
*
* @Route("/{_locale}/admin/customfield/new", name="customfield_new")
* @Route("/{_locale}/admin/customfield/new", name="customfield_create")
*/
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 '
.'ChillCustomFieldsBundle:CustomFieldsDefaultGroup d '
.CustomFieldsDefaultGroup::class.' 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 = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice.csv.twig';
$template = '@ChillCustomFields/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($value);
$date = new \DateTime((string) $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 = 'ChillCustomFieldsBundle:CustomFieldsRendering:date.'
$template = '@ChillCustomFields/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 = 'ChillCustomFieldsBundle:CustomFieldsRendering:choice_long.'
$template = '@ChillCustomFields/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 = 'ChillCustomFieldsBundle:CustomFieldsRendering:number.'
$template = '@ChillCustomFields/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 = 'ChillCustomFieldsBundle:CustomFieldsRendering:text.csv.twig';
$template = '@ChillCustomFields/CustomFieldsRendering/text.csv.twig';
}
return $this->templating

View File

@@ -11,6 +11,7 @@ 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;
@@ -45,7 +46,7 @@ class CustomFieldType extends AbstractType
if ('entity' === $options['group_widget']) {
$builder->add('customFieldsGroup', EntityType::class, [
'class' => 'ChillCustomFieldsBundle:CustomFieldsGroup',
'class' => CustomFieldsGroup::class,
'choice_label' => fn ($g) => $this->translatableStringHelper->localize($g->getName()),
]);
} elseif ('hidden' === $options['group_widget']) {

View File

@@ -1,4 +1,8 @@
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\ClassMetadata;
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
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 = [], ClassMetadata $classMetadata = null)
public function normalize(array $attributes, string $format = 'docgen', ?array $context = [], ClassMetadataInterface $classMetadata = null)
{
$data = [];
$data['isNull'] = true;
@@ -44,7 +44,7 @@ class NormalizeNullValueHelper
return $data;
}
private function getContextForAttribute(string $key, array $initialContext, ?ClassMetadata $classMetadata): array
private function getContextForAttribute(string $key, array $initialContext, ?ClassMetadataInterface $classMetadata): array
{
if (null === $classMetadata) {
return $initialContext;

View File

@@ -19,6 +19,7 @@ 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;
@@ -52,12 +53,15 @@ 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 (AttributeMetadata $a) use ($expectedGroups) {
static function (AttributeMetadataInterface $a) use ($expectedGroups) {
foreach ($a->getGroups() as $g) {
if (\in_array($g, $expectedGroups, true)) {
return true;
@@ -119,7 +123,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
return $type->getName();
}
if ($type instanceof \ReflectionIntersectionType) {
foreach (array_map(fn (\ReflectionNamedType $t) => $t->getName(), $type->getTypes()) as $classString) {
foreach (array_map(fn (\ReflectionType $t) => $t->getName(), $type->getTypes()) as $classString) {
if (ReadableCollection::class === $classString) {
return ReadableCollection::class;
}
@@ -211,7 +215,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
}
/**
* @param array|AttributeMetadata[] $attributes
* @param array<AttributeMetadata> $attributes
*
* @return array
*

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\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,9 +18,11 @@ 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.
@@ -161,6 +163,15 @@ 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.
*/
@@ -419,6 +430,11 @@ class User implements UserInterface, \Stringable
}
}
public function getPhonenumber(): ?PhoneNumber
{
return $this->phonenumber;
}
/**
* @throws \RuntimeException if the groupCenter is not in the collection
*/
@@ -639,4 +655,11 @@ class User implements UserInterface, \Stringable
return $this;
}
public function setPhonenumber(?PhoneNumber $phonenumber): self
{
$this->phonenumber = $phonenumber;
return $this;
}
}

View File

@@ -92,6 +92,11 @@ class UserHelper
}
$asStrings = [];
if (array_key_exists('uid', $decoded) || is_numeric($decoded)) {
// this is a single value. We have to wrap it into an array
$decoded = [$decoded];
}
foreach ($decoded as $userId) {
if (is_array($userId)) {
$uid = $userId['uid'];

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\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,6 +16,7 @@ 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;
@@ -44,6 +45,9 @@ 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

@@ -1,7 +1,6 @@
import {ShowHide} from 'ChillMainAssets/lib/show_hide/index';
document.addEventListener('DOMContentLoaded', function(_e) {
console.log('pick-rolling-date');
document.querySelectorAll('div[data-rolling-date]').forEach( (picker) => {
const
roll_wrapper = picker.querySelector('div.roll-wrapper'),
@@ -11,12 +10,8 @@ document.addEventListener('DOMContentLoaded', function(_e) {
froms: [roll_wrapper],
container: [fixed_wrapper],
test: function (elems) {
console.log('testing');
console.log('elems', elems);
for (let el of elems) {
for (let select_roll of el.querySelectorAll('select[data-roll-picker]')) {
console.log('select_roll', select_roll);
console.log('value', select_roll.value);
return select_roll.value === 'fixed_date';
}
}

View File

@@ -28,9 +28,7 @@
{% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('page_export') }}
{% if export_alias == 'count_social_work_actions' %}
{{ encore_entry_script_tags('vue_export_action_goal_result') }}
{% endif %}
{{ encore_entry_script_tags('vue_export_action_goal_result') }}
{{ encore_entry_script_tags('mod_pick_rolling_date') }}
{% endblock js %}

View File

@@ -0,0 +1,58 @@
{#
* 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,6 +29,14 @@ 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,6 +18,7 @@ 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;
@@ -34,6 +35,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
'text_without_absent' => '',
'label' => '',
'email' => '',
'isAbsent' => false,
];
public function __construct(private readonly UserRender $userRender) {}
@@ -61,9 +63,13 @@ 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, '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, '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)];
}
$data = [
@@ -74,6 +80,7 @@ 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

@@ -58,7 +58,7 @@ class AddressReferenceFromBano
foreach ($stmt as $record) {
$this->baseImporter->importAddress(
$record['refId'],
substr($record['refId'], 0, 5), // extract insee from reference
substr((string) $record['refId'], 0, 5), // extract insee from reference
$record['postcode'],
$record['street'],
$record['streetNumber'],

View File

@@ -23,7 +23,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
*/
class PostalCodeFRFromOpenData
{
private const CSV = 'https://datanova.legroupe.laposte.fr/explore/dataset/laposte_hexasmal/download/?format=csv&timezone=Europe/Berlin&lang=fr&use_labels_for_header=true&csv_separator=%3B';
private const CSV = 'https://datanova.laposte.fr/data-fair/api/v1/datasets/laposte-hexasmal/data-files/019HexaSmal.csv';
public function __construct(private readonly PostalCodeBaseImporter $baseImporter, private readonly HttpClientInterface $client, private readonly LoggerInterface $logger) {}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\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

@@ -0,0 +1,140 @@
<?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

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\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,6 +44,13 @@ 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,6 +39,13 @@ 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

@@ -41,7 +41,7 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\Column(type="text")
* @ORM\Column(type="text", nullable=false, options={"default":""})
*
* @Groups({"read", "write", "docgen:read"})
*

View File

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

View File

@@ -206,6 +206,7 @@ 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();
@@ -220,7 +221,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($data['calc_date']));
->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate));
if ($this->filterStatsByCenters) {
$qb
@@ -236,7 +237,7 @@ class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements L
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($calcDate));
return $qb;
}

View File

@@ -206,6 +206,7 @@ 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();
@@ -231,7 +232,7 @@ class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ListInterface,
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($calcDate));
return $qb;
}

View File

@@ -193,6 +193,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
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();
@@ -208,7 +209,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
->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($data['calc_date']));
->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate));
if ($this->filterStatsByCenters) {
$qb
@@ -224,7 +225,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($calcDate));
return $qb;
}
@@ -278,7 +279,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
// referrers => at date XXXX
$qb
->addSelect('(SELECT JSON_BUILD_OBJECT(\'uid\', IDENTITY(history.user), \'d\', history.startDate) FROM '.UserHistory::class.' history '.
'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calc_date AND (history.endDate IS NULL OR history.endDate > :calc_date)) AS referrers');
'WHERE history.accompanyingPeriod = acp AND history.startDate <= :calc_date AND (history.endDate IS NULL OR history.endDate > :calc_date)) AS acpw_referrers');
// persons
$qb

View File

@@ -332,13 +332,18 @@ 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;
.' | '.$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);
}
if ('_other' === $slugChoice && $cfType->isChecked($cf, $slugChoice, $decoded)) {

View File

@@ -15,6 +15,7 @@ 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;
@@ -59,7 +60,13 @@ class CreatorFilter implements FilterInterface
{
return [
'Filtered by creator: only %creators%', [
'%creators%' => implode(', ', array_map(static fn (User $u) => $u->getLabel(), $data['accepted_creators'])),
'%creators%' => implode(
', ',
array_map(
static fn (User $u) => $u->getLabel(),
$data['accepted_creators'] instanceof Collection ? $data['accepted_creators']->toArray() : $data['accepted_creators']
)
),
], ];
}

View File

@@ -23,6 +23,7 @@ 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;
@@ -68,7 +69,10 @@ 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']));
->setParameter('acp_geog_filter_units', array_map(
static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id,
$data['units'] instanceof Collection ? $data['units']->toArray() : $data['units']
));
}
public function applyOn(): string

View File

@@ -16,6 +16,7 @@ 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;
@@ -52,7 +53,10 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface
[
'%3parties%' => implode(
', ',
array_map(fn (ThirdParty $thirdParty) => $this->thirdPartyRender->renderString($thirdParty, []), $data['handling_3parties'])
array_map(
fn (ThirdParty $thirdParty) => $this->thirdPartyRender->renderString($thirdParty, []),
$data['handling_3parties'] instanceof Collection ? $data['handling_3parties']->toArray() : $data['handling_3parties']
)
),
],
];

View File

@@ -21,6 +21,7 @@ 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;
@@ -115,7 +116,7 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface
', ',
array_map(
fn (UserJob $userJob) => $this->translatableStringHelper->localize($userJob->getLabel()),
$data['jobs']
$data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
)
),
'%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'),

View File

@@ -21,6 +21,7 @@ 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;
@@ -110,7 +111,7 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface
', ',
array_map(
fn (Scope $scope) => $this->translatableStringHelper->localize($scope->getName()),
$data['scopes']
$data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes']
)
),
'%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'),

View File

@@ -17,6 +17,7 @@ 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;
@@ -98,7 +99,10 @@ 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']);
$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']
);
return ['export.filter.course.by_step.Filtered by steps: only %step% and between %date_from% and %date_to%', [
'%step%' => implode(', ', $steps),

View File

@@ -17,6 +17,7 @@ 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;
@@ -101,7 +102,10 @@ 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']);
$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']
);
return ['Filtered by steps: only %step%', [
'%step%' => implode(', ', $steps),

View File

@@ -20,6 +20,7 @@ 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;
@@ -72,7 +73,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface
', ',
array_map(
fn (User $u) => $this->userRender->renderString($u, []),
$data['users']
$data['users'] instanceof Collection ? $data['users']->toArray() : $data['users']
)
),
'%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'),

View File

@@ -15,14 +15,14 @@ use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Andx;
use Chill\PersonBundle\Repository\SocialWork\EvaluationRepositoryInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class EvaluationTypeFilter implements FilterInterface
{
public function __construct(private TranslatableStringHelper $translatableStringHelper) {}
public function __construct(private TranslatableStringHelper $translatableStringHelper, private EvaluationRepositoryInterface $evaluationRepository) {}
public function addRole(): ?string
{
@@ -31,16 +31,9 @@ final readonly class EvaluationTypeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('eval.evaluation', ':evaluationtype');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->andWhere(
$qb->expr()->in('workeval.evaluation', ':evaluationtype')
);
$qb->setParameter('evaluationtype', $data['accepted_evaluationtype']);
}
@@ -51,11 +44,17 @@ final readonly class EvaluationTypeFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$evaluations = $this->evaluationRepository->findAllActive();
usort($evaluations, fn (Evaluation $a, Evaluation $b) => $this->translatableStringHelper->localize($a->getTitle()) <=> $this->translatableStringHelper->localize($b->getTitle()));
$builder->add('accepted_evaluationtype', EntityType::class, [
'class' => Evaluation::class,
'choices' => $evaluations,
'choice_label' => fn (Evaluation $ev): string => $this->translatableStringHelper->localize($ev->getTitle()),
'multiple' => true,
'expanded' => true,
'expanded' => false,
'attr' => ['class' => 'select2'],
]);
}

View File

@@ -21,6 +21,7 @@ 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;
@@ -84,7 +85,7 @@ class ByHouseholdCompositionFilter implements FilterInterface
{
$compos = array_map(
fn (HouseholdCompositionType $compositionType) => $this->translatableStringHelper->localize($compositionType->getLabel()),
$data['compositions']->toArray()
$data['compositions'] instanceof Collection ? $data['compositions']->toArray() : $data['compositions']
);
return ['export.filter.person.by_composition.Filtered by composition at %date%: only %compositions%', [

View File

@@ -77,6 +77,7 @@ 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,6 +20,7 @@ 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;
@@ -102,7 +103,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']
$data['units'] instanceof Collection ? $data['units']->toArray() : $data['units']
)
),
],

View File

@@ -15,6 +15,7 @@ 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;
@@ -55,7 +56,13 @@ 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'])),
'%creators%' => implode(
', ',
array_map(
static fn (User $u) => $u->getLabel(),
$data['creators'] instanceof Collection ? $data['creators']->toArray() : $data['creators']
)
),
],
];
}

View File

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

View File

@@ -26,6 +26,7 @@ class AccompanyingCourseCommentType extends AbstractType
{
$builder->add('content', ChillTextareaType::class, [
'required' => false,
'empty_data' => '',
]);
}

View File

@@ -1,11 +1,10 @@
<template>
<teleport to="#export_filters_social_work_type_filter_form">
<fieldset class="mb-3" id="actionType">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('action.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
v-model="action"
:options="actions.options"
@@ -14,20 +13,20 @@
:multiple="true"
:close-on-select="false"
:placeholder="$t('action.placeholder')"
label="text"
:custom-label="formatSocialAction"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
<fieldset class="mb-3" id="goal">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('goal.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
v-model="goal"
:options="goals.options"
@@ -41,16 +40,16 @@
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
<fieldset class="mb-3" id="result">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('result.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
v-model="result"
:options="results.options"
@@ -64,12 +63,10 @@
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
</teleport>
</template>
<script>
@@ -150,74 +147,120 @@ export default {
}
},
},
mounted() {
this.getSocialActionsList();
this.actions.hiddenField.value = '';
this.goals.hiddenField.value = '';
this.results.hiddenField.value = '';
//console.log(this.actions.hiddenField, this.goals.hiddenField, this.results.hiddenField);
async mounted() {
await 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);
}
}
}
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);
}
}
}
},
methods: {
async getSocialActionsList() {
this.actions.options = await getSocialActions();
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();
},
formatSocialAction({text, issue}) {
return text + ' (' + issue.text + ')';
},
/**
* Select/unselect in Action Multiselect
* @param value
*/
selectAction(value) {
async 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 => {
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;
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();
}));
});
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);
});
},
unselectAction(value) {
//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
*/
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;
async selectGoal(value) {
return getResultByGoal(value.id).then(results => {
this.addElementInData('results', results);
})
},
unselectGoal(value) {
//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;
getResultByGoal(value.id).then(results => {
[ this.results.options, this.results.value ] = this.removeElementInData('results', results);
}).catch;
},
/**
* Select/unselect in Result Multiselect
* @param value
@@ -225,11 +268,11 @@ export default {
selectResult(value) {
//console.log('----'); console.log('select result', value.id);
},
unselectResult(value) {
//console.log('----'); console.log('unselect result', value.id);
},
/**
* Choose parent action will involve retaining the "children" actions.
* @param value
@@ -244,7 +287,7 @@ export default {
}
return [];
},
/**
* Add response elements in data target
* @param target string -> 'actions', 'goals' or 'results'
@@ -263,8 +306,9 @@ export default {
if (dump.length > 0) {
//console.log('push ' + dump.length + ' elems in', target, dump);
}
data.options.sort();
},
/**
* Remove response elements from data target
* @param target string -> 'actions', 'goals' or 'results'
@@ -279,7 +323,7 @@ export default {
if (found) {
data.options = data.options.filter(e => e.id !== elem.id);
dump.push(elem.id);
this.removeSelectedElement(target, elem);
}
})
@@ -288,7 +332,7 @@ export default {
}
return [ data.options, data.value ];
},
/**
*
* @param target
@@ -300,10 +344,10 @@ export default {
elements.forEach(elem => {
let selected = data.value.some(e => e.id === elem.id);
if (!selected) {
data.value.push(elem);
dump.push(elem.id);
// add in hiddenField
this.rebuildHiddenFieldValues(target);
}
@@ -312,7 +356,7 @@ export default {
//console.log('add ' + dump.length + ' selected elems in', target, dump);
}
},
/**
* Remove element from selected and from hiddenField
* @param target
@@ -322,19 +366,19 @@ export default {
let data = this[target];
let selected = data.value.some(e => e.id === elem.id);
if (selected) {
// remove from selected
data.value = data.value.filter(e => e.id !== elem.id);
//console.log('remove ' + elem.id + ' from selected ' + target);
// remove from hiddenField
this.rebuildHiddenFieldValues(target);
// in any cases, remove should be recursive
this.unselectToNextField(target, elem);
}
},
/**
* When unselect Action, it could remove elements in goals multiselect.
* In that case, we have to unselect Goal to remove elements in results too.
@@ -348,7 +392,7 @@ export default {
//console.log('!!!! done');
}
},
/**
* Rebuild values serie (string) in target HiddenField
* @param target
@@ -362,14 +406,14 @@ export default {
})
//console.log(data.hiddenField);
},
addIdToValue(string, id) {
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
},
transTitle ({ title }) {
return title.fr //TODO multilang
},
@@ -378,4 +422,4 @@ export default {
</script>
<style scoped>
</style>
</style>

View File

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

View File

@@ -2,12 +2,15 @@ import { createApp } from "vue";
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import App from './App.vue';
const i18n = _createI18n({});
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('#export_export')
;
const app = createApp({
template: `<app></app>`,
})
.use(i18n)
.component('app', App)
.mount(after);
}

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,8 +64,9 @@ const visMessages = {
placeholder: "Choisissez le genre de l'usager",
woman: "Féminin",
man: "Masculin",
neuter: "Neutre, non binaire",
undefined: "Non renseigné"
both: "Neutre, non binaire",
undefined: "Non renseigné",
unknown: "Non renseigné"
}
},
error_only_one_person: "Une seule personne peut être sélectionnée !",

View File

@@ -153,6 +153,8 @@ 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 === 'neuter' ? 'fa-neuter' : 'fa-genderless';
return this.person.gender === 'woman' ? 'fa-venus' : this.person.gender === 'man' ? 'fa-mars' : this.person.gender === 'both' ? '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 === 'neuter' ? 'person.gender.neuter' : 'person.gender.undefined';
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';
},
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="neuter">{{ $t('person.gender.neuter') }}</option>
<option value="both">{{ $t('person.gender.both') }}</option>
</select>
<label>{{ $t('person.gender.title') }}</label>
</div>
@@ -291,8 +291,12 @@ export default {
return 'fa-venus';
case 'man':
return 'fa-mars';
case 'neuter':
case 'both':
return 'fa-neuter';
case 'unknown':
return 'fa-genderless';
default:
return 'fa-genderless';
}
},
genderTranslation() {
@@ -301,8 +305,12 @@ export default {
return 'person.gender.woman';
case 'man':
return 'person.gender.man';
case 'neuter':
return 'person.gender.neuter';
case 'both':
return 'person.gender.both';
case 'unknown':
return 'person.gender.unknown';
default:
return 'person.gender.unknown';
}
},
feminized() {

View File

@@ -36,7 +36,8 @@ const personMessages = {
placeholder: "Choisissez le genre de l'usager",
woman: "Féminin",
man: "Masculin",
neuter: "Neutre, non binaire",
both: "Neutre, non binaire",
unknown: "Non renseigné",
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 == 'neuter') ? 'fa-neuter' : 'fa-genderless' %}
(person.gender == 'man') ? 'fa-mars' : (person.gender == 'both') ? 'fa-neuter' : 'fa-genderless' %}
{% set genderTitle = (person.gender == 'woman') ? 'woman' :
(person.gender == 'man') ? 'man' : (person.gender == 'neuter') ? 'neuter' : 'Not given'|trans %}
(person.gender == 'man') ? 'man' : (person.gender == 'both') ? 'both' : 'Not given'|trans %}
<p class="moreinfo">
<i class="fa fa-fw {{ gender }}" title="{{ genderTitle|trans }}"></i>

View File

@@ -35,6 +35,7 @@ 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

@@ -69,10 +69,10 @@ final class EvaluationTypeFilterTest extends AbstractFilterTest
return [
$em->createQueryBuilder()
->select('eval.id')
->select('workeval.id')
->from(AccompanyingPeriod::class, 'acp')
->join('acp.works', 'acpw')
->join('acpw.accompanyingPeriodWorkEvaluations', 'eval'),
->join('acpw.accompanyingPeriodWorkEvaluations', 'workeval'),
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* 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

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20231128143534 extends AbstractMigration
{
public function getDescription(): string
{
return 'Set a default for the content column of accompanying_period_comment';
}
public function up(Schema $schema): void
{
$this->addSql("UPDATE chill_person_accompanying_period_comment SET content='' WHERE content IS NULL");
$this->addSql('ALTER TABLE chill_person_accompanying_period_comment ALTER COLUMN content SET NOT NULL');
$this->addSql('ALTER TABLE chill_person_accompanying_period_comment ALTER COLUMN content SET DEFAULT \'\'');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_comment ALTER COLUMN content DROP DEFAULT');
$this->addSql('ALTER TABLE chill_person_accompanying_period_comment ALTER COLUMN content DROP NOT NULL');
}
}

View File

@@ -61,10 +61,10 @@ Spoken languages': 'Langues parlées'
'Unknown spoken languages': 'Langues parlées inconnues'
Male: Homme
Female: Femme
Neuter: Neutre
#Both: Neutre
man: Homme
woman: Femme
neuter: Neutre
#both: 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
Neuter: Non-binair
Both: Non-binair
man: Man
woman: Vrouw
neuter: Non-binair
both: Non-binair
Man: Man
Woman: Vrouw
both: Onbepaald
Both: Onbepaald
#both: Onbepaald
#Both: Onbepaald
Divorced: Gescheiden
Separated: Uit elkaar
Widow: Weduwe/weduwnaar