Compare commits

...

122 Commits

Author SHA1 Message Date
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
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
1f47f157ea Release v2.12.0 2023-11-15 14:03:46 +01:00
5ab0d3f8da add units on the headers for avg duration of accompanying period work 2023-11-15 13:57:48 +01:00
98fd5cae78 Merge branch '204-export-action-creator' into 'master'
[export] add filters/aggregators for creator works action

Closes #204

See merge request Chill-Projet/chill-bundles!610
2023-11-15 12:49:08 +00:00
2bb29242e4 Merge branch '206-add-exports' into 'master'
"Ajouter un export "usagers concernées par un échange" et "usagers concernés par une action""

Closes #203 and #206

See merge request Chill-Projet/chill-bundles!613
2023-11-15 12:45:29 +00:00
e6cab938c8 DX: increase max connection in postgres service 2023-11-15 13:41:35 +01:00
f5f4d8fcdd remove blank line in changie 2023-11-15 13:30:48 +01:00
e9df26c2f7 Export: add clauses on the social work start date and end date within the filter "Filter accompanying period by accompanying period work"' 2023-11-15 13:25:08 +01:00
af6bee2497 Export: add an export which count persons on activity 2023-11-15 13:25:08 +01:00
af585bada3 Export: add a export which count persons on accompanying period work 2023-11-15 13:24:59 +01:00
f9763b866d add 6 new tests for filters/aggrs 2023-11-15 13:00:45 +01:00
7f18a2fb7d [export] better translation since filters are sorted 2023-11-15 13:00:45 +01:00
3cdad6caff Revert "test cs-fixer"
This reverts commit 9aa1d22d69e0f67333a8c39574fa2efed71c2bc8.
2023-11-15 13:00:45 +01:00
200ab836c7 test cs-fixer 2023-11-15 13:00:45 +01:00
3e39c0ced7 rector and cs-fixer 2023-11-15 13:00:45 +01:00
83c3621c26 declare service 2023-11-15 13:00:45 +01:00
b790e2fcf1 complete aggregators 2023-11-15 13:00:45 +01:00
fde6000d0b filters alterQuery 2023-11-15 13:00:45 +01:00
3892d1e877 remove date field 2023-11-15 13:00:45 +01:00
6944773868 create 6 new filters/aggr files 2023-11-15 13:00:45 +01:00
d5bc9d10d5 Merge branch 'issue202_export_duration_actions' into 'master'
Export: create an export which add the average  duration of accompanying period work

Closes #202

See merge request Chill-Projet/chill-bundles!611
2023-11-15 11:42:35 +00:00
3743b336a6 Export: create an export which add the average duration of accompanying period work 2023-11-15 11:42:34 +00:00
3332413fe0 Merge branch '199-missing-exports' into 'master'
Add missing filters and grouping

Closes #189, #200, and #199

See merge request Chill-Projet/chill-bundles!609
2023-11-14 20:24:21 +00:00
86e659edd4 DX: replace some string by constants 2023-11-14 21:17:09 +01:00
2e60ba3137 Export: fix infinite loop in HouseholdVoter 2023-11-14 21:17:08 +01:00
1663c6f7c7 DX: rename CountHousehold => CountHouseholdInPeriod.php + add test 2023-11-14 21:17:08 +01:00
d01b6db5dc DX: remove unused property 2023-11-14 21:17:08 +01:00
847c8238e8 Export: fix typo in filter "filter accompanying period work on end date 2023-11-14 21:17:08 +01:00
2cc5fa06ae 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
2023-11-14 21:17:07 +01:00
0d741ab886 Export: add grouping "group peoples by postal code" 2023-11-14 21:17:07 +01:00
9cb794fef2 Merge branch 'documentation-configure-absence-sync' into 'master'
Update the description and screenshots for the configuration of calendar

See merge request Chill-Projet/chill-bundles!608
2023-11-14 19:41:04 +00:00
fd03855ee6 Merge branch '205-fix-acpwork-referrers-as-a-list' into 'master'
Fix normalization of accompanying period work

Closes #205

See merge request Chill-Projet/chill-bundles!612
2023-11-14 19:40:42 +00:00
ac9897d9e7 DX: add a test to ensure that accompanyingPeriodWork::getReferrers is a list 2023-11-14 20:33:18 +01:00
88469dbe8e Fix serialization of accompangying period work referrers 2023-11-14 18:34:28 +01:00
cbc83c0e63 Export: add grouping "group activity by person" (activites saved in person context) 2023-11-13 15:29:01 +01:00
27012d842d Export: add an ActivityPresenceFilter 2023-11-13 15:29:01 +01:00
4112e59af4 Export: add an ActivityPresenceAggregator 2023-11-09 17:35:34 +01:00
1b0a30baf8 Merge branch '188-fix-ci' into 'master'
debug ci

Closes #188

See merge request Chill-Projet/chill-bundles!606
2023-11-09 15:20:55 +00:00
9673b369ef DX: restore the requirements of phpunit's tests
The problem with timezone is now fixed
2023-11-09 16:13:58 +01:00
43966c4d5a remove the change of timezone in test's setUp 2023-11-09 16:01:34 +01:00
c9f438319c update the description and screenshots for the configuration of calendar
and absence synchronization
2023-11-08 10:21:03 +01:00
58b45c6f1b Prepare for v2.11.0 2023-11-07 16:18:33 +01:00
f756c0d71e Merge branch '185-fix-inconsistencies-person-geog-unit-aggregator' into 'master'
Fix geographical unit stat aggregator by course: show also the course if they...

Closes #194 and #185

See merge request Chill-Projet/chill-bundles!607
2023-11-07 15:15:12 +00:00
bb1602934c Fix "group activity by creator job" aggregator 2023-11-07 16:07:24 +01:00
83fe3ec3fc Export: add a "filter activity by creator job" filter 2023-11-07 16:06:22 +01:00
abebb79e8b Fix geographical unit stat aggregator by course: show also the course if they are not located within a location 2023-11-07 14:41:56 +01:00
3a269ba953 update changelog 2023-11-07 13:28:11 +01:00
a4a4aa119e Merge branch 'issue182_fix_doublon_bug' into 'master'
Adjust query to transfer relationship from one person to another

See merge request Chill-Projet/chill-bundles!603
2023-11-07 12:26:18 +00:00
139f5a1ff9 Merge branch 'issue182_fix_doublon_bug' of gitlab.com:Chill-Projet/chill-bundles into issue182_fix_doublon_bug 2023-11-07 10:54:36 +01:00
1e43772b17 php cs fixer 2023-11-07 10:54:22 +01:00
c29b356c02 minor change to rerun pipeline 2023-11-07 10:54:22 +01:00
050a2955ca Add a comment 2023-11-07 10:54:22 +01:00
a1132cf82f add changie 2023-11-07 10:54:22 +01:00
db70ce4a61 Adjust query to transfer relationship from one person to another depending on whether original person was in the to or from position 2023-11-07 10:54:22 +01:00
4d20a46717 Fix geographical unit stat aggregator: date range on association with left join 2023-11-06 22:03:37 +01:00
dd75cb695c Fix date range operator in GeographicalUnitStatAggregator 2023-11-06 22:03:08 +01:00
8cb9142800 Release for 2.10.5 2023-11-05 23:50:15 +01:00
4847724f6f temporarily allow failure in phpunit tests, due to a problem with timezone 2023-11-05 23:41:54 +01:00
6280453523 Fix activity date filter: validation of form using RollingDate 2023-11-05 23:29:33 +01:00
68f56671a4 DX: fix data type with new rules for phpstan 2023-11-05 23:19:00 +01:00
dc860d0c46 DX: new CS for new version of php-cs-fixer 2023-11-05 23:14:19 +01:00
6fd80f9f2e [export] fix data type from form: either collection or array 2023-11-05 23:08:13 +01:00
3ce78d0c16 php cs fixer 2023-11-01 10:52:42 +01:00
f43bd2bc58 minor change to rerun pipeline 2023-11-01 09:48:48 +01:00
ec85f05e0b Php cs fixer 2023-10-30 14:09:03 +01:00
bfe0d9a137 Add a comment 2023-10-30 13:52:50 +01:00
cac092c45a add changie 2023-10-30 12:00:25 +01:00
39bb06b991 Adjust query to transfer relationship from one person to another depending on whether original person was in the to or from position 2023-10-30 11:53:41 +01:00
b2aa465b03 unreleased changie file deleted 2023-10-26 17:44:49 +02:00
0fa8944c29 Update changelog 2023-10-26 17:39:55 +02:00
7494dbfc7c changie made 2023-10-26 17:38:22 +02:00
642958bde2 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2023-10-26 17:35:22 +02:00
57bd6d1be4 remove dump 2023-10-26 17:33:39 +02:00
bbcbbf078f remove dump 2023-10-26 17:04:56 +02:00
ba778b1cf8 update changelog 2023-10-26 16:07:34 +02:00
728fd642fa Merge branch 'replace_translator' 2023-10-26 16:05:17 +02:00
4150eb56b4 changie v2.10.2 2023-10-26 15:23:01 +02:00
2927561c02 added changie 2023-10-26 15:22:22 +02:00
5188891108 php style fixer 2023-10-26 15:21:20 +02:00
c6deb21606 replace old method of getting translator with injection of translatorInterface 2023-10-26 15:20:19 +02:00
cdfb084fe4 update changelog 2023-10-26 14:17:15 +02:00
5ce21aadce Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2023-10-26 14:14:51 +02:00
b9000a38d3 add changie 2023-10-26 14:14:36 +02:00
89a185a34f replace get->('translator') with injection of translatorInterface 2023-10-26 14:13:12 +02:00
bc6c3a1089 Release: 2.10.1 2023-10-24 20:52:46 +02:00
2c50f484f0 Fix export controller when generating an export without any data in session 2023-10-24 20:46:23 +02:00
4bc1de01d6 Release: version 2.10.0: fix typos and French translation 2023-10-24 18:56:42 +02:00
e21fe70b75 Release: version 2.10.0 2023-10-24 15:50:47 +02:00
461995b56b Merge branch '172-missing-exports' into 'master'
Fix some issues with exports

Closes #172

See merge request Chill-Projet/chill-bundles!601
2023-10-24 13:47:30 +00:00
e635b73256 DX: mark a class as final 2023-10-24 15:39:57 +02:00
54ae17a8d2 Fix label on some filters 2023-10-24 15:39:57 +02:00
617d09ab8a Merge branch '179-remove-filter-by-center-in-exports' into 'master'
Resolve "Export: permettre de ne pas filtrer les résultats par centre"

Closes #179

See merge request Chill-Projet/chill-bundles!599
2023-10-24 13:18:03 +00:00
f8ee2903b2 Merge branch '172-missing-exports' into 'master'
[Export]: add missing grouping and filters and fix some issues

Closes #177 and #172

See merge request Chill-Projet/chill-bundles!598
2023-10-24 13:17:03 +00:00
36b0844e79 Fix issue with duration aggregator as month 2023-10-24 14:17:28 +02:00
7bff5ce39e DX: rename UserScopeFilter to CreatorScopeFilter 2023-10-24 14:17:08 +02:00
4375ecf49a [export controller] skip the page "select a center" when the configuration value of filter_stats_by_center is set to false 2023-10-19 17:43:32 +02:00
a6e930958b [export] create a parameter that will force to skip the filtering by center (ACL) when generating an export 2023-10-19 17:19:28 +02:00
5f805626f7 [export] sort filters and aggregators by title 2023-10-19 14:39:22 +02:00
4c9ea740c8 [export] fix date range selection on filtre and grouping "by status of the course at date" on accompanying periods 2023-10-19 11:44:53 +02:00
981dc6a959 [export] add a filter and aggregator on accompanying periods: group by activity type (accompanying course having at least one activity from this type) 2023-10-19 10:33:56 +02:00
11fb9bcd0b [export] add a filter and aggregator on activities: filter/group by persons taking part to the activity 2023-10-19 10:33:55 +02:00
a4edb34668 [export] add a filter and aggregator on accompanying period work: group/filter by handling third party 2023-10-19 10:33:55 +02:00
f799fe0649 [export] add a grouping of accompanying period by opening and closing date 2023-10-19 10:33:54 +02:00
220 changed files with 7291 additions and 1020 deletions

20
.changes/v2.10.0.md Normal file
View File

@@ -0,0 +1,20 @@
## v2.10.0 - 2023-10-24
### Feature
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] Add a filter "grouping accompanying period by opening date" and "grouping accompanying period by closing date"
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on accompanying period work: group/filter by handling third party
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on activites: group/filter activities by people participating to the activities
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a grouping on accompanying period export: group by activity type associated to at least one activity within the accompanying period
* [export] sort filters and aggregators by title
* ([#179](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/179)) [export] create a parameter that will force to skip the filtering by center (ACL) when generating an export
### Fixed
* ([#177](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/177)) [export] fix date range selection on filter and grouping "by status of the course at date", on accompanying periods
### Résumé francophone des changements
- Ajout d'un regroupement sur les parcours: par date de cloture et d'ouverture;
- Ajouter d'un filtre et regroupement par tiers traitant sur les actions d'accompagnement;
- ajout d'un filtre et regroupement par usager participant sur les échanges
- ajout d'un regroupement: par type d'activité associé au parcours;
- trie les filtre et regroupements par ordre alphabétique dans els exports
- ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports
- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date"

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

@@ -0,0 +1,3 @@
## v2.10.1 - 2023-10-24
### Fixed
* Fix export controller when generating an export without any data in session

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

@@ -0,0 +1,3 @@
## v2.10.2 - 2023-10-26
### Fixed
* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Use injection of translator instead of ->get().

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

@@ -0,0 +1,3 @@
## v2.10.3 - 2023-10-26
### Fixed
* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Replace old method of getting translator with injection of translatorInterface

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

@@ -0,0 +1,3 @@
## v2.10.4 - 2023-10-26
### Fixed
* Fix null value constraint errors when merging relationships in doubles

4
.changes/v2.10.5.md Normal file
View File

@@ -0,0 +1,4 @@
## v2.10.5 - 2023-11-05
### Fixed
* ([#183](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/183)) Fix "problem during download" on some filters, which used a wrong data type
* ([#184](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/184)) Fix filter "activity by date"

4
.changes/v2.10.6.md Normal file
View File

@@ -0,0 +1,4 @@
## v2.10.6 - 2023-11-07
### Fixed
* ([#182](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/182)) Fix merging of double person files. Adjustement relationship sql statement
* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix aggregator by geographical unit on person: avoid inconsistencies

6
.changes/v2.11.0.md Normal file
View File

@@ -0,0 +1,6 @@
## 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"
### Fixed
* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix "group accompanying period by geographical unit": take into account the accompanying periods when the period is not located within an unit
* Fix "group activity by creator job" aggregator

26
.changes/v2.12.0.md Normal file
View File

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

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

View File

@@ -11,6 +11,10 @@ cache:
services: services:
- name: postgis/postgis:14-3.3-alpine - name: postgis/postgis:14-3.3-alpine
alias: db alias: db
command:
- postgres
- "-c"
- max_connections=1000
- name: redis - name: redis
alias: redis alias: redis
@@ -28,6 +32,8 @@ variables:
REDIS_PORT: 6379 REDIS_PORT: 6379
REDIS_URL: redis://redis:6379 REDIS_URL: redis://redis:6379
DEFAULT_CARRIER_CODE: BE DEFAULT_CARRIER_CODE: BE
# force a timezone
TZ: Europe/Brussels
stages: stages:
- Composer install - Composer install

View File

@@ -6,6 +6,101 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## 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"
### Fixed
* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix "group accompanying period by geographical unit": take into account the accompanying periods when the period is not located within an unit
* Fix "group activity by creator job" aggregator
## v2.10.6 - 2023-11-07
### Fixed
* ([#182](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/182)) Fix merging of double person files. Adjustement relationship sql statement
* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix aggregator by geographical unit on person: avoid inconsistencies
## v2.10.5 - 2023-11-05
### Fixed
* ([#183](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/183)) Fix "problem during download" on some filters, which used a wrong data type
* ([#184](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/184)) Fix filter "activity by date"
## v2.10.4 - 2023-10-26
### Fixed
* Fix null value constraint errors when merging relationships in doubles
## v2.10.3 - 2023-10-26
### Fixed
* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Replace old method of getting translator with injection of translatorInterface
## v2.10.2 - 2023-10-26
### Fixed
* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Use injection of translator instead of ->get().
## v2.10.1 - 2023-10-24
### Fixed
* Fix export controller when generating an export without any data in session
## v2.10.0 - 2023-10-24
### Feature
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] Add a filter "grouping accompanying period by opening date" and "grouping accompanying period by closing date"
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on accompanying period work: group/filter by handling third party
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a filter and aggregator on activites: group/filter activities by people participating to the activities
* ([#172](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/172)) [export] add a grouping on accompanying period export: group by activity type associated to at least one activity within the accompanying period
* [export] sort filters and aggregators by title
* ([#179](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/179)) [export] create a parameter that will force to skip the filtering by center (ACL) when generating an export
### Fixed
* ([#177](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/177)) [export] fix date range selection on filter and grouping "by status of the course at date", on accompanying periods
### Résumé francophone des changements
- Ajout d'un regroupement sur les parcours: par date de cloture et d'ouverture;
- Ajouter d'un filtre et regroupement par tiers traitant sur les actions d'accompagnement;
- ajout d'un filtre et regroupement par usager participant sur les échanges
- ajout d'un regroupement: par type d'activité associé au parcours;
- trie les filtre et regroupements par ordre alphabétique dans els exports
- ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports
- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date"
## v2.9.2 - 2023-10-17 ## v2.9.2 - 2023-10-17
### Fixed ### Fixed
* Fix possible null values in string's entities * Fix possible null values in string's entities

View File

@@ -1,13 +1,15 @@
Configure Chill for calendar sync and SSO with Microsoft Graph (Outlook) Configure Chill for calendar and absence synchronisation and SSO with Microsoft Graph (Outlook)
======================================================================== ===============================================================================================
Chill offers the possibility to: Chill offers the possibility to:
* authenticate users using Microsoft Graph, with relatively small adaptations; * authenticate users using Microsoft Graph, with relatively small adaptations;
* synchronize calendar in both ways (`see the user manual for a large description of the feature <https://gitea.champs-libres.be/Chill-project/manuals>`_). * synchronize calendar in both ways (`see the user manual for a large description of the feature <https://gitea.champs-libres.be/Chill-project/manuals>`_).
Both can be configured separately (synchronising calendars without SSO, or SSO without calendar). When calendar sync is configured without SSL, the user's email address is the key to associate Chill's users with Microsoft's ones. Both can be configured separately (synchronising calendars without SSO, or SSO without calendar).
Please note that the user's email address is the key to associate Chill's users with Microsoft's ones.
Configure SSO Configure SSO
------------- -------------
@@ -46,7 +48,7 @@ Do not forget to provider user's accesses to your app, using the "Utilisateurs e
You must know have gathered all the required variables for SSO: You must know have gathered all the required variables for SSO:
.. code-block:: .. code-block::
SAML_BASE_URL=https://test.chill.be # must be SAML_BASE_URL=https://test.chill.be # must be
SAML_ENTITY_ID=https://test.chill.be # must match the one entered SAML_ENTITY_ID=https://test.chill.be # must match the one entered
@@ -186,20 +188,27 @@ Configure chill app
Configure sync Configure sync and calendar access
-------------- ----------------------------------
The sync processe might be configured in the same app, or into a different app. The purpose of this configuration is the following:
The synchronization processes use Oauth2.0 for authentication and authorization. - let user read their calendar and shared calendar within Chill (with the same permissions as the one configured in Outlook / Azure);
- allow chill instance to write appointment ("Rendez-vous") into their calendar, and invite other users to their appointment;
- allow chill instance to be notified if an appoint is added or removed by the user within another interface than Chill: if the appointment match another one created in the Chill interface, the date and time are updated in Chill;
- allow chill instance to read the absence of the user and, if set, mark the user as absent in Chill;
The sync processe might be configured in the same app, or into a different app on the Azure side.
The synchronization processes use Oauth 2.0 / OpenID Connect for authentication and authorization.
.. note:: .. note::
Two flows are in use: Two flows are in use:
* we authenticate "on behalf of a user", to allow users to see their own calendar or other user's calendar into the web interface. * we authenticate "on behalf of a user", to allow users to see their own calendar or other user's calendar into the web interface.
Typically, when the page is loaded, Chill first check that an authorization token exists. If not, the user is redirected to Microsoft Azure for authentification and a new token is grabbed (most of the times, this is transparent for users). Typically, when the page is loaded, Chill first check that an authorization token exists. If not, the user is redirected to Microsoft Azure for authentification and a new token is grabbed (most of the times, this is transparent for users).
* Chill also acts "as a machine", to synchronize calendars with a daemon background. * Chill also acts "as a machine", to synchronize calendars with a daemon background.
@@ -229,8 +238,9 @@ Some explanation:
The sync daemon must have write access: The sync daemon must have write access:
* the daemon must be allowed to read all users and their profile, to establish a link between them and the Chill's users: (:code:`Users.Read.All`); * the daemon must be allowed to read all users and their profile, to establish a link between them and the Chill's users: (:code:`Users.Read.All`);
* it must also be allowed to read and write into the calendars (:code:`Calendars.ReadWrite.All`) * it must also be allowed to read and write into the calendars (:code:`Calendars.ReadWrite.All`);
* for sending invitation to other users, the permission (:code:`Mail.Send`) must be granted. * for sending invitation to other users, the permission (:code:`Mail.Send`) must be granted;
* and, for reading the absence status of the user and sync it with chill, it must be able to read the mailboxSettings (:code:`MailboxSettings.Read`).
At this step, you might choose to accept those permissions for all users, or let them do it by yourself. At this step, you might choose to accept those permissions for all users, or let them do it by yourself.
@@ -301,7 +311,7 @@ The calendar synchronization is processed using symfony messenger. It seems to b
The association between chill's users and Microsoft's users is done by this cli command: The association between chill's users and Microsoft's users is done by this cli command:
.. code-block:: .. code-block::
bin/console chill:calendar:msgraph-user-map-subscribe bin/console chill:calendar:msgraph-user-map-subscribe

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View File

@@ -673,8 +673,8 @@ final class ActivityController extends AbstractController
throw $this->createNotFoundException('Accompanying Period not found'); throw $this->createNotFoundException('Accompanying Period not found');
} }
// TODO Add permission // TODO Add permission
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); // $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
} else { } else {
throw $this->createNotFoundException('Person or Accompanying Period not found'); throw $this->createNotFoundException('Person or Accompanying Period not found');
} }

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
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\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ByActivityTypeAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_by_activity_type_agg';
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
private ActivityTypeRepositoryInterface $activityTypeRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
) {}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('after_date', PickRollingDateType::class, [
'required' => false,
'label' => 'export.aggregator.acp.by_activity_type.after_date',
])
->add('before_date', PickRollingDateType::class, [
'required' => false,
'label' => 'export.aggregator.acp.by_activity_type.before_date',
]);
}
public function getFormDefaultData(): array
{
return [
'before_date' => null,
'after_date' => null,
];
}
public function getLabels($key, array $values, mixed $data)
{
return function (null|int|string $value): string {
if ('_header' === $value) {
return 'export.aggregator.acp.by_activity_type.activity_type';
}
if ('' === $value || null === $value || null === $activityType = $this->activityTypeRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($activityType->getName());
};
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_actype_id'];
}
public function getTitle()
{
return 'export.aggregator.acp.by_activity_type.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
// we make a left join, with acp having at least one activity of the given type
$exists = 'EXISTS (SELECT 1 FROM '.Activity::class." {$p}_activity WHERE {$p}_activity.accompanyingPeriod = acp AND {$p}_activity.activityType = {$p}_activity_type";
if (null !== $data['after_date']) {
$exists .= " AND {$p}_activity.date > :{$p}_after_date";
$qb->setParameter("{$p}_after_date", $this->rollingDateConverter->convert($data['after_date']));
}
if (null !== $data['before_date']) {
$exists .= " AND {$p}_activity.date < :{$p}_before_date";
$qb->setParameter("{$p}_before_date", $this->rollingDateConverter->convert($data['before_date']));
}
$exists .= ')';
$qb->leftJoin(
ActivityType::class,
"{$p}_activity_type",
Join::WITH,
$exists
);
$qb
->addSelect("{$p}_activity_type.id AS {$p}_actype_id")
->addGroupBy("{$p}_actype_id");
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ActivityPresenceAggregator implements AggregatorInterface
{
public function __construct(private ActivityPresenceRepositoryInterface $activityPresenceRepository, private TranslatableStringHelperInterface $translatableStringHelper) {}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return function (null|int|string $value): string {
if ('_header' === $value) {
return 'export.aggregator.activity.by_activity_presence.header';
}
if (null === $value || '' === $value || null === $presence = $this->activityPresenceRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($presence->getName());
};
}
public function getQueryKeys($data)
{
return ['activity_presence_aggregator_attendee'];
}
public function getTitle(): string
{
return 'export.aggregator.activity.by_activity_presence.Group activity by presence';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data): void
{
$qb->addSelect('IDENTITY(activity.attendee) AS activity_presence_aggregator_attendee');
$qb->addGroupBy('activity_presence_aggregator_attendee');
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

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

View File

@@ -14,18 +14,18 @@ namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class JobScopeAggregator implements AggregatorInterface class CreatorJobAggregator implements AggregatorInterface
{ {
private const PREFIX = 'acp_agg_creator_job'; private const PREFIX = 'acp_agg_creator_job';
public function __construct( public function __construct(
private readonly ScopeRepository $scopeRepository, private readonly UserJobRepositoryInterface $userJobRepository,
private readonly TranslatableStringHelper $translatableStringHelper private readonly TranslatableStringHelper $translatableStringHelper
) {} ) {}
@@ -76,17 +76,15 @@ class JobScopeAggregator implements AggregatorInterface
{ {
return function ($value): string { return function ($value): string {
if ('_header' === $value) { if ('_header' === $value) {
return 'Scope'; return 'Job';
} }
if (null === $value || '' === $value) { if (null === $value || '' === $value || null === $s = $this->userJobRepository->find($value)) {
return ''; return '';
} }
$s = $this->scopeRepository->find($value);
return $this->translatableStringHelper->localize( return $this->translatableStringHelper->localize(
$s->getName() $s->getLabel()
); );
}; };
} }

View File

@@ -73,7 +73,6 @@ class DateAggregator implements AggregatorInterface
'choices' => self::CHOICES, 'choices' => self::CHOICES,
'multiple' => false, 'multiple' => false,
'expanded' => true, 'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
]); ]);
} }

View File

@@ -99,12 +99,6 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
public function getLabels($key, array $values, $data) 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) { return function ($value) use ($data) {
if ('_header' === $value) { if ('_header' === $value) {
return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason'; return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason';

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\ActivityBundle\Export\Aggregator\PersonAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class PersonAggregator implements AggregatorInterface
{
public function __construct(private LabelPersonHelper $labelPersonHelper) {}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.person.by_person.person');
}
public function getQueryKeys($data)
{
return ['activity_by_person_agg'];
}
public function getTitle()
{
return 'export.aggregator.person.by_person.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->addSelect('IDENTITY(activity.person) AS activity_by_person_agg')
->addGroupBy('activity_by_person_agg');
}
public function applyOn()
{
return Declarations::ACTIVITY_PERSON;
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Tests\Export\Aggregator\PersonsAggregatorTest;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see PersonsAggregatorTest
*/
final readonly class PersonsAggregator implements AggregatorInterface
{
private const PREFIX = 'act_persons_agg';
public function __construct(private LabelPersonHelper $labelPersonHelper) {}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
if ($key !== self::PREFIX.'_pid') {
throw new \UnexpectedValueException('this key should not be handled: '.$key);
}
return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons');
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_pid'];
}
public function getTitle()
{
return 'export.aggregator.activity.by_persons.Group activity by persons';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.persons', "{$p}_p")
->addSelect("{$p}_p.id AS {$p}_pid")
->addGroupBy("{$p}_pid");
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

@@ -11,8 +11,8 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP; namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper; use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportInterface;
@@ -21,19 +21,19 @@ use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class AvgActivityDuration implements ExportInterface, GroupedExportInterface class AvgActivityDuration implements ExportInterface, GroupedExportInterface
{ {
protected EntityRepository $repository; private readonly bool $filterStatsByCenters;
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly ActivityRepository $activityRepository,
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 buildForm(FormBuilderInterface $builder) {}
@@ -91,23 +91,25 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
{ {
$centers = array_map(static fn ($el) => $el['center'], $acl); $centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository->createQueryBuilder('activity'); $qb = $this->activityRepository->createQueryBuilder('activity');
$qb $qb
->join('activity.accompanyingPeriod', 'acp') ->join('activity.accompanyingPeriod', 'acp')
->select('AVG(activity.durationTime) as export_avg_activity_duration') ->select('AVG(activity.durationTime) as export_avg_activity_duration')
->andWhere($qb->expr()->isNotNull('activity.durationTime')); ->andWhere($qb->expr()->isNotNull('activity.durationTime'));
$qb if ($this->filterStatsByCenters) {
->andWhere( $qb
$qb->expr()->exists( ->andWhere(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part $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) 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) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
' '
)
) )
) ->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers); }
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);

View File

@@ -24,16 +24,21 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface
{ {
protected EntityRepository $repository; protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct( public function __construct(
EntityManagerInterface $em, EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) { ) {
$this->repository = $em->getRepository(Activity::class); $this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
@@ -101,16 +106,18 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
->select('AVG(activity.travelTime) as export_avg_activity_visit_duration') ->select('AVG(activity.travelTime) as export_avg_activity_visit_duration')
->andWhere($qb->expr()->isNotNull('activity.travelTime')); ->andWhere($qb->expr()->isNotNull('activity.travelTime'));
$qb if ($this->filterStatsByCenters) {
->andWhere( $qb
$qb->expr()->exists( ->andWhere(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part $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) 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) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
' '
)
) )
) ->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers); }
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);

View File

@@ -24,16 +24,21 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class CountActivity implements ExportInterface, GroupedExportInterface class CountActivity implements ExportInterface, GroupedExportInterface
{ {
protected EntityRepository $repository; protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct( public function __construct(
EntityManagerInterface $em, EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) { ) {
$this->repository = $em->getRepository(Activity::class); $this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
} }
public function buildForm(FormBuilderInterface $builder) {} public function buildForm(FormBuilderInterface $builder) {}
@@ -95,16 +100,18 @@ class CountActivity implements ExportInterface, GroupedExportInterface
->createQueryBuilder('activity') ->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp'); ->join('activity.accompanyingPeriod', 'acp');
$qb if ($this->filterStatsByCenters) {
->andWhere( $qb
$qb->expr()->exists( ->andWhere(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part $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) 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) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
' '
)
) )
) ->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers); }
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);

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\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\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;
class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly 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_person_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_person_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_person_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');
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 person.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,
];
}
}

View File

@@ -22,11 +22,21 @@ use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper;
use Chill\MainBundle\Export\ListInterface; use Chill\MainBundle\Export\ListInterface;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class ListActivity implements ListInterface, GroupedExportInterface class ListActivity implements ListInterface, GroupedExportInterface
{ {
public function __construct(private readonly ListActivityHelper $helper, private readonly EntityManagerInterface $entityManager, private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper) {} private readonly bool $filterStatsByCenters;
public function __construct(
private readonly ListActivityHelper $helper,
private readonly EntityManagerInterface $entityManager,
private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
@@ -107,21 +117,27 @@ class ListActivity implements ListInterface, GroupedExportInterface
->join('activity.accompanyingPeriod', 'acp') ->join('activity.accompanyingPeriod', 'acp')
->leftJoin('acp.participations', 'acppart') ->leftJoin('acp.participations', 'acppart')
->leftJoin('acppart.person', 'person') ->leftJoin('acppart.person', 'person')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL') ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL');
->andWhere(
$qb->expr()->exists( if ($this->filterStatsByCenters) {
'SELECT 1 $qb
->andWhere(
$qb->expr()->exists(
'SELECT 1
FROM '.PersonCenterHistory::class.' acl_count_person_history FROM '.PersonCenterHistory::class.' acl_count_person_history
WHERE acl_count_person_history.person = person WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers) AND acl_count_person_history.center IN (:authorized_centers)
' '
)
) )
) ->setParameter('authorized_centers', $centers);
}
$qb
// some grouping are necessary // some grouping are necessary
->addGroupBy('acp.id') ->addGroupBy('acp.id')
->addOrderBy('activity.date') ->addOrderBy('activity.date')
->addOrderBy('activity.id') ->addOrderBy('activity.id');
->setParameter('authorized_centers', $centers);
$this->helper->addSelect($qb); $this->helper->addSelect($qb);

View File

@@ -24,16 +24,20 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class SumActivityDuration implements ExportInterface, GroupedExportInterface class SumActivityDuration implements ExportInterface, GroupedExportInterface
{ {
protected EntityRepository $repository; protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct( public function __construct(
EntityManagerInterface $em, EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) { ) {
$this->repository = $em->getRepository(Activity::class); $this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
@@ -101,16 +105,18 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
$qb->select('SUM(activity.durationTime) as export_sum_activity_duration') $qb->select('SUM(activity.durationTime) as export_sum_activity_duration')
->andWhere($qb->expr()->isNotNull('activity.durationTime')); ->andWhere($qb->expr()->isNotNull('activity.durationTime'));
$qb if ($this->filterStatsByCenters) {
->andWhere( $qb
$qb->expr()->exists( ->andWhere(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part $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) 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) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
' '
)
) )
) ->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers); }
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);

View File

@@ -24,16 +24,20 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface
{ {
protected EntityRepository $repository; protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct( public function __construct(
EntityManagerInterface $em, EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) { ) {
$this->repository = $em->getRepository(Activity::class); $this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
@@ -101,16 +105,18 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
$qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration') $qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration')
->andWhere($qb->expr()->isNotNull('activity.travelTime')); ->andWhere($qb->expr()->isNotNull('activity.travelTime'));
$qb if ($this->filterStatsByCenters) {
->andWhere( $qb
$qb->expr()->exists( ->andWhere(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part $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) 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) WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
' '
)
) )
) ->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers); }
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);

View File

@@ -19,11 +19,19 @@ use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class CountActivity implements ExportInterface, GroupedExportInterface class CountActivity implements ExportInterface, GroupedExportInterface
{ {
public function __construct(protected ActivityRepository $activityRepository) {} private readonly bool $filterStatsByCenters;
public function __construct(
private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) {} public function buildForm(FormBuilderInterface $builder) {}
@@ -82,23 +90,25 @@ class CountActivity implements ExportInterface, GroupedExportInterface
$qb = $this->activityRepository $qb = $this->activityRepository
->createQueryBuilder('activity') ->createQueryBuilder('activity')
->join('activity.person', 'person') ->join('activity.person', 'person');
->join('person.centerHistory', 'centerHistory');
$qb->select('COUNT(activity.id) as export_count_activity'); $qb->select('COUNT(activity.id) as export_count_activity');
$qb if ($this->filterStatsByCenters) {
->where( $qb
$qb->expr()->andX( ->join('person.centerHistory', 'centerHistory')
$qb->expr()->lte('centerHistory.startDate', 'activity.date'), ->where(
$qb->expr()->orX( $qb->expr()->andX(
$qb->expr()->isNull('centerHistory.endDate'), $qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date') $qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
) )
) )
) ->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->andWhere($qb->expr()->in('centerHistory.center', ':centers')) ->setParameter('centers', $centers);
->setParameter('centers', $centers); }
return $qb; return $qb;
} }

View File

@@ -23,6 +23,7 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\DBAL\Exception\InvalidArgumentException; use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Constraints\Callback;
@@ -44,8 +45,17 @@ class ListActivity implements ListInterface, GroupedExportInterface
'person_lastname', 'person_lastname',
'person_id', 'person_id',
]; ];
private readonly bool $filterStatsByCenters;
public function __construct(protected EntityManagerInterface $entityManager, protected TranslatorInterface $translator, protected TranslatableStringHelperInterface $translatableStringHelper, private readonly ActivityRepository $activityRepository) {} public function __construct(
protected EntityManagerInterface $entityManager,
protected TranslatorInterface $translator,
protected TranslatableStringHelperInterface $translatableStringHelper,
private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
@@ -188,20 +198,22 @@ class ListActivity implements ListInterface, GroupedExportInterface
$qb $qb
->from('ChillActivityBundle:Activity', 'activity') ->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'actperson') ->join('activity.person', 'actperson');
->join('actperson.centerHistory', 'centerHistory');
$qb->where( if ($this->filterStatsByCenters) {
$qb->expr()->andX( $qb->join('actperson.centerHistory', 'centerHistory');
$qb->expr()->lte('centerHistory.startDate', 'activity.date'), $qb->where(
$qb->expr()->orX( $qb->expr()->andX(
$qb->expr()->isNull('centerHistory.endDate'), $qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date') $qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
) )
) )
) ->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->andWhere($qb->expr()->in('centerHistory.center', ':centers')) ->setParameter('centers', $centers);
->setParameter('centers', $centers); }
foreach ($this->fields as $f) { foreach ($this->fields as $f) {
if (\in_array($f, $data['fields'], true)) { if (\in_array($f, $data['fields'], true)) {

View File

@@ -20,6 +20,7 @@ use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations; use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
/** /**
@@ -30,17 +31,21 @@ use Symfony\Component\Form\FormBuilderInterface;
class StatActivityDuration implements ExportInterface, GroupedExportInterface class StatActivityDuration implements ExportInterface, GroupedExportInterface
{ {
final public const SUM = 'sum'; final public const SUM = 'sum';
private readonly bool $filterStatsByCenters;
/** /**
* @param string $action the stat to perform * @param string $action the stat to perform
*/ */
public function __construct( public function __construct(
private readonly ActivityRepository $activityRepository, private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
/** /**
* The action for this report. * The action for this report.
*/ */
protected string $action = 'sum' protected string $action = 'sum'
) {} ) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder) {} public function buildForm(FormBuilderInterface $builder) {}
@@ -119,21 +124,23 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
} }
$qb->select($select) $qb->select($select)
->join('activity.person', 'person') ->join('activity.person', 'person');
->join('person.centerHistory', 'centerHistory');
$qb if ($this->filterStatsByCenters) {
->where( $qb
$qb->expr()->andX( ->join('person.centerHistory', 'centerHistory')
$qb->expr()->lte('centerHistory.startDate', 'activity.date'), ->where(
$qb->expr()->orX( $qb->expr()->andX(
$qb->expr()->isNull('centerHistory.endDate'), $qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date') $qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
) )
) )
) ->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->andWhere($qb->expr()->in('centerHistory.center', ':centers')) ->setParameter('centers', $centers);
->setParameter('centers', $centers); }
return $qb; return $qb;
} }

View File

@@ -13,16 +13,12 @@ namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; 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; use Symfony\Contracts\Translation\TranslatorInterface;
class ActivityDateFilter implements FilterInterface class ActivityDateFilter implements FilterInterface
@@ -74,46 +70,6 @@ class ActivityDateFilter implements FilterInterface
->add('date_to', PickRollingDateType::class, [ ->add('date_to', PickRollingDateType::class, [
'label' => 'Activities before this date', 'label' => '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('This date should be after '
.'the date given in "Implied in an activity after '
.'this date" field')
));
}
}
});
} }
public function getFormDefaultData(): array public function getFormDefaultData(): array

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class ActivityPresenceFilter implements FilterInterface
{
public function __construct(
private TranslatableStringHelperInterface $translatableStringHelper,
private TranslatorInterface $translator
) {}
public function getTitle()
{
return 'export.filter.activity.by_presence.Filter activity by activity presence';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('presences', EntityType::class, [
'class' => ActivityPresence::class,
'choice_label' => fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName())
.($presence->isActive() ? '' : ' ('.$this->translator->trans('inactive').')'),
'multiple' => true,
'expanded' => true,
'label' => 'export.filter.activity.by_presence.presences',
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string')
{
$presences = array_map(
fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName()),
$data['presences'] instanceof Collection ? $data['presences']->toArray() : $data['presences']
);
return [
'export.filter.activity.by_presence.Filtered by activity presence: only %presences%',
['%presences%' => implode(', ', $presences)],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->andWhere('activity.attendee IN (:activity_presence_filter_presences)')
->setParameter('activity_presence_filter_presences', $data['presences']);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

@@ -80,7 +80,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
// collect all the reasons'name used in this filter in one array // collect all the reasons'name used in this filter in one array
$reasonsNames = array_map( $reasonsNames = array_map(
fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()), fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()),
$this->activityTypeRepository->findBy(['id' => $data['types']->toArray()]) $this->activityTypeRepository->findBy(['id' => $data['types'] instanceof \Doctrine\Common\Collections\Collection ? $data['types']->toArray() : $data['types']])
); );
return ['Filtered by activity type: only %list%', [ return ['Filtered by activity type: only %list%', [

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class CreatorJobFilter implements FilterInterface
{
private const PREFIX = 'acp_act_filter_creator_job';
public function __construct(
private TranslatableStringHelper $translatableStringHelper,
private TranslatorInterface $translator,
private UserJobRepositoryInterface $userJobRepository,
) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.createdBy', "{$p}_user")
->leftJoin(
UserJobHistory::class,
"{$p}_history",
Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// job_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->andWhere(
$qb->expr()->in("{$p}_history.job", ":{$p}_jobs")
)
->setParameter(
"{$p}_jobs",
$data['jobs'],
);
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('jobs', EntityType::class, [
'choices' => $this->userJobRepository->findAllOrderedByName(),
'class' => UserJob::class,
'choice_label' => fn (UserJob $s) => $this->translatableStringHelper->localize(
$s->getLabel()
).($s->isActive() ? '' : '('.$this->translator->trans('inactive').')'),
'label' => 'export.filter.activity.by_creator_job.job_form_label',
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$jobs = array_map(
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
$data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
);
return ['export.filter.activity.by_creator_job.Filtered activity by user job: only %jobs%', [
'%jobs%' => implode(', ', $jobs),
]];
}
public function getFormDefaultData(): array
{
return [
'jobs' => [],
];
}
public function getTitle(): string
{
return 'export.filter.activity.by_creator_job.Filter activity by user job';
}
}

View File

@@ -21,9 +21,9 @@ use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
class UserScopeFilter implements FilterInterface class CreatorScopeFilter implements FilterInterface
{ {
private const PREFIX = 'acp_act_filter_user_scope'; // creator ? cfr translation private const PREFIX = 'acp_act_filter_creator_scope';
public function __construct( public function __construct(
private readonly TranslatableStringHelper $translatableStringHelper private readonly TranslatableStringHelper $translatableStringHelper
@@ -39,7 +39,7 @@ class UserScopeFilter implements FilterInterface
$p = self::PREFIX; $p = self::PREFIX;
$qb $qb
->leftJoin('activity.user', "{$p}_user") // createdBy ? cfr translation ->leftJoin('activity.createdBy', "{$p}_user")
->leftJoin( ->leftJoin(
UserScopeHistory::class, UserScopeHistory::class,
"{$p}_history", "{$p}_history",

View File

@@ -17,6 +17,7 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@@ -79,7 +80,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
// collect all the reasons'name used in this filter in one array // collect all the reasons'name used in this filter in one array
$reasonsNames = array_map( $reasonsNames = array_map(
fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"', fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
$this->activityReasonRepository->findBy(['id' => $data['reasons']->toArray()]) $this->activityReasonRepository->findBy(['id' => $data['reasons'] instanceof Collection ? $data['reasons']->toArray() : $data['reasons']])
); );
return [ return [

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Tests\Export\Filter\PersonsFilterTest;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see PersonsFilterTest
*/
final readonly class PersonsFilter implements FilterInterface
{
private const PREFIX = 'act_persons_filter';
public function __construct(private PersonRenderInterface $personRender) {}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$orX = $qb->expr()->orX();
foreach (array_values($data['accepted_persons']) as $key => $person) {
$orX->add($qb->expr()->isMemberOf(":{$p}_p_{$key}", 'activity.persons'));
$qb->setParameter(":{$p}_p_{$key}", $person);
}
$qb->andWhere($orX);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_persons', PickPersonDynamicType::class, [
'multiple' => true,
'label' => 'export.filter.activity.by_persons.persons taking part on the activity',
]);
}
public function getFormDefaultData(): array
{
return [
'accepted_persons' => [],
];
}
public function describeAction($data, $format = 'string')
{
$users = [];
foreach ($data['accepted_persons'] as $u) {
$users[] = $this->personRender->renderString($u, []);
}
return ['export.filter.activity.by_persons.Filtered activity by persons: only %persons%', [
'%persons%' => implode(', ', $users),
]];
}
public function getTitle(): string
{
return 'export.filter.activity.by_persons.Filter activity by persons';
}
}

View File

@@ -23,8 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class SentReceivedFilter implements FilterInterface class SentReceivedFilter implements FilterInterface
{ {
private const CHOICES = [ private const CHOICES = [
'is sent' => Activity::SENTRECEIVED_SENT, 'export.filter.activity.by_sent_received.is sent' => Activity::SENTRECEIVED_SENT,
'is received' => Activity::SENTRECEIVED_RECEIVED, 'export.filter.activity.by_sent_received.is received' => Activity::SENTRECEIVED_RECEIVED,
]; ];
private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT; private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT;
@@ -64,6 +64,7 @@ class SentReceivedFilter implements FilterInterface
'multiple' => false, 'multiple' => false,
'expanded' => true, 'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE, 'empty_data' => self::DEFAULT_CHOICE,
'label' => 'export.filter.activity.by_sent_received.Sent or received',
]); ]);
} }

View File

@@ -17,6 +17,7 @@ use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -80,7 +81,7 @@ class UsersJobFilter implements FilterInterface
', ', ', ',
array_map( array_map(
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
$data['jobs']->toArray() $data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
) )
), ),
]]; ]];

View File

@@ -18,6 +18,7 @@ use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -83,7 +84,7 @@ class UsersScopeFilter implements FilterInterface
', ', ', ',
array_map( array_map(
fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
$data['scopes']->toArray() $data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes']
) )
), ),
]]; ]];

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByActivityTypeAggregator;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ByActivityTypeAggregatorTest extends AbstractAggregatorTest
{
private RollingDateConverterInterface $rollingDateConverter;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->rollingDateConverter = self::$container->get(RollingDateConverterInterface::class);
$this->activityTypeRepository = self::$container->get(ActivityTypeRepositoryInterface::class);
$this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class);
}
public function getAggregator()
{
return new ByActivityTypeAggregator(
$this->rollingDateConverter,
$this->activityTypeRepository,
$this->translatableStringHelper,
);
}
public function getFormData()
{
return [
[
'after_date' => null,
'before_date' => null,
],
[
'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'before_date' => null,
],
[
'after_date' => null,
'before_date' => new RollingDate(RollingDate::T_TODAY),
],
[
'after_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'before_date' => new RollingDate(RollingDate::T_TODAY),
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(distinct acp.id)')
->from(AccompanyingPeriod::class, 'acp'),
];
}
}

View File

@@ -0,0 +1,62 @@
<?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\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ActivityPresenceAggregator;
use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class ActivityPresenceAggregatorTest extends AbstractAggregatorTest
{
private TranslatableStringHelperInterface $translatableStringHelper;
private ActivityPresenceRepositoryInterface $activityPresenceRepository;
protected function setUp(): void
{
self::bootKernel();
$this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class);
$this->activityPresenceRepository = self::$container->get(ActivityPresenceRepositoryInterface::class);
}
public function getAggregator()
{
return new ActivityPresenceAggregator($this->activityPresenceRepository, $this->translatableStringHelper);
}
public function getFormData()
{
return [
[],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity'),
];
}
}

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator; namespace Chill\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\JobScopeAggregator; use Chill\ActivityBundle\Export\Aggregator\CreatorJobAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@@ -21,15 +21,15 @@ use Doctrine\ORM\EntityManagerInterface;
* *
* @coversNothing * @coversNothing
*/ */
final class JobScopeAggregatorTest extends AbstractAggregatorTest final class CreatorJobAggregatorTest extends AbstractAggregatorTest
{ {
private JobScopeAggregator $aggregator; private CreatorJobAggregator $aggregator;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->aggregator = self::$container->get(JobScopeAggregator::class); $this->aggregator = self::$container->get(CreatorJobAggregator::class);
} }
public function getAggregator() public function getAggregator()

View File

@@ -0,0 +1,57 @@
<?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\Aggregator\PersonAggregators;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\PersonAggregators\PersonAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class PersonAggregatorTest extends AbstractAggregatorTest
{
private LabelPersonHelper $labelPersonHelper;
protected function setUp(): void
{
self::bootKernel();
$this->labelPersonHelper = self::$container->get(LabelPersonHelper::class);
}
public function getAggregator()
{
return new PersonAggregator($this->labelPersonHelper);
}
public function getFormData()
{
return [[]];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity'),
];
}
}

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\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\PersonsAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class PersonsAggregatorTest extends AbstractAggregatorTest
{
private LabelPersonHelper $labelPersonHelper;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->labelPersonHelper = self::$container->get(LabelPersonHelper::class);
}
public function getAggregator()
{
return new PersonsAggregator($this->labelPersonHelper);
}
public function getFormData()
{
return [
[],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity'),
];
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP; namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityDuration; use Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityDuration;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
/** /**
@@ -21,18 +22,17 @@ use Chill\MainBundle\Test\Export\AbstractExportTest;
*/ */
final class AvgActivityDurationTest extends AbstractExportTest final class AvgActivityDurationTest extends AbstractExportTest
{ {
private AvgActivityDuration $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.avg_activity_duration_linked_to_acp');
} }
public function getExport() public function getExport()
{ {
return $this->export; $activityRepository = self::$container->get(ActivityRepository::class);
yield new AvgActivityDuration($activityRepository, $this->getParameters(true));
yield new AvgActivityDuration($activityRepository, $this->getParameters(false));
} }
public function getFormData(): array public function getFormData(): array

View File

@@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityVisitDuration; use Chill\ActivityBundle\Export\Export\LinkedToACP\AvgActivityVisitDuration;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
use Doctrine\ORM\EntityManagerInterface;
/** /**
* @internal * @internal
@@ -21,18 +22,17 @@ use Chill\MainBundle\Test\Export\AbstractExportTest;
*/ */
final class AvgActivityVisitDurationTest extends AbstractExportTest final class AvgActivityVisitDurationTest extends AbstractExportTest
{ {
private AvgActivityVisitDuration $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.avg_activity_visit_duration_linked_to_acp');
} }
public function getExport() public function getExport()
{ {
return $this->export; $em = self::$container->get(EntityManagerInterface::class);
yield new AvgActivityVisitDuration($em, $this->getParameters(true));
yield new AvgActivityVisitDuration($em, $this->getParameters(false));
} }
public function getFormData(): array public function getFormData(): array

View File

@@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity; use Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
use Doctrine\ORM\EntityManagerInterface;
/** /**
* @internal * @internal
@@ -21,18 +22,17 @@ use Chill\MainBundle\Test\Export\AbstractExportTest;
*/ */
final class CountActivityTest extends AbstractExportTest final class CountActivityTest extends AbstractExportTest
{ {
private CountActivity $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.count_activity_linked_to_acp');
} }
public function getExport() public function getExport()
{ {
return $this->export; $em = self::$container->get(EntityManagerInterface::class);
yield new CountActivity($em, $this->getParameters(true));
yield new CountActivity($em, $this->getParameters(false));
} }
public function getFormData(): array public function getFormData(): array

View File

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

View File

@@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityDuration; use Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityDuration;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
use Doctrine\ORM\EntityManagerInterface;
/** /**
* @internal * @internal
@@ -21,18 +22,17 @@ use Chill\MainBundle\Test\Export\AbstractExportTest;
*/ */
final class SumActivityDurationTest extends AbstractExportTest final class SumActivityDurationTest extends AbstractExportTest
{ {
private SumActivityDuration $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.sum_activity_duration_linked_to_acp');
} }
public function getExport() public function getExport()
{ {
return $this->export; $em = self::$container->get(EntityManagerInterface::class);
yield new SumActivityDuration($em, $this->getParameters(true));
yield new SumActivityDuration($em, $this->getParameters(false));
} }
public function getFormData(): array public function getFormData(): array

View File

@@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityVisitDuration; use Chill\ActivityBundle\Export\Export\LinkedToACP\SumActivityVisitDuration;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
use Doctrine\ORM\EntityManagerInterface;
/** /**
* @internal * @internal
@@ -32,7 +33,10 @@ final class SumActivityVisitDurationTest extends AbstractExportTest
public function getExport() public function getExport()
{ {
return $this->export; $em = self::$container->get(EntityManagerInterface::class);
yield new SumActivityVisitDuration($em, $this->getParameters(true));
yield new SumActivityVisitDuration($em, $this->getParameters(false));
} }
public function getFormData(): array public function getFormData(): array

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson; namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity; use Chill\ActivityBundle\Export\Export\LinkedToPerson\CountActivity;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
/** /**
@@ -21,18 +22,17 @@ use Chill\MainBundle\Test\Export\AbstractExportTest;
*/ */
final class CountActivityTest extends AbstractExportTest final class CountActivityTest extends AbstractExportTest
{ {
private CountActivity $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.count_activity_linked_to_person');
} }
public function getExport() public function getExport()
{ {
return $this->export; $activityRepository = self::$container->get(ActivityRepository::class);
yield new CountActivity($activityRepository, $this->getParameters(true));
yield new CountActivity($activityRepository, $this->getParameters(false));
} }
public function getFormData(): array public function getFormData(): array

View File

@@ -12,8 +12,12 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson; namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity; use Chill\ActivityBundle\Export\Export\LinkedToPerson\ListActivity;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @internal * @internal
@@ -24,14 +28,12 @@ final class ListActivityTest extends AbstractExportTest
{ {
use ProphecyTrait; use ProphecyTrait;
private ListActivity $export; private readonly ListActivity $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.list_activity_linked_to_person');
$request = $this->prophesize() $request = $this->prophesize()
->willExtend(\Symfony\Component\HttpFoundation\Request::class); ->willExtend(\Symfony\Component\HttpFoundation\Request::class);
@@ -43,7 +45,26 @@ final class ListActivityTest extends AbstractExportTest
public function getExport() public function getExport()
{ {
return $this->export; $em = self::$container->get(EntityManagerInterface::class);
$translator = self::$container->get(TranslatorInterface::class);
$translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class);
$activityRepository = self::$container->get(ActivityRepository::class);
yield new ListActivity(
$em,
$translator,
$translatableStringHelper,
$activityRepository,
$this->getParameters(true)
);
yield new ListActivity(
$em,
$translator,
$translatableStringHelper,
$activityRepository,
$this->getParameters(false)
);
} }
public function getFormData() public function getFormData()

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson; namespace Chill\ActivityBundle\Tests\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration; use Chill\ActivityBundle\Export\Export\LinkedToPerson\StatActivityDuration;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Test\Export\AbstractExportTest; use Chill\MainBundle\Test\Export\AbstractExportTest;
/** /**
@@ -23,18 +24,18 @@ use Chill\MainBundle\Test\Export\AbstractExportTest;
*/ */
final class StatActivityDurationTest extends AbstractExportTest final class StatActivityDurationTest extends AbstractExportTest
{ {
private StatActivityDuration $export; private readonly StatActivityDuration $export;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->export = self::$container->get('chill.activity.export.sum_activity_duration_linked_to_person');
} }
public function getExport() public function getExport()
{ {
return $this->export; $activityRepository = self::$container->get(ActivityRepository::class);
yield new StatActivityDuration($activityRepository, $this->getParameters(true), 'sum');
yield new StatActivityDuration($activityRepository, $this->getParameters(false), 'sum');
} }
public function getFormData(): array public function getFormData(): array

View File

@@ -0,0 +1,76 @@
<?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\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ActivityPresenceFilter;
use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class ActivityPresenceFilterTest extends AbstractFilterTest
{
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->translator = self::$container->get(TranslatorInterface::class);
$this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class);
}
public function getFilter()
{
return new ActivityPresenceFilter($this->translatableStringHelper, $this->translator);
}
public function getFormData()
{
self::bootKernel();
$presences = self::$container->get(ActivityPresenceRepositoryInterface::class)
->findAll();
return [
[
'presences' => $presences,
],
[
'presences' => new ArrayCollection($presences),
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
yield $em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity');
self::ensureKernelShutdown();
}
}

View File

@@ -52,6 +52,7 @@ final class ActivityReasonFilterTest extends AbstractFilterTest
public function getFormData() public function getFormData()
{ {
self::bootKernel(); self::bootKernel();
$data = [];
$em = self::$container $em = self::$container
->get(EntityManagerInterface::class); ->get(EntityManagerInterface::class);
@@ -62,10 +63,13 @@ final class ActivityReasonFilterTest extends AbstractFilterTest
// generate an array of 5 different combination of results // generate an array of 5 different combination of results
for ($i = 0; 5 > $i; ++$i) { for ($i = 0; 5 > $i; ++$i) {
yield ['reasons' => new ArrayCollection(array_splice($reasons, ($i + 1) * -1))]; $data[] = ['reasons' => new ArrayCollection(array_splice($reasons, ($i + 1) * -1))];
$data[] = ['reasons' => array_splice($reasons, ($i + 1) * -1)];
} }
self::ensureKernelShutdown(); self::ensureKernelShutdown();
return $data;
} }
public function getQueryBuilders(): iterable public function getQueryBuilders(): iterable

View File

@@ -57,6 +57,9 @@ final class ActivityTypeFilterTest extends AbstractFilterTest
$data[] = [ $data[] = [
'types' => new ArrayCollection([$a]), 'types' => new ArrayCollection([$a]),
]; ];
/*$data[] = [
'types' => [$a],
];*/
} }
return $data; return $data;

View File

@@ -0,0 +1,75 @@
<?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\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\CreatorJobFilter;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class CreatorJobFilterTest extends AbstractFilterTest
{
private EntityManagerInterface $entityManager;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
private UserJobRepositoryInterface $userJobRepository;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
$this->translatableStringHelper = self::$container->get(TranslatableStringHelperInterface::class);
$this->translator = self::$container->get(TranslatorInterface::class);
$this->userJobRepository = self::$container->get(UserJobRepositoryInterface::class);
}
public function getFilter()
{
return new CreatorJobFilter(
$this->translatableStringHelper,
$this->translator,
$this->userJobRepository
);
}
public function getFormData()
{
$this->setUp();
$jobs = $this->userJobRepository->findAll();
return [
['jobs' => $jobs],
];
}
public function getQueryBuilders()
{
self::setUp();
return [
$this->entityManager->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity')
->join('activity.user', 'actuser'),
];
}
}

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Filter; namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\UserScopeFilter; use Chill\ActivityBundle\Export\Filter\CreatorScopeFilter;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@@ -22,15 +22,15 @@ use Doctrine\ORM\EntityManagerInterface;
* *
* @coversNothing * @coversNothing
*/ */
final class UserScopeFilterTest extends AbstractFilterTest final class CreatorScopeFilterTest extends AbstractFilterTest
{ {
private UserScopeFilter $filter; private CreatorScopeFilter $filter;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->filter = self::$container->get(UserScopeFilter::class); $this->filter = self::$container->get(CreatorScopeFilter::class);
} }
public function getFilter() public function getFilter()

View File

@@ -0,0 +1,72 @@
<?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\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\PersonsFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
*
* @coversNothing
*/
class PersonsFilterTest extends AbstractFilterTest
{
private PersonRenderInterface $personRender;
protected function setUp(): void
{
parent::setUp();
self::bootKernel();
$this->personRender = self::$container->get(PersonRenderInterface::class);
}
public function getFilter()
{
return new PersonsFilter($this->personRender);
}
public function getFormData()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$persons = $em->createQuery('SELECT p FROM '.Person::class.' p ')
->setMaxResults(2)
->getResult();
self::ensureKernelShutdown();
return [
[
'accepted_persons' => $persons,
],
];
}
public function getQueryBuilders()
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
yield $em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity');
self::ensureKernelShutdown();
}
}

View File

@@ -16,6 +16,10 @@ services:
tags: tags:
- { name: chill.export, alias: 'list_activity_linked_to_person' } - { name: chill.export, alias: 'list_activity_linked_to_person' }
Chill\ActivityBundle\Export\Export\LinkedToACP\CountPersonsOnActivity:
tags:
- { name: chill.export, alias: 'count_person_on_activity' }
chill.activity.export.count_activity_linked_to_acp: chill.activity.export.count_activity_linked_to_acp:
class: Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity class: Chill\ActivityBundle\Export\Export\LinkedToACP\CountActivity
tags: tags:
@@ -109,10 +113,16 @@ services:
tags: tags:
- { name: chill.export_filter, alias: 'activity_user_filter' } - { name: chill.export_filter, alias: 'activity_user_filter' }
Chill\ActivityBundle\Export\Filter\UserScopeFilter: Chill\ActivityBundle\Export\Filter\CreatorScopeFilter:
tags: tags:
# WARNING: for backward compatibility reason, the alias is named with userscope. Changing this will
# affect all saved exports (unless we write a migration for that)
- { name: chill.export_filter, alias: 'activity_userscope_filter' } - { name: chill.export_filter, alias: 'activity_userscope_filter' }
Chill\ActivityBundle\Export\Filter\CreatorJobFilter:
tags:
- { name: chill.export_filter, alias: 'activity_creatorjob_filter' }
Chill\ActivityBundle\Export\Filter\UsersJobFilter: Chill\ActivityBundle\Export\Filter\UsersJobFilter:
tags: tags:
- { name: chill.export_filter, alias: 'activity_usersjob_filter' } - { name: chill.export_filter, alias: 'activity_usersjob_filter' }
@@ -129,6 +139,11 @@ services:
tags: tags:
- { name: chill.export_filter, alias: 'period_having_activity_betw_dates_filter' } - { name: chill.export_filter, alias: 'period_having_activity_betw_dates_filter' }
Chill\ActivityBundle\Export\Filter\ActivityPresenceFilter:
tags:
- { name: chill.export_filter, alias: 'activity_presence_filter' }
## Aggregators ## Aggregators
Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator: Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator:
tags: tags:
@@ -177,7 +192,7 @@ services:
tags: tags:
- { name: chill.export_aggregator, alias: activity_creator_scope_aggregator } - { name: chill.export_aggregator, alias: activity_creator_scope_aggregator }
Chill\ActivityBundle\Export\Aggregator\JobScopeAggregator: Chill\ActivityBundle\Export\Aggregator\CreatorJobAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: activity_creator_job_aggregator } - { name: chill.export_aggregator, alias: activity_creator_job_aggregator }
@@ -200,3 +215,23 @@ services:
Chill\ActivityBundle\Export\Aggregator\SentReceivedAggregator: Chill\ActivityBundle\Export\Aggregator\SentReceivedAggregator:
tags: tags:
- { name: chill.export_aggregator, alias: activity_sentreceived_aggregator } - { name: chill.export_aggregator, alias: activity_sentreceived_aggregator }
Chill\ActivityBundle\Export\Filter\PersonsFilter:
tags:
- { name: chill.export_filter, alias: activity_by_persons_filter }
Chill\ActivityBundle\Export\Aggregator\PersonsAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_by_persons_aggregator }
Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByActivityTypeAggregator:
tags:
- { name: chill.export_aggregator, alias: acp_by_activity_type_aggregator }
Chill\ActivityBundle\Export\Aggregator\ActivityPresenceAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_presence_agg }
Chill\ActivityBundle\Export\Aggregator\PersonAggregators\PersonAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_person_agg }

View File

@@ -275,7 +275,7 @@ Filter activity by linked socialaction: Filtrer les échanges par action liée
'Filtered activity by linked socialaction: only %actions%': "Filtré par action liée: uniquement %actions%" 'Filtered activity by linked socialaction: only %actions%': "Filtré par action liée: uniquement %actions%"
Filter activity by linked socialissue: Filtrer les échanges par problématique liée Filter activity by linked socialissue: Filtrer les échanges par problématique liée
'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%" 'Filtered activity by linked socialissue: only %issues%': "Filtré par problématique liée: uniquement %issues%"
Filter activity by user: Filtrer les échanges par créateur Filter activity by user: Filtrer les échanges par utilisateur principal
Filter activity by users: Filtrer les échanges par utilisateur participant Filter activity by users: Filtrer les échanges par utilisateur participant
Filter activity by creator: Filtrer les échanges par créateur de l'échange Filter activity by creator: Filtrer les échanges par créateur de l'échange
'Filtered activity by user: only %users%': "Filtré par référent: uniquement %users%" 'Filtered activity by user: only %users%': "Filtré par référent: uniquement %users%"
@@ -332,6 +332,11 @@ docgen:
myWorksOnly: Prendre en compte uniquement les actions d'accompagnement dont je suis référent myWorksOnly: Prendre en compte uniquement les actions d'accompagnement dont je suis référent
export: export:
export:
count_person_on_activity:
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
list: list:
activity: activity:
users name: Nom des utilisateurs users name: Nom des utilisateurs
@@ -359,10 +364,10 @@ export:
Filter by users job: Filtrer les échanges par métier d'au moins un utilisateur participant Filter by users job: Filtrer les échanges par métier d'au moins un utilisateur participant
'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%' 'Filtered activity by users job: only %jobs%': 'Filtré par métier d''au moins un utilisateur participant: seulement %jobs%'
by_users_scope: by_users_scope:
Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant Filter by users scope: Filtrer les échanges par service d'au moins un utilisateur participant
'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%' 'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%'
course_having_activity_between_date: course_having_activity_between_date:
Title: Filtre les parcours ayant reçu un échange entre deux dates Title: Filtrer les parcours ayant reçu un échange entre deux dates
Receiving an activity after: Ayant reçu un échange après le Receiving an activity after: Ayant reçu un échange après le
Receiving an activity before: Ayant reçu un échange avant le Receiving an activity before: Ayant reçu un échange avant le
acp_by_activity_type: acp_by_activity_type:
@@ -372,13 +377,39 @@ export:
Implied in an activity before this date: Impliqué dans un échange avant cette date Implied in an activity before this date: Impliqué dans un échange avant cette date
Activity reasons for those activities: Sujets de ces échanges Activity reasons for those activities: Sujets de ces échanges
if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte
title: Filtrer les personnes ayant été associés à un échange au cours de la période title: Filtrer les usagers ayant été associés à un échange au cours de la période
date mismatch: La date de fin de la période doit être supérieure à la date du début date mismatch: La date de fin de la période doit être supérieure à la date du début
by_creator_scope: by_creator_scope:
Filter activity by user scope: Filtrer les échanges par service du créateur de l'échange Filter activity by user scope: Filtrer les échanges par service du créateur de l'échange
'Filtered activity by user scope: only %scopes%': "Filtré par service du créateur: uniquement %scopes%" 'Filtered activity by user scope: only %scopes%': "Filtré par service du créateur de l'échange: uniquement %scopes%"
by_creator_job:
job_form_label: Métiers
Filter activity by user job: Filtrer les échanges par métier du créateur de l'échange
'Filtered activity by user job: only %jobs%': "Filtré par service du créateur de l'échange: uniquement %jobs%"
by_persons:
Filter activity by persons: Filtrer les échanges par usager participant
'Filtered activity by persons: only %persons%': 'Échanges filtrés par usagers participants: seulement %persons%'
persons taking part on the activity: Usagers participants à l'échange
by_sent_received:
Sent or received: Envoyé ou reçu
is sent: envoyé
is received: reçu
by_presence:
Filter activity by activity presence: Filtrer les échanges par présence de l'usager
presences: Présences
'Filtered by activity presence: only %presences%': 'Filtré par présence de l''usager: seulement %presences%'
aggregator: aggregator:
person:
by_person:
title: Grouper les échanges par usager (dossier d'usager dans lequel l'échange est enregistré)
person: Usager
acp:
by_activity_type:
title: Grouper les parcours par type d'échange
after_date: Uniquement échanges après cette date
before_date: Uniquement échanges avant cette date
activity_type: Types d'échange
activity: activity:
by_sent_received: by_sent_received:
Sent or received: Envoyé ou reçu Sent or received: Envoyé ou reçu
@@ -398,8 +429,14 @@ export:
Group activity by creator scope: Grouper les échanges par service du créateur de l'échange Group activity by creator scope: Grouper les échanges par service du créateur de l'échange
Calc date: Date de calcul du service du créateur de l'échange Calc date: Date de calcul du service du créateur de l'échange
by_creator_job: by_creator_job:
Group activity by creator job: Grouper les échanges par service du créateur de l'échange Group activity by creator job: Grouper les échanges par métier du créateur de l'échange
Calc date: Date de calcul du service du créateur de l'échange Calc date: Date de calcul du métier du créateur de l'échange
by_persons:
Group activity by persons: Grouper les échanges par usager participant
Persons: Usagers participants
by_activity_presence:
Group activity by presence: Grouper les échanges par présence de l'usager
header: Présence de(s) usager(s)
generic_doc: generic_doc:
filter: filter:

View File

@@ -17,6 +17,7 @@ use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -76,7 +77,7 @@ class ByActivityTypeFilter implements FilterInterface
{ {
$types = array_map( $types = array_map(
fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()), fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()),
$data['types']->toArray() $data['types'] instanceof Collection ? $data['types']->toArray() : $data['types']
); );
return ['export.filter.Filtered by aside activity type: only %type%', [ return ['export.filter.Filtered by aside activity type: only %type%', [

View File

@@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserLocationType; use Chill\MainBundle\Form\Type\PickUserLocationType;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -54,7 +55,12 @@ final readonly class ByLocationFilter implements FilterInterface
public function describeAction($data, $format = 'string'): array public function describeAction($data, $format = 'string'): array
{ {
$locations = $data['locations']->map(fn (Location $l): string => $l->getName()); $extractFunction = fn (Location $l): string => $l->getName();
if ($data['locations'] instanceof Collection) {
$locations = $data['locations']->map($extractFunction);
} else {
$locations = array_map($extractFunction, $data['locations']);
}
return ['export.filter.Filtered by aside activity location: only %location%', [ return ['export.filter.Filtered by aside activity location: only %location%', [
'%location%' => implode(', ', $locations), '%location%' => implode(', ', $locations),

View File

@@ -17,6 +17,7 @@ use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -80,7 +81,7 @@ class ByUserJobFilter implements FilterInterface
', ', ', ',
array_map( array_map(
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
$data['jobs']->toArray() $data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
) )
), ),
]]; ]];

View File

@@ -18,6 +18,7 @@ use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@@ -83,7 +84,7 @@ class ByUserScopeFilter implements FilterInterface
', ', ', ',
array_map( array_map(
fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
$data['scopes']->toArray() $data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes']
) )
), ),
]]; ]];

View File

@@ -25,7 +25,9 @@ use Symfony\Component\Validator\Exception\LogicException;
class CountCalendars implements ExportInterface, GroupedExportInterface class CountCalendars implements ExportInterface, GroupedExportInterface
{ {
public function __construct(private readonly CalendarRepository $calendarRepository) {} public function __construct(
private readonly CalendarRepository $calendarRepository,
) {}
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {

View File

@@ -356,20 +356,6 @@ final class CalendarSyncerTest extends TestCase
} }
JSON; JSON;
protected function setUp(): void
{
parent::setUp();
// all tests should run when timezone = +02:00
$brussels = new \DateTimeZone('Europe/Brussels');
if (7200 === $brussels->getOffset(new \DateTimeImmutable())) {
date_default_timezone_set('Europe/Brussels');
} else {
date_default_timezone_set('Europe/Moscow');
}
}
public function testHandleAttendeesConfirmingCalendar(): void public function testHandleAttendeesConfirmingCalendar(): void
{ {
$machineHttpClient = new MockHttpClient([ $machineHttpClient = new MockHttpClient([

View File

@@ -18,12 +18,15 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* Class CustomFieldController. * Class CustomFieldController.
*/ */
class CustomFieldController extends AbstractController class CustomFieldController extends AbstractController
{ {
public function __construct(private readonly TranslatorInterface $translator) {}
/** /**
* Creates a new CustomField entity. * Creates a new CustomField entity.
* *
@@ -40,13 +43,13 @@ class CustomFieldController extends AbstractController
$em->persist($entity); $em->persist($entity);
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->translator
->trans('The custom field has been created')); ->trans('The custom field has been created'));
return $this->redirectToRoute('customfieldsgroup_show', ['id' => $entity->getCustomFieldsGroup()->getId()]); return $this->redirectToRoute('customfieldsgroup_show', ['id' => $entity->getCustomFieldsGroup()->getId()]);
} }
$this->addFlash('error', $this->get('translator') $this->addFlash('error', $this->translator
->trans('The custom field form contains errors')); ->trans('The custom field form contains errors'));
return $this->render('@ChillCustomFields/CustomField/new.html.twig', [ return $this->render('@ChillCustomFields/CustomField/new.html.twig', [
@@ -130,13 +133,13 @@ class CustomFieldController extends AbstractController
if ($editForm->isSubmitted() && $editForm->isValid()) { if ($editForm->isSubmitted() && $editForm->isValid()) {
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->translator
->trans('The custom field has been updated')); ->trans('The custom field has been updated'));
return $this->redirectToRoute('customfield_edit', ['id' => $id]); return $this->redirectToRoute('customfield_edit', ['id' => $id]);
} }
$this->addFlash('error', $this->get('translator') $this->addFlash('error', $this->translator
->trans('The custom field form contains errors')); ->trans('The custom field form contains errors'));
return $this->render('@ChillCustomFields/CustomField/edit.html.twig', [ return $this->render('@ChillCustomFields/CustomField/edit.html.twig', [

View File

@@ -11,14 +11,14 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Serializer\Helper; namespace Chill\DocGeneratorBundle\Serializer\Helper;
use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class NormalizeNullValueHelper class NormalizeNullValueHelper
{ {
public function __construct(private readonly NormalizerInterface $normalizer, private readonly ?string $discriminatorType = null, private readonly ?string $discriminatorValue = null) {} 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 = [];
$data['isNull'] = true; $data['isNull'] = true;
@@ -44,7 +44,7 @@ class NormalizeNullValueHelper
return $data; 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) { if (null === $classMetadata) {
return $initialContext; return $initialContext;

View File

@@ -19,6 +19,7 @@ use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Serializer\Exception\ExceptionInterface; use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Exception\LogicException; use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Mapping\AttributeMetadata; use Symfony\Component\Serializer\Mapping\AttributeMetadata;
use Symfony\Component\Serializer\Mapping\AttributeMetadataInterface;
use Symfony\Component\Serializer\Mapping\ClassMetadata; use Symfony\Component\Serializer\Mapping\ClassMetadata;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
@@ -52,12 +53,15 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
} }
$metadata = $this->classMetadataFactory->getMetadataFor($classMetadataKey); $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) ? $expectedGroups = \array_key_exists(AbstractNormalizer::GROUPS, $context) ?
\is_array($context[AbstractNormalizer::GROUPS]) ? $context[AbstractNormalizer::GROUPS] : [$context[AbstractNormalizer::GROUPS]] \is_array($context[AbstractNormalizer::GROUPS]) ? $context[AbstractNormalizer::GROUPS] : [$context[AbstractNormalizer::GROUPS]]
: []; : [];
$attributes = \array_filter( $attributes = \array_filter(
$metadata->getAttributesMetadata(), $metadata->getAttributesMetadata(),
static function (AttributeMetadata $a) use ($expectedGroups) { static function (AttributeMetadataInterface $a) use ($expectedGroups) {
foreach ($a->getGroups() as $g) { foreach ($a->getGroups() as $g) {
if (\in_array($g, $expectedGroups, true)) { if (\in_array($g, $expectedGroups, true)) {
return true; return true;
@@ -119,7 +123,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
return $type->getName(); return $type->getName();
} }
if ($type instanceof \ReflectionIntersectionType) { 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) { if (ReadableCollection::class === $classString) {
return ReadableCollection::class; return ReadableCollection::class;
} }
@@ -211,7 +215,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
} }
/** /**
* @param array|AttributeMetadata[] $attributes * @param array<AttributeMetadata> $attributes
* *
* @return array * @return array
* *

View File

@@ -118,7 +118,7 @@ class EventController extends AbstractController
$this->addFlash( $this->addFlash(
'success', 'success',
$this->get('translator') $this->translator
->trans('The event has been sucessfully removed') ->trans('The event has been sucessfully removed')
); );
@@ -249,7 +249,7 @@ class EventController extends AbstractController
$em->persist($entity); $em->persist($entity);
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->translator
->trans('The event was created')); ->trans('The event was created'));
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $entity->getId()]); return $this->redirectToRoute('chill_event__event_show', ['event_id' => $entity->getId()]);
@@ -364,7 +364,7 @@ class EventController extends AbstractController
if ($editForm->isValid()) { if ($editForm->isValid()) {
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->translator
->trans('The event was updated')); ->trans('The event was updated'));
return $this->redirectToRoute('chill_event__event_edit', ['event_id' => $event_id]); return $this->redirectToRoute('chill_event__event_edit', ['event_id' => $event_id]);

View File

@@ -23,6 +23,7 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* Class ParticipationController. * Class ParticipationController.
@@ -32,7 +33,7 @@ class ParticipationController extends AbstractController
/** /**
* ParticipationController constructor. * ParticipationController constructor.
*/ */
public function __construct(private readonly LoggerInterface $logger) {} public function __construct(private readonly LoggerInterface $logger, private readonly TranslatorInterface $translator) {}
/** /**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/create", name="chill_event_participation_create") * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/event/participation/create", name="chill_event_participation_create")
@@ -177,7 +178,7 @@ class ParticipationController extends AbstractController
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans( $this->addFlash('success', $this->translator->trans(
'The participations were created' 'The participations were created'
)); ));
@@ -211,7 +212,7 @@ class ParticipationController extends AbstractController
$em->persist($participation); $em->persist($participation);
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans( $this->addFlash('success', $this->translator->trans(
'The participation was created' 'The participation was created'
)); ));
@@ -260,7 +261,7 @@ class ParticipationController extends AbstractController
$this->addFlash( $this->addFlash(
'success', 'success',
$this->get('translator') $this->translator
->trans('The participation has been sucessfully removed') ->trans('The participation has been sucessfully removed')
); );
@@ -340,7 +341,7 @@ class ParticipationController extends AbstractController
switch ($event->getParticipations()->count()) { switch ($event->getParticipations()->count()) {
case 0: case 0:
// if there aren't any participation, redirect to the 'show' view with an add flash // if there aren't any participation, redirect to the 'show' view with an add flash
$this->addFlash('warning', $this->get('translator') $this->addFlash('warning', $this->translator
->trans('There are no participation to edit for this event')); ->trans('There are no participation to edit for this event'));
return $this->redirectToRoute( return $this->redirectToRoute(
@@ -434,7 +435,7 @@ class ParticipationController extends AbstractController
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans( $this->addFlash('success', $this->translator->trans(
'The participation was updated' 'The participation was updated'
)); ));
@@ -480,7 +481,7 @@ class ParticipationController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush(); $this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->get('translator')->trans('The participations ' $this->addFlash('success', $this->translator->trans('The participations '
.'have been successfully updated.')); .'have been successfully updated.'));
return $this->redirectToRoute( return $this->redirectToRoute(
@@ -652,7 +653,7 @@ class ParticipationController extends AbstractController
if ([] === $newParticipations) { if ([] === $newParticipations) {
// if we do not have nay participants, redirect to event view // if we do not have nay participants, redirect to event view
$this->addFlash('error', $this->get('translator')->trans( $this->addFlash('error', $this->translator->trans(
'None of the requested people may participate ' 'None of the requested people may participate '
.'the event: they are maybe already participating.' .'the event: they are maybe already participating.'
)); ));

View File

@@ -27,6 +27,7 @@ use Chill\MainBundle\Security\Authorization\SavedExportVoter;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormFactoryInterface;
@@ -46,6 +47,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/ */
class ExportController extends AbstractController class ExportController extends AbstractController
{ {
private readonly bool $filterStatsByCenters;
public function __construct( public function __construct(
private readonly ChillRedis $redis, private readonly ChillRedis $redis,
private readonly ExportManager $exportManager, private readonly ExportManager $exportManager,
@@ -56,8 +59,11 @@ class ExportController extends AbstractController
private readonly EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
private readonly ExportFormHelper $exportFormHelper, private readonly ExportFormHelper $exportFormHelper,
private readonly SavedExportRepositoryInterface $savedExportRepository, private readonly SavedExportRepositoryInterface $savedExportRepository,
private readonly Security $security private readonly Security $security,
) {} ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
/** /**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/exports/download/{alias}", name="chill_main_export_download", methods={"GET"}) * @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/exports/download/{alias}", name="chill_main_export_download", methods={"GET"})
@@ -275,7 +281,7 @@ class ExportController extends AbstractController
$options = match ($step) { $options = match ($step) {
'export', 'generate_export' => [ 'export', 'generate_export' => [
'export_alias' => $alias, 'export_alias' => $alias,
'picked_centers' => $exportManager->getPickedCenters($data['centers']), 'picked_centers' => $exportManager->getPickedCenters($data['centers'] ?? []),
], ],
'formatter', 'generate_formatter' => [ 'formatter', 'generate_formatter' => [
'export_alias' => $alias, 'export_alias' => $alias,
@@ -298,7 +304,7 @@ class ExportController extends AbstractController
FormType::class, FormType::class,
$defaultFormData, $defaultFormData,
[ [
'method' => $isGenerate ? 'GET' : 'POST', 'method' => $isGenerate ? Request::METHOD_GET : Request::METHOD_POST,
'csrf_protection' => !$isGenerate, 'csrf_protection' => !$isGenerate,
] ]
); );
@@ -333,9 +339,9 @@ class ExportController extends AbstractController
$exportManager = $this->exportManager; $exportManager = $this->exportManager;
// check we have data from the previous step (export step) // check we have data from the previous step (export step)
$data = $this->session->get('centers_step', null); $data = $this->session->get('centers_step', []);
if (null === $data) { if (null === $data && true === $this->filterStatsByCenters) {
return $this->redirectToRoute('chill_main_export_new', [ return $this->redirectToRoute('chill_main_export_new', [
'step' => $this->getNextStep('export', $export, true), 'step' => $this->getNextStep('export', $export, true),
'alias' => $alias, 'alias' => $alias,
@@ -346,7 +352,7 @@ class ExportController extends AbstractController
$form = $this->createCreateFormExport($alias, 'export', $data, $savedExport); $form = $this->createCreateFormExport($alias, 'export', $data, $savedExport);
if ('POST' === $request->getMethod()) { if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {
@@ -400,7 +406,7 @@ class ExportController extends AbstractController
$form = $this->createCreateFormExport($alias, 'formatter', $data, $savedExport); $form = $this->createCreateFormExport($alias, 'formatter', $data, $savedExport);
if ('POST' === $request->getMethod()) { if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {
@@ -484,9 +490,13 @@ class ExportController extends AbstractController
$alias = $rawData['alias']; $alias = $rawData['alias'];
$formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], $savedExport); if ($this->filterStatsByCenters) {
$formCenters->submit($rawData['centers']); $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], $savedExport);
$dataCenters = $formCenters->getData(); $formCenters->submit($rawData['centers']);
$dataCenters = $formCenters->getData();
} else {
$dataCenters = ['centers' => []];
}
$formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, $savedExport); $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, $savedExport);
$formExport->submit($rawData['export']); $formExport->submit($rawData['export']);
@@ -513,12 +523,20 @@ class ExportController extends AbstractController
*/ */
private function selectCentersStep(Request $request, DirectExportInterface|ExportInterface $export, $alias, SavedExport $savedExport = null) private function selectCentersStep(Request $request, DirectExportInterface|ExportInterface $export, $alias, SavedExport $savedExport = null)
{ {
if (!$this->filterStatsByCenters) {
return $this->redirectToRoute('chill_main_export_new', [
'step' => $this->getNextStep('centers', $export),
'alias' => $alias,
'from_saved' => $request->get('from_saved', ''),
]);
}
/** @var \Chill\MainBundle\Export\ExportManager $exportManager */ /** @var \Chill\MainBundle\Export\ExportManager $exportManager */
$exportManager = $this->exportManager; $exportManager = $this->exportManager;
$form = $this->createCreateFormExport($alias, 'centers', [], $savedExport); $form = $this->createCreateFormExport($alias, 'centers', [], $savedExport);
if ('POST' === $request->getMethod()) { if (Request::METHOD_POST === $request->getMethod()) {
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isValid()) { if ($form->isValid()) {

View File

@@ -32,12 +32,13 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class UserController extends CRUDController class UserController extends CRUDController
{ {
final public const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter'; final public const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter';
public function __construct(private readonly LoggerInterface $logger, private readonly ValidatorInterface $validator, private readonly UserPasswordEncoderInterface $passwordEncoder, private readonly UserRepository $userRepository, protected ParameterBagInterface $parameterBag) {} public function __construct(private readonly LoggerInterface $logger, private readonly ValidatorInterface $validator, private readonly UserPasswordEncoderInterface $passwordEncoder, private readonly UserRepository $userRepository, protected ParameterBagInterface $parameterBag, private readonly TranslatorInterface $translator) {}
/** /**
* @Route("/{_locale}/admin/main/user/{uid}/add_link_groupcenter", * @Route("/{_locale}/admin/main/user/{uid}/add_link_groupcenter",
@@ -65,7 +66,7 @@ class UserController extends CRUDController
if (0 === $this->validator->validate($user)->count()) { if (0 === $this->validator->validate($user)->count()) {
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator')->trans('The ' $this->addFlash('success', $this->translator->trans('The '
.'permissions have been successfully added to the user')); .'permissions have been successfully added to the user'));
$returnPathParams = $request->query->has('returnPath') ? $returnPathParams = $request->query->has('returnPath') ?
@@ -115,14 +116,14 @@ class UserController extends CRUDController
try { try {
$user->removeGroupCenter($groupCenter); $user->removeGroupCenter($groupCenter);
} catch (\RuntimeException $ex) { } catch (\RuntimeException $ex) {
$this->addFlash('error', $this->get('translator')->trans($ex->getMessage())); $this->addFlash('error', $this->translator->trans($ex->getMessage()));
return $this->redirectToRoute('chill_crud_admin_user_edit', ['id' => $uid]); return $this->redirectToRoute('chill_crud_admin_user_edit', ['id' => $uid]);
} }
$em->flush(); $em->flush();
$this->addFlash('success', $this->get('translator') $this->addFlash('success', $this->translator
->trans('The permissions where removed.')); ->trans('The permissions where removed.'));
return $this->redirectToRoute('chill_crud_admin_user_edit', ['id' => $uid]); return $this->redirectToRoute('chill_crud_admin_user_edit', ['id' => $uid]);
@@ -207,7 +208,7 @@ class UserController extends CRUDController
$user->setCurrentLocation($currentLocation); $user->setCurrentLocation($currentLocation);
$this->getDoctrine()->getManager()->flush(); $this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->get('translator')->trans('Current location successfully updated')); $this->addFlash('success', $this->translator->trans('Current location successfully updated'));
return $this->redirect( return $this->redirect(
$request->query->has('returnPath') ? $request->query->get('returnPath') : $request->query->has('returnPath') ? $request->query->get('returnPath') :
@@ -243,7 +244,7 @@ class UserController extends CRUDController
$user->setPassword($this->passwordEncoder->encodePassword($user, $password)); $user->setPassword($this->passwordEncoder->encodePassword($user, $password));
$this->getDoctrine()->getManager()->flush(); $this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->get('translator')->trans('Password successfully updated!')); $this->addFlash('success', $this->translator->trans('Password successfully updated!'));
return $this->redirect( return $this->redirect(
$request->query->has('returnPath') ? $request->query->get('returnPath') : $request->query->has('returnPath') ? $request->query->get('returnPath') :

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

@@ -116,8 +116,12 @@ class Configuration implements ConfigurationInterface
->booleanNode('form_show_centers') ->booleanNode('form_show_centers')
->defaultTrue() ->defaultTrue()
->end() ->end()
->booleanNode('filter_stats_by_center')
->defaultTrue()
->info("if set to false, the exports won't take into account the center of the people")
->end() ->end()
->end() ->end()
->end() // end of 'acl'
->booleanNode('access_global_history') ->booleanNode('access_global_history')
->defaultTrue() ->defaultTrue()
->end() ->end()

View File

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

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export; namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
@@ -54,20 +55,17 @@ class ExportManager
*/ */
private array $formatters = []; private array $formatters = [];
private readonly string|\Stringable|\Symfony\Component\Security\Core\User\UserInterface $user;
public function __construct( public function __construct(
private readonly LoggerInterface $logger, private readonly LoggerInterface $logger,
private readonly AuthorizationCheckerInterface $authorizationChecker, private readonly AuthorizationCheckerInterface $authorizationChecker,
private readonly AuthorizationHelperInterface $authorizationHelper, private readonly AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage, private readonly TokenStorageInterface $tokenStorage,
iterable $exports, iterable $exports,
iterable $aggregators, iterable $aggregators,
iterable $filters iterable $filters
// iterable $formatters, // iterable $formatters,
// iterable $exportElementProvider // iterable $exportElementProvider
) { ) {
$this->user = $tokenStorage->getToken()->getUser();
$this->exports = iterator_to_array($exports); $this->exports = iterator_to_array($exports);
$this->aggregators = iterator_to_array($aggregators); $this->aggregators = iterator_to_array($aggregators);
$this->filters = iterator_to_array($filters); $this->filters = iterator_to_array($filters);
@@ -91,20 +89,24 @@ class ExportManager
* *
* @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias
*/ */
public function &getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): iterable public function getFiltersApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array
{ {
if ($export instanceof DirectExportInterface) { if ($export instanceof DirectExportInterface) {
return; return [];
} }
$filters = [];
foreach ($this->filters as $alias => $filter) { foreach ($this->filters as $alias => $filter) {
if ( if (
\in_array($filter->applyOn(), $export->supportsModifiers(), true) \in_array($filter->applyOn(), $export->supportsModifiers(), true)
&& $this->isGrantedForElement($filter, $export, $centers) && $this->isGrantedForElement($filter, $export, $centers)
) { ) {
yield $alias => $filter; $filters[$alias] = $filter;
} }
} }
return $filters;
} }
/** /**
@@ -112,22 +114,26 @@ class ExportManager
* *
* @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array
* *
* @return iterable<string, AggregatorInterface>|null a \Generator that contains aggretagors. The key is the filter's alias * @return array<string, AggregatorInterface> an array that contains aggregators. The key is the filter's alias
*/ */
public function &getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): ?iterable public function getAggregatorsApplyingOn(DirectExportInterface|ExportInterface $export, array $centers = null): array
{ {
if ($export instanceof ListInterface || $export instanceof DirectExportInterface) { if ($export instanceof ListInterface || $export instanceof DirectExportInterface) {
return; return [];
} }
$aggregators = [];
foreach ($this->aggregators as $alias => $aggregator) { foreach ($this->aggregators as $alias => $aggregator) {
if ( if (
\in_array($aggregator->applyOn(), $export->supportsModifiers(), true) \in_array($aggregator->applyOn(), $export->supportsModifiers(), true)
&& $this->isGrantedForElement($aggregator, $export, $centers) && $this->isGrantedForElement($aggregator, $export, $centers)
) { ) {
yield $alias => $aggregator; $aggregators[$alias] = $aggregator;
} }
} }
return $aggregators;
} }
public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void
@@ -347,6 +353,17 @@ class ExportManager
return $this->filters[$alias]; return $this->filters[$alias];
} }
public function getAllFilters(): array
{
$filters = [];
foreach ($this->filters as $alias => $filter) {
$filters[$alias] = $filter;
}
return $filters;
}
/** /**
* get all filters. * get all filters.
* *
@@ -452,7 +469,7 @@ class ExportManager
if (null === $centers || [] === $centers) { if (null === $centers || [] === $centers) {
// we want to try if at least one center is reachable // we want to try if at least one center is reachable
return [] !== $this->authorizationHelper->getReachableCenters( return [] !== $this->authorizationHelper->getReachableCenters(
$this->user, $this->tokenStorage->getToken()->getUser(),
$role $role
); );
} }
@@ -484,11 +501,17 @@ class ExportManager
{ {
$r = []; $r = [];
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof User) {
return [];
}
foreach ($centers as $center) { foreach ($centers as $center) {
$r[] = [ $r[] = [
'center' => $center, 'center' => $center,
'circles' => $this->authorizationHelper->getReachableScopes( 'circles' => $this->authorizationHelper->getReachableScopes(
$this->user, $user,
$element->requiredRole(), $element->requiredRole(),
$center $center
), ),

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\MainBundle\Export;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class SortExportElement
{
public function __construct(
private TranslatorInterface $translator,
) {}
/**
* @param array<int|string, FilterInterface> $elements
*/
public function sortFilters(array &$elements): void
{
uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle()));
}
/**
* @param array<int|string, AggregatorInterface> $elements
*/
public function sortAggregators(array &$elements): void
{
uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle()));
}
}

View File

@@ -37,9 +37,13 @@ class RollingDateDataMapper implements DataMapperInterface
{ {
$forms = iterator_to_array($forms); $forms = iterator_to_array($forms);
$viewData = new RollingDate( if (null === $forms['roll']->getData()) {
$forms['roll']->getData() ?? RollingDate::T_TODAY, $viewData = null;
$forms['fixedDate']->getData() } else {
); $viewData = new RollingDate(
$forms['roll']->getData(),
$forms['fixedDate']->getData()
);
}
} }
} }

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Export; namespace Chill\MainBundle\Form\Type\Export;
use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\SortExportElement;
use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint; use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
@@ -28,15 +29,7 @@ class ExportType extends AbstractType
final public const PICK_FORMATTER_KEY = 'pick_formatter'; final public const PICK_FORMATTER_KEY = 'pick_formatter';
/** public function __construct(private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement) {}
* @var ExportManager
*/
protected $exportManager;
public function __construct(ExportManager $exportManager)
{
$this->exportManager = $exportManager;
}
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
@@ -58,12 +51,12 @@ class ExportType extends AbstractType
if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { if ($export instanceof \Chill\MainBundle\Export\ExportInterface) {
// add filters // add filters
$filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']);
$this->sortExportElement->sortFilters($filters);
$filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]);
foreach ($filters as $alias => $filter) { foreach ($filters as $alias => $filter) {
$filterBuilder->add($alias, FilterType::class, [ $filterBuilder->add($alias, FilterType::class, [
'filter_alias' => $alias, 'filter' => $filter,
'export_manager' => $this->exportManager,
'label' => $filter->getTitle(), 'label' => $filter->getTitle(),
'constraints' => [ 'constraints' => [
new ExportElementConstraint(['element' => $filter]), new ExportElementConstraint(['element' => $filter]),
@@ -76,6 +69,7 @@ class ExportType extends AbstractType
// add aggregators // add aggregators
$aggregators = $this->exportManager $aggregators = $this->exportManager
->getAggregatorsApplyingOn($export, $options['picked_centers']); ->getAggregatorsApplyingOn($export, $options['picked_centers']);
$this->sortExportElement->sortAggregators($aggregators);
$aggregatorBuilder = $builder->create( $aggregatorBuilder = $builder->create(
self::AGGREGATOR_KEY, self::AGGREGATOR_KEY,
FormType::class, FormType::class,

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\Export; namespace Chill\MainBundle\Form\Type\Export;
use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\FormType;
@@ -25,8 +26,7 @@ class FilterType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$exportManager = $options['export_manager']; $filter = $options['filter'];
$filter = $exportManager->getFilter($options['filter_alias']);
$builder $builder
->add(self::ENABLED_FIELD, CheckboxType::class, [ ->add(self::ENABLED_FIELD, CheckboxType::class, [
@@ -46,8 +46,9 @@ class FilterType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$resolver->setRequired('filter_alias') $resolver
->setRequired('export_manager') ->setRequired('filter')
->setAllowedTypes('filter', [FilterInterface::class])
->setDefault('compound', true) ->setDefault('compound', true)
->setDefault('error_bubbling', false); ->setDefault('error_bubbling', false);
} }

View File

@@ -34,6 +34,7 @@ class PickRollingDateType extends AbstractType
), ),
'multiple' => false, 'multiple' => false,
'expanded' => false, 'expanded' => false,
'required' => $options['required'],
'label' => 'rolling_date.roll_movement', 'label' => 'rolling_date.roll_movement',
]) ])
->add('fixedDate', ChillDateType::class, [ ->add('fixedDate', ChillDateType::class, [
@@ -53,15 +54,22 @@ class PickRollingDateType extends AbstractType
{ {
$resolver->setDefaults([ $resolver->setDefaults([
'class' => RollingDate::class, 'class' => RollingDate::class,
'empty_data' => new RollingDate(RollingDate::T_TODAY), 'empty_data' => null,
'constraints' => [ 'constraints' => [
new Callback($this->validate(...)), new Callback($this->validate(...)),
], ],
'required' => true,
]); ]);
$resolver->setAllowedTypes('required', 'bool');
} }
public function validate($data, ExecutionContextInterface $context, $payload): void public function validate($data, ExecutionContextInterface $context, $payload): void
{ {
if (null === $data) {
return;
}
/** @var RollingDate $data */ /** @var RollingDate $data */
if (RollingDate::T_FIXED_DATE === $data->getRoll() && null === $data->getFixedDate()) { if (RollingDate::T_FIXED_DATE === $data->getRoll() && null === $data->getFixedDate()) {
$context $context

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

View File

@@ -12,14 +12,15 @@ declare(strict_types=1);
namespace Chill\MainBundle\Repository; namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
class UserJobRepository implements UserJobRepositoryInterface readonly class UserJobRepository implements UserJobRepositoryInterface
{ {
private readonly EntityRepository $repository; private EntityRepository $repository;
public function __construct(EntityManagerInterface $em) public function __construct(EntityManagerInterface $em, private TranslatableStringHelperInterface $translatableStringHelper)
{ {
$this->repository = $em->getRepository(UserJob::class); $this->repository = $em->getRepository(UserJob::class);
} }
@@ -42,6 +43,15 @@ class UserJobRepository implements UserJobRepositoryInterface
return $this->repository->findBy(['active' => true]); return $this->repository->findBy(['active' => true]);
} }
public function findAllOrderedByName(): array
{
$jobs = $this->findAll();
usort($jobs, fn (UserJob $a, UserJob $b) => $this->translatableStringHelper->localize($a->getLabel()) <=> $this->translatableStringHelper->localize($b->getLabel()));
return $jobs;
}
/** /**
* @param mixed|null $limit * @param mixed|null $limit
* @param mixed|null $offset * @param mixed|null $offset

View File

@@ -28,6 +28,13 @@ interface UserJobRepositoryInterface extends ObjectRepository
*/ */
public function findAllActive(): array; public function findAllActive(): array;
/**
* a list of UserJob ordered by name.
*
* @return array<UserJob>
*/
public function findAllOrderedByName(): array;
/** /**
* @param mixed|null $limit * @param mixed|null $limit
* @param mixed|null $offset * @param mixed|null $offset

View File

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

View File

@@ -1,5 +1,5 @@
{# {#
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop> <info@champs-libres.coop> / <http://www.champs-libres.coop>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
@@ -28,92 +28,90 @@
{% block js %} {% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }} {{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('page_export') }} {{ encore_entry_script_tags('page_export') }}
{% if export_alias == 'count_social_work_actions' %} {{ encore_entry_script_tags('vue_export_action_goal_result') }}
{{ encore_entry_script_tags('vue_export_action_goal_result') }}
{% endif %}
{{ encore_entry_script_tags('mod_pick_rolling_date') }} {{ encore_entry_script_tags('mod_pick_rolling_date') }}
{% endblock js %} {% endblock js %}
{% block content %} {% block content %}
<div class="col-md-10"> <div class="col-md-10">
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }} {{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
<h1>{{ export.title|trans }}</h1> <h1>{{ export.title|trans }}</h1>
<p>{{ export.description|trans }}</p> <p>{{ export.description|trans }}</p>
{{ form_start(form) }} {{ form_start(form) }}
{% if form.children.export.children.filters is defined %} {% if form.children.export.children.filters is defined %}
{% if form.children.export.children.filters is not empty%} {% if form.children.export.children.filters is not empty%}
<section class="filter mb-4"> <section class="filter mb-4">
<h2>{{ 'Filters'| trans }}</h2> <h2>{{ 'Filters'| trans }}</h2>
{{ form_errors(form.children.export.children.filters) }} {{ form_errors(form.children.export.children.filters) }}
<div class="flex-table"> <div class="flex-table">
{% for filter_form in form.children.export.children.filters %} {% for filter_form in form.children.export.children.filters %}
<div class="item-bloc no-altern"> <div class="item-bloc no-altern">
{{ form_widget(filter_form.enabled, { {{ form_widget(filter_form.enabled, {
'label': filter_form.vars.label, 'label': filter_form.vars.label,
'label_attr': { 'class': 'h6' }, 'label_attr': { 'class': 'h6' },
'attr': { 'data-display-target': filter_form.vars.id } 'attr': { 'data-display-target': filter_form.vars.id }
}) }} }) }}
<div data-display-show-hide="{{ filter_form.vars.id }}"> <div data-display-show-hide="{{ filter_form.vars.id }}">
{{ form_widget(filter_form.form) }} {{ form_widget(filter_form.form) }}
{{ form_errors(filter_form) }} {{ form_errors(filter_form) }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
{% else %} {% else %}
{# render the children, to mark the widget as 'rendered' #} {# render the children, to mark the widget as 'rendered' #}
{{ form_widget(form.children.export.children.filters) }} {{ form_widget(form.children.export.children.filters) }}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if form.children.export.children.aggregators is defined %} {% if form.children.export.children.aggregators is defined %}
{% if form.children.export.children.aggregators is not empty %} {% if form.children.export.children.aggregators is not empty %}
<section class="aggregator mb-4"> <section class="aggregator mb-4">
<h2>{{ 'Aggregators'| trans }}</h2> <h2>{{ 'Aggregators'| trans }}</h2>
<div class="flex-table"> <div class="flex-table">
{% for aggregator_form in form.children.export.children.aggregators %} {% for aggregator_form in form.children.export.children.aggregators %}
<div class="item-bloc no-altern"> <div class="item-bloc no-altern">
{{ form_widget(aggregator_form.enabled, { {{ form_widget(aggregator_form.enabled, {
'label': aggregator_form.vars.label, 'label': aggregator_form.vars.label,
'label_attr': { 'class': 'h6' }, 'label_attr': { 'class': 'h6' },
'attr': { 'data-display-target': aggregator_form.vars.id } 'attr': { 'data-display-target': aggregator_form.vars.id }
}) }} }) }}
<div data-display-show-hide="{{ aggregator_form.vars.id }}"> <div data-display-show-hide="{{ aggregator_form.vars.id }}">
{{ form_widget(aggregator_form.form) }} {{ form_widget(aggregator_form.form) }}
{{ form_errors(aggregator_form) }} {{ form_errors(aggregator_form) }}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</section> </section>
{% else %} {% else %}
{# render the children, to mark the widget as 'rendered' #} {# render the children, to mark the widget as 'rendered' #}
{{ form_widget(form.children.export.children.aggregators) }} {{ form_widget(form.children.export.children.aggregators) }}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if form.children.export.children.export.children|length > 0 %} {% if form.children.export.children.export.children|length > 0 %}
<style> <style>
div#export_export_fields { column-count: 2; } div#export_export_fields { column-count: 2; }
</style> </style>
@@ -123,12 +121,12 @@
</h2> </h2>
{{ form_widget(form.children.export.children.export) }} {{ form_widget(form.children.export.children.export) }}
</section> </section>
{% else %} {% else %}
{# render the children, to mark the widget as 'rendered' #} {# render the children, to mark the widget as 'rendered' #}
{{ form_widget(form.children.export.children.export) }} {{ form_widget(form.children.export.children.export) }}
{% endif %} {% endif %}
{% if form.children.export.children.pick_formatter is defined %} {% if form.children.export.children.pick_formatter is defined %}
<section class="formatter mb-4"> <section class="formatter mb-4">
<h2> <h2>
@@ -140,7 +138,7 @@
{{ form_row(form.children.export.children.pick_formatter.children.alias, { 'label' : 'Formatter' }) }} {{ form_row(form.children.export.children.pick_formatter.children.alias, { 'label' : 'Formatter' }) }}
</section> </section>
{% endif %} {% endif %}
<p>{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' }, 'label' : 'Go to formatter options' } ) }}</p> <p>{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-create' }, 'label' : 'Go to formatter options' } ) }}</p>
{{ form_end(form) }} {{ form_end(form) }}

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(); $user = $this->security->getUser();
if ($user instanceof User) { 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()) { if (null !== $user->getCurrentLocation()) {
$locationTextMenu = $user->getCurrentLocation()->getName(); $locationTextMenu = $user->getCurrentLocation()->getName();
} else { } else {

View File

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

View File

@@ -11,11 +11,15 @@ declare(strict_types=1);
namespace Chill\MainBundle\Test\Export; namespace Chill\MainBundle\Test\Export;
use Chill\MainBundle\Export\DirectExportInterface;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Test\PrepareClientTrait; use Chill\MainBundle\Test\PrepareClientTrait;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NativeQuery; use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/** /**
* This class provide a set of tests for exports. * This class provide a set of tests for exports.
@@ -94,7 +98,7 @@ abstract class AbstractExportTest extends WebTestCase
/** /**
* Create an instance of the report to test. * Create an instance of the report to test.
* *
* @return \Chill\MainBundle\Export\ExportInterface an instance of the export to test * @return ExportInterface|DirectExportInterface|iterable<ExportInterface>|iterable<DirectExportInterface> an instance of the export to test
*/ */
abstract public function getExport(); abstract public function getExport();
@@ -116,19 +120,40 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
abstract public function getModifiersCombination(); abstract public function getModifiersCombination();
protected function getParameters(bool $filterStatsByCenter): ParameterBagInterface
{
return new ParameterBag(['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStatsByCenter]]]);
}
/**
* wrap the results of @see{self::getExports()}, which may be an iterable or an export into an iterble.
*/
private function getExports(): iterable
{
$exports = $this->getExport();
if (is_iterable($exports)) {
return $exports;
}
return [$exports];
}
/** /**
* Test the formatters type are string. * Test the formatters type are string.
*/ */
public function testGetAllowedFormattersType() public function testGetAllowedFormattersType()
{ {
$formattersTypes = $this->getExport()->getAllowedFormattersTypes(); foreach ($this->getExports() as $export) {
$formattersTypes = $export->getAllowedFormattersTypes();
$this->assertContainsOnly( $this->assertContainsOnly(
'string', 'string',
$formattersTypes, $formattersTypes,
true, true,
'Test that the method `getAllowedFormattersTypes` returns an array of string' 'Test that the method `getAllowedFormattersTypes` returns an array of string'
); );
}
} }
/** /**
@@ -136,17 +161,17 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testGetDescription() public function testGetDescription()
{ {
$export = $this->getExport(); foreach ($this->getExports() as $export) {
$this->assertIsString(
$this->assertIsString( $export->getDescription(),
$export->getDescription(), 'Assert that the `getDescription` method return a string'
'Assert that the `getDescription` method return a string' );
); $this->assertNotEmpty(
$this->assertNotEmpty( $export->getDescription(),
$export->getDescription(), 'Assert that the `getDescription` method does not return an empty '
'Assert that the `getDescription` method does not return an empty ' .'string.'
.'string.' );
); }
} }
/** /**
@@ -156,19 +181,21 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testGetQueryKeys(array $data) public function testGetQueryKeys(array $data)
{ {
$queryKeys = $this->getExport()->getQueryKeys($data); foreach ($this->getExports() as $export) {
$queryKeys = $export->getQueryKeys($data);
$this->assertContainsOnly( $this->assertContainsOnly(
'string', 'string',
$queryKeys, $queryKeys,
true, true,
'test that the query keys returned by `getQueryKeys` are only strings' 'test that the query keys returned by `getQueryKeys` are only strings'
); );
$this->assertGreaterThanOrEqual( $this->assertGreaterThanOrEqual(
1, 1,
\count($queryKeys), \count($queryKeys),
'test that there are at least one query key returned' 'test that there are at least one query key returned'
); );
}
} }
/** /**
@@ -187,61 +214,70 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testGetResultsAndLabels($modifiers, $acl, array $data) public function testGetResultsAndLabels($modifiers, $acl, array $data)
{ {
// it is more convenient to group the `getResult` and `getLabels` test foreach ($this->getExports() as $export) {
// due to the fact that testing both methods use the same tools. // it is more convenient to group the `getResult` and `getLabels` test
// due to the fact that testing both methods use the same tools.
$queryKeys = $this->getExport()->getQueryKeys($data); $queryKeys = $export->getQueryKeys($data);
$query = $this->getExport()->initiateQuery($modifiers, $acl, $data); $query = $export->initiateQuery($modifiers, $acl, $data);
// limit the result for the query for performance reason (only for QueryBuilder, // limit the result for the query for performance reason (only for QueryBuilder,
// not possible in NativeQuery) // not possible in NativeQuery)
if ($query instanceof QueryBuilder) { if ($query instanceof QueryBuilder) {
$query->setMaxResults(1); $query->setMaxResults(1);
} }
$results = $this->getExport()->getResult($query, $data); $results = $export->getResult($query, $data);
$this->assertIsArray( $this->assertIsArray(
$results, $results,
'assert that the returned result is an array' 'assert that the returned result is an array'
);
if (0 === \count($results)) {
$this->markTestIncomplete('The result is empty. We cannot process tests '
.'on results');
}
// testing the result
$result = $results[0];
$this->assertTrue(
is_iterable($result),
'test that each row in the result is traversable or an array'
);
foreach ($result as $key => $value) {
$this->assertContains(
$key,
$queryKeys,
'test that each key is present in `getQueryKeys`'
); );
$closure = $this->getExport()->getLabels($key, [$value], $data); if (0 === \count($results)) {
$this->markTestIncomplete('The result is empty. We cannot process tests '
.'on results');
}
// testing the result
$result = $results[0];
$this->assertTrue( $this->assertTrue(
\is_callable($closure, false), is_iterable($result),
'test that the `getLabels` for key is a callable' 'test that each row in the result is traversable or an array'
); );
$this->assertTrue( $i = 0;
// conditions foreach ($result as $key => $value) {
\is_string((string) \call_user_func($closure, '_header')) $this->assertContains(
&& !empty(\call_user_func($closure, '_header')) $key,
&& '_header' !== \call_user_func($closure, '_header'), $queryKeys,
// message 'test that each key is present in `getQueryKeys`'
sprintf('Test that the callable return by `getLabels` for key %s ' );
.'can provide an header', $key)
); $closure = $export->getLabels($key, [$value], $data);
$this->assertTrue(
\is_callable($closure, false),
'test that the `getLabels` for key is a callable'
);
$this->assertTrue(
// conditions
\is_string((string) \call_user_func($closure, '_header'))
&& !empty(\call_user_func($closure, '_header'))
&& '_header' !== \call_user_func($closure, '_header'),
// message
sprintf('Test that the callable return by `getLabels` for key %s '
.'can provide an header', $key)
);
++$i;
if ($i > 15) {
// do not iterate on each result
break;
}
}
} }
} }
@@ -250,14 +286,14 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testGetType() public function testGetType()
{ {
$export = $this->getExport(); foreach ($this->getExports() as $export) {
$this->assertIsString(
$this->assertIsString( $export->getType(),
$export->getType(), 'Assert that the `getType` method return a string'
'Assert that the `getType` method return a string' );
); $this->assertNotEmpty($export->getType(), 'Assert that the `getType` method'
$this->assertNotEmpty($export->getType(), 'Assert that the `getType` method' .' does not return an empty string.');
.' does not return an empty string.'); }
} }
/** /**
@@ -272,34 +308,36 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testInitiateQuery(mixed $modifiers, mixed $acl, mixed $data) public function testInitiateQuery(mixed $modifiers, mixed $acl, mixed $data)
{ {
$query = $this->getExport()->initiateQuery($modifiers, $acl, $data); foreach ($this->getExports() as $export) {
$query = $export->initiateQuery($modifiers, $acl, $data);
$this->assertTrue( $this->assertTrue(
$query instanceof QueryBuilder || $query instanceof NativeQuery, $query instanceof QueryBuilder || $query instanceof NativeQuery,
sprintf( sprintf(
'Assert that the returned query is an instance of %s or %s', 'Assert that the returned query is an instance of %s or %s',
QueryBuilder::class, QueryBuilder::class,
Query::class NativeQuery::class
) )
);
if ($query instanceof QueryBuilder) {
$this->assertGreaterThanOrEqual(
1,
\count($query->getDQLPart('select')),
"assert there is at least one 'select' part"
); );
$this->assertGreaterThanOrEqual( if ($query instanceof QueryBuilder) {
1, $this->assertGreaterThanOrEqual(
\count($query->getDQLPart('from')), 1,
"assert there is at least one 'from' part" \count($query->getDQLPart('select')),
); "assert there is at least one 'select' part"
} elseif ($query instanceof NativeQuery) { );
$this->assertNotEmpty(
$query->getSQL(), $this->assertGreaterThanOrEqual(
'check that the SQL query is not empty' 1,
); \count($query->getDQLPart('from')),
"assert there is at least one 'from' part"
);
} elseif ($query instanceof NativeQuery) {
$this->assertNotEmpty(
$query->getSQL(),
'check that the SQL query is not empty'
);
}
} }
} }
@@ -308,9 +346,11 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testRequiredRole() public function testRequiredRole()
{ {
$role = $this->getExport()->requiredRole(); foreach ($this->getExports() as $export) {
$role = $export->requiredRole();
self::assertIsString($role); self::assertIsString($role);
}
} }
/** /**
@@ -323,22 +363,23 @@ abstract class AbstractExportTest extends WebTestCase
*/ */
public function testSupportsModifier(mixed $modifiers, mixed $acl, mixed $data) public function testSupportsModifier(mixed $modifiers, mixed $acl, mixed $data)
{ {
$export = $this->getExport(); foreach ($this->getExports() as $export) {
$query = $export->initiateQuery($modifiers, $acl, $data); $query = $export->initiateQuery($modifiers, $acl, $data);
if ($query instanceof QueryBuilder) { if ($query instanceof QueryBuilder) {
$this->assertContainsOnly( $this->assertContainsOnly(
'string', 'string',
$export->supportsModifiers(), $export->supportsModifiers(),
true, true,
'Test that the `supportsModifiers` method returns only strings' 'Test that the `supportsModifiers` method returns only strings'
); );
} elseif ($query instanceof NativeQuery) { } elseif ($query instanceof NativeQuery) {
$this->assertTrue( $this->assertTrue(
null === $export->supportsModifiers() null === $export->supportsModifiers()
|| 0 === \count($export->supportsModifiers()), || 0 === \count($export->supportsModifiers()),
'Test that the `supportsModifier` methods returns null or an empty array' 'Test that the `supportsModifier` methods returns null or an empty array'
); );
}
} }
} }
} }

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,193 @@
<?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\Export;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\SortExportElement;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @internal
*
* @coversNothing
*/
class SortExportElementTest extends KernelTestCase
{
private SortExportElement $sortExportElement;
public function setUp(): void
{
parent::setUpBeforeClass();
$this->sortExportElement = new SortExportElement($this->makeTranslator());
}
public function testSortFilterRealData(): void
{
self::bootKernel();
$sorter = self::$container->get(SortExportElement::class);
$translator = self::$container->get(TranslatorInterface::class);
$exportManager = self::$container->get(ExportManager::class);
$filters = $exportManager->getAllFilters();
$sorter->sortFilters($filters);
$previousName = null;
foreach ($filters as $filter) {
if (null === $previousName) {
$previousName = $translator->trans($filter->getTitle());
continue;
}
$current = $translator->trans($filter->getTitle());
if ($current === $previousName) {
continue;
}
self::assertEquals(-1, $previousName <=> $current, sprintf("comparing '%s' and '%s'", $previousName, $current));
$previousName = $current;
}
}
public function testSortAggregator(): void
{
$aggregators = [
'foo' => $a = $this->makeAggregator('a'),
'zop' => $q = $this->makeAggregator('q'),
'bar' => $c = $this->makeAggregator('c'),
'baz' => $b = $this->makeAggregator('b'),
];
$this->sortExportElement->sortAggregators($aggregators);
self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($aggregators));
self::assertSame($a, $aggregators['foo']);
self::assertSame($b, $aggregators['baz']);
self::assertSame($c, $aggregators['bar']);
self::assertSame($q, $aggregators['zop']);
}
public function testSortFilter(): void
{
$filters = [
'foo' => $a = $this->makeFilter('a'),
'zop' => $q = $this->makeFilter('q'),
'bar' => $c = $this->makeFilter('c'),
'baz' => $b = $this->makeFilter('b'),
];
$this->sortExportElement->sortFilters($filters);
self::assertEquals(['foo', 'baz', 'bar', 'zop'], array_keys($filters));
self::assertSame($a, $filters['foo']);
self::assertSame($b, $filters['baz']);
self::assertSame($c, $filters['bar']);
self::assertSame($q, $filters['zop']);
}
private function makeTranslator(): TranslatorInterface
{
return new class () implements TranslatorInterface {
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
{
return $id;
}
public function getLocale(): string
{
return 'en';
}
};
}
private function makeAggregator(string $title): AggregatorInterface
{
return new class ($title) implements AggregatorInterface {
public function __construct(private readonly string $title) {}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return fn ($v) => $v;
}
public function getQueryKeys($data)
{
return [];
}
public function getTitle()
{
return $this->title;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data) {}
public function applyOn()
{
return [];
}
};
}
private function makeFilter(string $title): FilterInterface
{
return new class ($title) implements FilterInterface {
public function __construct(private readonly string $title) {}
public function getTitle()
{
return $this->title;
}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string')
{
return ['a', []];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data) {}
public function applyOn()
{
return [];
}
};
}
}

Some files were not shown because too many files have changed in this diff Show More