Compare commits

...

117 Commits

Author SHA1 Message Date
13abc36529 Merge branch 'rector/rules-up-to-php82-2023-08' into rector/rules-up-to-php82 2023-07-19 23:21:20 +02:00
023a29cb78 apply rector rules: php up to php82 2023-07-19 23:19:50 +02:00
7b637d1287 change rector config: up to php82 2023-07-19 23:15:42 +02:00
74ed34ba97 apply rules rector 2023-07-19 22:48:26 +02:00
6e6f19c499 fix small risky code 2023-07-19 16:28:51 +02:00
075aca493b DX: type-hing oneToMany and ManyToMany properties as collection 2023-07-19 16:21:17 +02:00
224c2c74e8 Merge remote-tracking branch 'origin/master' into rector/rules-up-to-php80
Conflicts:
	src/Bundle/ChillActivityBundle/Controller/ActivityController.php
	src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php
	src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php
	src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php
	src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php
	src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php
	src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php
	src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php
	src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
	src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php
	src/Bundle/ChillEventBundle/Search/EventSearch.php
	src/Bundle/ChillMainBundle/Controller/ExportController.php
	src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php
	src/Bundle/ChillMainBundle/Cron/CronManager.php
	src/Bundle/ChillMainBundle/Entity/CronJobExecution.php
	src/Bundle/ChillMainBundle/Export/ExportManager.php
	src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php
	src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php
	src/Bundle/ChillMainBundle/Repository/NotificationRepository.php
	src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php
	src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php
	src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php
	src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php
	src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php
	src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php
	src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php
	src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php
	src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php
	src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php
	src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php
	src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkEvaluationContext.php
	src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php
	src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php
	src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
2023-07-17 12:49:13 +02:00
1ebf68683f release 2.5.2 2023-07-15 09:00:03 +02:00
98c2ee3ab2 [Collate Address] when updating address point, do not use the point's address reference if the similarity is below 2023-07-15 08:59:07 +02:00
4d7b4500c1 release 2.5.1 2023-07-14 13:03:29 +02:00
ca2ba9d8e4 [collate addresses] block collating addresses to another address reference
where the address reference is already the best match
2023-07-14 13:03:28 +02:00
f34f8b1eaa Update v2.5.0.md - add missing changie 2023-07-14 10:42:11 +00:00
2b96160200 Release v2.5.0 2023-07-14 11:42:35 +02:00
933c353357 DX: [changie] fix bug when Custom.Long is not present 2023-07-14 11:32:10 +02:00
e6da727a11 Merge branch 'issue120_filter_social_actions' into 'master'
Add filter to social actions

See merge request Chill-Projet/chill-bundles!570
2023-07-14 09:14:21 +00:00
0719a541a6 Merge branch 'doc-chill-database' into 'master'
doc: documentation for database printciples

See merge request Chill-Projet/chill-bundles!562
2023-07-14 08:58:11 +00:00
883147ed9c add a missing sntence 2023-07-14 08:57:55 +00:00
718de2fad0 Merge branch '112-addresses-recollate' into 'master'
Feature: re-associate addresses with addresses references and postal code references in a cronjob and allow a cronjob to pass data from one execution to the next one

Closes #112

See merge request Chill-Projet/chill-bundles!580
2023-07-14 08:56:56 +00:00
643d8f99be collate address: force the country to be the same when comparing postal codes 2023-07-14 10:52:33 +02:00
3954d69c94 Merge branch '93-improve-address-design' into 'master'
Améliorations cosmétiques en rapport avec le bouton "détails de l'adresse"

See merge request Chill-Projet/chill-bundles!579
2023-07-14 08:47:18 +00:00
78a5e81e33 remove glitch from filter when no task states in database 2023-07-14 10:43:18 +02:00
b05e128b5e Handle case when some arguments are null in SingleTaskAclAwareRepository 2023-07-14 10:13:57 +02:00
8f39d0320c Merge branch '117-repair-queries-on-homepage-widget' into 'master'
[homepage widget] repair my unread notification list with actions and evaluations documents

See merge request Chill-Projet/chill-bundles!578
2023-07-14 07:48:46 +00:00
1cc61cee36 Fixed: fix CS + fix "my workflow" api when course or work has been delete 2023-07-14 09:44:05 +02:00
a21cefab31 Fix: last fixes for accompanying period work filters
- avoid errors when the user inverse date from and date to (we correct them)
- allow the user to filter by multiple users
- do not show filter by types if only one action type
- more type-hinting in the $filter argument for AccompanyingPeriodWorkRepository::findByAccompanyingPeriodOpenFirst
2023-07-14 09:23:23 +02:00
f2673d6c83 Merge remote-tracking branch 'origin/master' into issue120_filter_social_actions 2023-07-13 21:14:31 +02:00
18535ee85f DX: remove dump message 2023-07-13 21:12:08 +02:00
aea6796ba1 Merge branch 'user_filter_tasks' into 'master'
Filter on user within task list

See merge request Chill-Projet/chill-bundles!569
2023-07-13 16:07:54 +00:00
872d5e8ebf do not show filter by user on 'my tasks' page, and show different states
dynamically
2023-07-13 18:02:54 +02:00
c0901947ca [filterOrder] show selected picked users in the results boxes 2023-07-13 17:36:05 +02:00
ea0e5dfa14 Merge remote-tracking branch 'origin/master' into user_filter_tasks 2023-07-13 16:13:14 +02:00
59da93fd75 corrections 2023-07-13 14:07:01 +02:00
1d9b8729ab oups 2023-07-13 12:50:15 +02:00
22ded77bde Create new route to redirect from document to work that contains evaluation that contains document 2023-07-13 12:49:01 +02:00
995e2dac80 Merge branch '126-addperson-thirdparty-api-search' into 'master'
[AddPersons] redondance dans les résultats de recherche de tiers

See merge request Chill-Projet/chill-bundles!576
2023-07-13 08:37:32 +00:00
28c41aaf85 fix the number of parameters in the query 2023-07-13 10:32:56 +02:00
ce2ce42530 add changies 2023-07-13 10:32:51 +02:00
1409a3b23a update changelog 2023-07-13 10:27:29 +02:00
51e382760d add changie 2023-07-13 10:05:57 +02:00
b0fcffea2d fixed: take into account the first iteration of the cronjob 2023-07-13 09:03:41 +02:00
66e1047752 DX php cs fixer 2023-07-12 20:30:31 +02:00
7bee376718 DX phpstan fix and remove dump 2023-07-12 20:29:53 +02:00
6bc45bbca3 FEATURE [filter][actions] integrate user and dates filter into sql 2023-07-12 20:21:14 +02:00
f3912e5544 Merge branch '129-export-filter-course-having-activity' into 'master'
[export] Add a filter "filter course having an activity between two dates"

Closes #129

See merge request Chill-Projet/chill-bundles!574
2023-07-12 16:20:18 +00:00
0950074121 Merge branch '128-export-group-activity-by-localisation' into 'master'
Feature: [export] allow to group activities by location

Closes #128

See merge request Chill-Projet/chill-bundles!573
2023-07-12 16:11:51 +00:00
94e9b75e40 Merge branch '125-list-person-on-course' into 'master'
Feature: Add a list of peoples with the details of their accompanying courses

Closes #125

See merge request Chill-Projet/chill-bundles!572
2023-07-12 16:11:39 +00:00
23c7a92546 Merge branch '118-design-filterOrder' into 'master'
improve style of filterOrder box

See merge request Chill-Projet/chill-bundles!568
2023-07-12 16:11:22 +00:00
8391dbe448 Merge branch 'issue719_filter_activities_version_2' into 'master'
Filter the list of activities

See merge request Chill-Projet/chill-bundles!563
2023-07-12 16:11:10 +00:00
a7842b2597 [Addresses] add a cronjob to collate addresses with reference 2023-07-12 18:00:29 +02:00
1552b3c9d7 [Addresses] create a service to collate addresses with the address reference 2023-07-12 17:30:46 +02:00
2259a31260 show detail button in banners 2023-07-12 17:25:49 +02:00
36eed4323c add title tag on detail address button 2023-07-12 17:20:02 +02:00
9bf8c9b0ed UX: [address details] improve button integration 2023-07-12 17:03:55 +02:00
1930c48d28 Align address detail button style with banner context buttons 2023-07-12 17:03:55 +02:00
29306d2b66 UX: [vue][onTheFly] improve residential address position in modale 2023-07-12 17:03:55 +02:00
e0758215ba FEATURE [repository] implement filter logic 2023-07-12 15:17:03 +02:00
e82c7cdc6c Fixed: [homepage widget] repair my unread notification list with actions and evaluations documents 2023-07-12 13:24:23 +02:00
3f66e1a862 [cron-job] allow a cronjob to pass data from one execution to another
When a cronjob is executed, it may return an array of data.

This data will be passed as parameter on the next execution
2023-07-12 11:36:26 +02:00
efee2d8b44 cleaning 2023-07-12 10:53:12 +02:00
f3829d3390 adapt query to simplify join clauses (lightly improve perfs) 2023-07-12 10:50:17 +02:00
a2e705bd92 fixed: error with parent joins in thirdparty api search query 2023-07-12 10:38:11 +02:00
e38b369149 [cron-job] add a new "lastExecution" data on CronJobExecution entity
This column will store the results of the last execution
2023-07-12 10:26:53 +02:00
5a36a8660d Merge branch 'AMLI_fix_budget_calculator' into 'master'
AMLIi fix budget calculator

See merge request Chill-Projet/chill-bundles!575
2023-07-12 08:20:03 +00:00
f7d385eba1 DX add changie 2023-07-12 09:06:08 +02:00
edd66f6a6c FIX [budget][templates] reimplement display of all calculator results 2023-07-12 09:04:15 +02:00
ef1eb2031e Merge branch 'master' into user_filter_tasks 2023-07-12 07:53:21 +02:00
6065680e1e Feature: [export] allow to group activities by location 2023-07-11 15:01:32 +02:00
88114e3ba6 Fixed: [filterOrder] refactor active filter helper to a dedicated class and fix loading of multiple entity choices 2023-07-11 14:17:02 +02:00
bf93c1ddb2 fix label color in active filters pills 2023-07-11 14:06:10 +02:00
0d365e16e5 add missing translations 2023-07-10 15:59:33 +02:00
802ff20b5c Merge branch '118-design-filterOrder' of gitlab.com:Chill-Projet/chill-bundles into 118-design-filterOrder 2023-07-10 15:55:18 +02:00
cdfe201574 render active filters like pills 2023-07-10 15:55:05 +02:00
43419f9f15 [filterOrder] fix error in method getActiveFilters when dealing with entityChoice with incorrect number of translation 2023-07-10 15:39:00 +02:00
39896ea6e2 [FilterOrder] add a method to get all the active filters 2023-07-10 15:26:54 +02:00
ca62c3fd0b Merge remote-tracking branch 'origin/master' into 118-design-filterOrder 2023-07-10 11:51:39 +02:00
b3b84c5dc0 Merge branch 'issue719_filter_activities_version_2' into 118-design-filterOrder 2023-07-10 11:49:24 +02:00
6bdb3e9695 fix typo which prevent to apply a filter on activity types 2023-07-07 21:49:36 +02:00
20e64e8768 test filterOrder in an accordion 2023-07-07 15:41:29 +02:00
63f9bd5548 [export] Add ordering by person''s lastname or course opening date in list which concerns accompanying course or people 2023-07-07 12:42:32 +02:00
c8146ded17 Feature: add a list for people with their associated accompanying course 2023-07-07 12:36:24 +02:00
17d2b795b4 update changelog 2023-07-07 11:38:00 +02:00
7f30742fc3 Rename ListPersonWithAccompanyingPeriod to ListPersonHavingAccompanyingPeriod 2023-07-07 09:36:39 +02:00
56d9072abe change id, to avoid collision between ListPersonHelper and ListAccompanyingPeriodHelper 2023-07-07 09:33:03 +02:00
7ccff61c25 Refactor ListAccompanyingPeriod to use a helper for most of the work 2023-07-07 09:17:36 +02:00
cc97199c5d DX added changie 2023-07-06 13:40:25 +02:00
20d5fabc18 [repository][action filter] integrating filters in repository 2023-07-06 13:39:08 +02:00
c04fd66163 do not show filter on job or activity type if less than 2 possibilities 2023-07-05 22:20:27 +02:00
61982634a6 FEATURE add filter to the template 2023-07-05 16:05:51 +02:00
6c58e7eb3e DX phpstan and cs-fixer 2023-07-05 15:50:08 +02:00
4b25970ce0 FEATURE [filter] start implementation of social action filter 2023-07-05 15:35:13 +02:00
52d51264ba FIX [query][user filter] avoid replacement of user parameter in query 2023-07-05 14:57:28 +02:00
4e934653be DX changie added 2023-07-05 14:12:43 +02:00
1ee0e8e350 DX phpstan and csfixer 2023-07-05 13:35:06 +02:00
4da7040a49 FEATURE [user filter] implement query. Selecting multiple users doesn't work 2023-07-05 12:38:42 +02:00
a34b5f8588 Merge remote-tracking branch 'origin/118-design-filterOrder' into user_filter_tasks 2023-07-05 10:56:12 +02:00
0d626fb345 [FEATURE] implement user filter in orderFilterHelper for tasks 2023-07-05 10:55:30 +02:00
25d4b6acbb [FEATURE] allow adding of user filters in filter order - template still to be done 2023-07-05 10:54:37 +02:00
145c1df313 cleaning 2023-07-05 09:43:13 +02:00
7f9738975c UX: improve FilterOrder box design 2023-07-04 17:53:08 +02:00
3e63b4abf3 UX: improve FilterOrder box design 2023-07-04 16:42:56 +02:00
1485d1ce7a Merge branch 'master' into 118-design-filterOrder 2023-07-04 14:56:00 +02:00
0e5f1b4ab9 Feature: [activity list] add pagination 2023-06-23 12:44:54 +02:00
f7c11d3567 Feature: Add filters on activity list 2023-06-23 12:27:18 +02:00
51544cfc48 DX: improve typing of a property in UserJob 2023-06-23 12:24:40 +02:00
659dff3d2c DX: Add features to filterOrder
Allow to add single checkboxes and entitychoices to filter order
2023-06-23 12:24:32 +02:00
ad72192e24 doc: for database printciples 2023-06-08 11:24:27 +02:00
6d63177ff4 apply rules rector up to php82 2023-05-01 21:39:45 +02:00
81e8928344 DX: configure rector up to php82 2023-05-01 21:39:27 +02:00
737f5f9275 fixes 2023-05-01 21:22:56 +02:00
07c681fcec DX: update rector config 2023-04-28 23:19:05 +02:00
1c7d90a6ef fixes from a first test 2023-04-28 23:17:41 +02:00
24c33b306b Fix: urgent fix for EntityToJsonTransformer
The throw on error flag imposes us to propose a valid json string for decoding
2023-04-28 23:16:13 +02:00
7e19419861 fixes for last rebase and fixes for runtime 2023-04-28 23:03:36 +02:00
c35994203d fix phpstan issues 2023-04-28 22:55:01 +02:00
9027cbd196 DX: rector config: up to PHP8 [ci-skip][wip] 2023-04-28 22:30:35 +02:00
dde3002100 DX: apply rector rules up to php8.0 2023-04-28 22:30:33 +02:00
d8870e906f DX: rector in docs directory 2023-04-28 22:27:50 +02:00
951 changed files with 6315 additions and 10722 deletions

View File

@@ -1,5 +0,0 @@
kind: Feature
body: '[export] Add a filter "filter course having an activity between two dates"'
time: 2023-07-11T15:59:29.065329834+02:00
custom:
Issue: "129"

39
.changes/v2.5.0.md Normal file
View File

@@ -0,0 +1,39 @@
## v2.5.0 - 2023-07-14
### Feature
* Allow filtering on the basis of a user within general tasks lists
* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions.
* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course
* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples
* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation
* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates"
* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours
* Improve filtering layout
### Fixed
* reimplement the visualization of all calculator results
* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents
* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind
### DX
* Documentation for database principles
* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution
### UX
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners
* Improve residential address position on show onthefly modale
### Traduction francophone des principaux changements
* Ajout d'un filtre "par utilisateur" aux pages de tâche
* Filtre des actions d'accompagnement par date, type, intervenant
* export: liste des usagers concernés avec détail de leurs parcours
* export: ajout d'un regroupement des échanges par localisation
* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates"
* ajout d'une tâche cron pour associer les adresses à une adresse de référence
* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation
* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants
* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position
* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage"
* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée
* améliore le design et l'expérience utilisateur des filtres

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

@@ -0,0 +1,3 @@
## v2.5.1 - 2023-07-14
### Fixed
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match

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

@@ -0,0 +1,3 @@
## v2.5.2 - 2023-07-15
### Fixed
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)

View File

@@ -7,7 +7,7 @@ versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}' kindFormat: '### {{.Kind}}'
# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description. # Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
changeFormat: >- changeFormat: >-
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if not (eq .Custom.Long "") }} * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if and (.Custom.Long) (not (eq .Custom.Long "")) }}
{{ .Custom.Long }}{{ end }} {{ .Custom.Long }}{{ end }}
custom: custom:
@@ -30,6 +30,8 @@ kinds:
auto: patch auto: patch
- label: DX - label: DX
auto: patch auto: patch
- label: UX
auto: patch
newlines: newlines:
afterChangelogHeader: 1 afterChangelogHeader: 1
beforeChangelogVersion: 1 beforeChangelogVersion: 1

View File

@@ -6,6 +6,54 @@ 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.5.2 - 2023-07-15
### Fixed
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)
## v2.5.1 - 2023-07-14
### Fixed
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match
## v2.5.0 - 2023-07-14
### Feature
* Allow filtering on the basis of a user within general tasks lists
* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions.
* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course
* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples
* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation
* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates"
* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours
* Improve filtering layout
### Fixed
* reimplement the visualization of all calculator results
* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents
* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind
### DX
* Documentation for database principles
* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution
### UX
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners
* Improve residential address position on show onthefly modale
### Traduction francophone des principaux changements
* Ajout d'un filtre "par utilisateur" aux pages de tâche
* Filtre des actions d'accompagnement par date, type, intervenant
* export: liste des usagers concernés avec détail de leurs parcours
* export: ajout d'un regroupement des échanges par localisation
* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates"
* ajout d'une tâche cron pour associer les adresses à une adresse de référence
* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation
* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants
* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position
* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage"
* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée
* améliore le design et l'expérience utilisateur des filtres
## v2.4.0 - 2023-07-07 ## v2.4.0 - 2023-07-07
### Feature ### Feature

View File

@@ -54,18 +54,9 @@ class CountPerson implements ExportInterface
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)
{ {
// the Closure which will be executed by the formatter. // the Closure which will be executed by the formatter.
return function ($value) { return fn($value) => match ($value) {
switch ($value) { '_header' => $this->getTitle(),
case '_header': default => $value,
// we have to process specifically the '_header' string,
// which will be used by the formatter to show a column title
return $this->getTitle();
default:
// for all value, we do not process them and return them
// immediatly
return $value;
}
}; };
} }

View File

@@ -0,0 +1,84 @@
.. database-principles:
Principes de la base de données
###############################
Cette page donne une compréhension globale de la base de donnée de Chill, et explique quelques détails d'implémentations qui permettent d'accélérer les traitements à partir de la base de donnée, ou de l'exploiter plus aisément.
Cette page est rédigée en français.
.. note::
La stabilité du schéma de la base de donnée n'est pas garantie.
Toutefois, ce dernier évolue relativement peu. Il est rare que des tables ou des colonnes soient supprimées ou renommées. Mais il n'est pas garanti que cela puisse arriver.
Généralités
===========
Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`.
Schéma et conventions de nommage
--------------------------------
Au début de l'histoire de Chill, les schémas postgresql n'étaient pas exploités. Les données étaient stockées dans le schéma :code:`public`.
Par la suite, des nouveaux bundles sont apparus, et les tables ont été classées dans des schémas dédiés.
A l'heure actuelle:
- pour les anciens bundle, ceux qui ont déjà des tables dans le schéma public, les nouvelles tables sont ajoutées à ce schéma. Elles sont préfixées par :code:`chill_<nom du bundle>_`;
- pour les bundles plus récents, les tables sont créées dans le schéma dédié
Données avec de l'historicité
-----------------------------
Certaines données sont historisées:
- les référents d'un parcours;
- les statuts d'un parcours;
- la liaison entre les centres et les usagers;
- etc.
Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable.
Dans certains cas, la donnée actuelle (référent d'un parcours, par exemple) est également répétée au niveau de la table en elle-même. Par exemple, la table des parcours :code:`chill_person_accompanying_period` comporte une colonne :code:`step` (le statut du parcours) et :code:`user_id` (id du référent) en plus de l'historique. Bien que redondant, cela simplifie les traitements.
Relations particulières
=======================
Usagers, ménages, adresses
--------------------------
Les usagers ont une adresse au travers des ménages: dans l'interface, l'adresse est inscrite dans le dossier du ménage, et elle est "donnée" aux usagers membres du ménage, **et** qui partagent l'adresse de ce ménage. En effet, il est possible que des usagers "appartiennent" à un ménage sans y être domicilié: c'est le cas, par exemple, des enfants en garde alternée.
L'historique de l'appartenance des usagers au ménage est conservée, de même que l'historique des adresses pour un même ménage.
Les tables en jeu sont les suivantes:
- la table :code:`chill_person_person` liste les usagers;
- la table :code:`chill_person_household_members` liste les appartenances au ménage: il s'agit de la jointure entre les usagers et les ménages:
- les colonnes :code:`startDate` et :code:`endDate` indiquent la date de début et la date de fin de l'appartenance;
- la colonne :code:`shareHousehold` indique si l'utilisateur partage l'adresse du ménage (si oui, sa valeur est :code:`TRUE`)
- la table :code:`chill_person_household` liste les ménages
- la table :code:`chill_person_household_to_addresses` associe les ménages aux adresses;
- la table :code:`chill_main_address` contient les adresses, en indiquant la date de début de validité (:code:`validFrom`) et la fin de validité (:code:`validTo`).
Pour simplifier la résolution des adresses et des usagers, deux vues ont été mises en œuvre:
- la vue :code:`view_chill_person_household_address` reprend, pour chaque usager, l'historique des appartenances au ménage découpée par l'historique des adresses d'un ménage.
Autrement dit, une ligne est créée à chaque fois qu'un usager change de ménage, ou qu'un ménage change d'adresse. Il est donc possible de retrouver l'historique complet des adresses pour un usager donné via cette table.
- la vue :code:`view_chill_person_current_address` reprend l'adresse actuelle des usagers.
Adresses et unités géographiques
--------------------------------
Chill propose des statistiques sur la localisation des adresses par rapport à des zones géographiques (:code:`chill_main_geographical_unit`).
Comme la résolution géographique des adresses est coûteuse en CPU et en temps de traitement, une vue matérialisée a été créée: :code:`view_chill_main_address_geographical_unit`. Elle est rafraichie quotidiennement dans la base de donnée de production.
Liste des tables et commentaires
================================
Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`.

View File

@@ -0,0 +1,155 @@
order,table_schema,table_name,commentaire
1,chill_3party,party_category,Catégorie de tiers
2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
3,chill_3party,party_profession,Profession du tiers (déprécié)
4,chill_3party,third_party,Tiers
5,chill_3party,thirdparty_category,association tiers - catégories
6,chill_asideactivity,asideactivity,Activités annexes
7,chill_asideactivity,asideactivitycategory,Catégories d'activités annexes
8,chill_budget,charge,Charges du budget
9,chill_budget,charge_type,Types de charges
10,chill_budget,resource,Ressources du budget
11,chill_budget,resource_type,Types de ressources
12,chill_calendar,calendar,Rendez-vous
13,chill_calendar,calendar_doc,Document du rendez-vous
14,chill_calendar,calendar_range,Plage de disponibilité
15,chill_calendar,calendar_to_persons,association rendez-vous - usagers
16,chill_calendar,calendar_to_thirdparties,association rendez-vous - tiers
17,chill_calendar,cancel_reason,Motifs d'annulations
18,chill_calendar,invite,Invitation aux rendez-vous
19,chill_doc,accompanyingcourse_document,Documents associés aux parcours
20,chill_doc,document_category,Catégories de documents
21,chill_doc,person_document,Documents associés à l'usagers
22,chill_doc,stored_object,Documents
23,chill_task,recurring_task,Tâches récurrentes (non utilisé)
24,chill_task,single_task,Tâches
25,chill_task,single_task_place_event,Historique des transitions des tâches
26,chill_vendee,adressederelais,
27,chill_vendee,center_polygon
28,chill_vendee,entourage,
29,chill_vendee,geographical_unit
30,chill_vendee,geographical_unit_association
31,chill_vendee,mobilite
32,chill_vendee,niveauetude
33,chill_vendee,security_profile
34,chill_vendee,security_profile_action
35,chill_vendee,security_profile_jobs
36,chill_vendee,situationprofessionelle
37,chill_vendee,statutlogement
38,chill_vendee,tempsdetravail
39,chill_vendee,titredesejour
40,chill_vendee,vendee_person
41,chill_vendee,vendee_person_mineur
42,chill_vendee,vendeeperson_entourage
43,chill_vendee,vendeepersonmineur_adressederelais
44,public,accompanying_periods_scopes,Services associés aux parcours
45,public,activity,Échanges
46,public,activity_activityreason,s
47,public,activity_person,
48,public,activity_storedobject,
49,public,activity_thirdparty,
50,public,activity_user,
51,public,activityreason,Sujets d'échange
52,public,activityreasoncategory,Catégories de sujets
53,public,activitytpresence,Présence aux échanges
54,public,activitytype,Types d'échanges
55,public,activitytypecategory,Catégories de types d'échanges
56,public,centers,"Centres (territoires, agences, etc.)"
57,public,chill_activity_activity_chill_person_socialaction,
58,public,chill_activity_activity_chill_person_socialissue
59,public,chill_docgen_template,Gabarits de documents
60,public,chill_main_address,Adresses
61,public,chill_main_address_legacy,Anciennes adresses (dépréciés)
62,public,chill_main_address_reference,Adresses de référence
63,public,chill_main_civility,Civilités
64,public,chill_main_cronjob_execution,Dernière exécution des tâche cron
65,public,chill_main_geographical_unit,Unités géographiques
66,public,chill_main_geographical_unit_layer,Couches d'unités géographiques
67,public,chill_main_location,Localisations
68,public,chill_main_location_type,Types de localisations
69,public,chill_main_notification,Notifications
70,public,chill_main_notification_addresses_unread
71,public,chill_main_notification_addresses_user
72,public,chill_main_notification_comment,
73,public,chill_main_postal_code,Code postaux
74,public,chill_main_saved_export,Exports enregistrés
75,public,chill_main_user_job,Métiers
76,public,chill_main_workflow_entity,Workflows
77,public,chill_main_workflow_entity_comment
78,public,chill_main_workflow_entity_step,Etapes du workflow
79,public,chill_main_workflow_entity_step_cc_user,
80,public,chill_main_workflow_entity_step_user
81,public,chill_main_workflow_entity_step_user_by_accesskey,
82,public,chill_main_workflow_entity_subscriber_to_final,
83,public,chill_main_workflow_entity_subscriber_to_step
84,public,chill_person_accompanying_period,Parcours d'accompagnement
85,public,chill_person_accompanying_period_closingmotive,Motifs de cloture des parcours
86,public,chill_person_accompanying_period_comment,Commentaires des parcours
87,public,chill_person_accompanying_period_location_history,Historique de la localisatio ndes parcours
88,public,chill_person_accompanying_period_origin,Origine des parcours
89,public,chill_person_accompanying_period_participation,Appartenance des usagers au parcours
90,public,chill_person_accompanying_period_resource,Personnes ressources d'un parcours
91,public,chill_person_accompanying_period_social_issues,
92,public,chill_person_accompanying_period_step_history
93,public,chill_person_accompanying_period_user_history
94,public,chill_person_accompanying_period_work,Actions d'accompagnements
95,public,chill_person_accompanying_period_work_evaluation,Évaluations (dans les actions d'accompagnements)
96,public,chill_person_accompanying_period_work_evaluation_document,Documents des évaluations
97,public,chill_person_accompanying_period_work_goal,Objectifs d'une actions
98,public,chill_person_accompanying_period_work_goal_result,Objectifs et résultats d'une action
99,public,chill_person_accompanying_period_work_person,Usagers associés à une actions
100,public,chill_person_accompanying_period_work_referrer,Référents d'une actions
101,public,chill_person_accompanying_period_work_result,Résultats d'une action
102,public,chill_person_accompanying_period_work_third_party,Tiers traitants d'une action
103,public,chill_person_alt_name,"Noms supplémentaires d'un usager (nom marital, etc.)"
104,public,chill_person_household,Ménages
105,public,chill_person_household_composition,
106,public,chill_person_household_composition_type,Types de composition de ménage
107,public,chill_person_household_members,Membres du ménages
108,public,chill_person_household_position,Positions dans le ménage
109,public,chill_person_household_to_addresses,Association adresses - ménages
110,public,chill_person_marital_status,Etats civils
111,public,chill_person_not_duplicate,
112,public,chill_person_person,Usagers
113,public,chill_person_person_center_history,Historique des centres d'un usagers
114,public,chill_person_persons_to_addresses,Déprécié
115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager
116,public,chill_person_relations,Types de relations de filiation
117,public,chill_person_relationships,Relations de filiations
118,public,chill_person_residential_address,Adresses de résidences
119,public,chill_person_resource,Personnes ressources (pour les personnes)
120,public,chill_person_resource_kind,Type de personnes ressources
121,public,chill_person_social_action,Liste des actions d'accompagnement
122,public,chill_person_social_action_goal,Objectifs associés à une action
123,public,chill_person_social_action_result,Résultats associés à une action
124,public,chill_person_social_issue,Problématiques sociales
125,public,chill_person_social_work_evaluation,Evaluations disponibles
126,public,chill_person_social_work_evaluation_action,Associations entre les évaluations et les actions
127,public,chill_person_social_work_goal,Objectifs disponibles pour une actions
128,public,chill_person_social_work_goal_result,Objectifs et résultats disponible pour une action
129,public,chill_person_social_work_result,Résultats disponibles pour une action
130,public,country,Pays
131,public,custom_field_long_choice_options,
132,public,customfield
133,public,customfieldsdefaultgroup
134,public,customfieldsgroup
135,public,geography_columns,Table liée à postgis
136,public,geometry_columns,Table liée à postgis
137,public,group_centers,
138,public,language,Langues
139,public,messenger_messages,Table système
140,public,migration_versions,Table système
141,public,permission_groups
142,public,permissionsgroup_rolescope
143,public,persons_spoken_languages
144,public,regroupment,Regroupement de centres
145,public,regroupment_center,
146,public,role_scopes,
147,public,scopes,Services
148,public,spatial_ref_sys,Table système (postgis)
149,public,user_groupcenter,
150,public,users,Utilisateurs
151,public,view_chill_person_accompanying_period_info,
152,public,view_chill_person_current_address
153,public,view_chill_person_household_address
154,public,view_chill_person_person_center_history_current
Can't render this file because it has a wrong number of fields in line 28.

View File

@@ -36,6 +36,7 @@ As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the fram
Assets <assets.rst> Assets <assets.rst>
Cron Jobs <cronjob.rst> Cron Jobs <cronjob.rst>
Info about entities <entity-info.rst> Info about entities <entity-info.rst>
Info about database (in French) <database-principles.rst>
Layout and UI Layout and UI
************** **************

View File

@@ -23,19 +23,12 @@ class ChillMainConfiguration implements ConfigurationInterface
{ {
use AddWidgetConfigurationTrait; use AddWidgetConfigurationTrait;
/**
* @var ContainerBuilder
*/
private $containerBuilder;
public function __construct( public function __construct(
array $widgetFactories, array $widgetFactories,
ContainerBuilder $containerBuilder private readonly ContainerBuilder $containerBuilder
) { ) {
// we register here widget factories (see below) // we register here widget factories (see below)
$this->setWidgetFactories($widgetFactories); $this->setWidgetFactories($widgetFactories);
// we will need the container builder later...
$this->containerBuilder = $containerBuilder;
} }
public function getConfigTreeBuilder() public function getConfigTreeBuilder()

View File

@@ -21,11 +21,11 @@ return static function (RectorConfig $rectorConfig): void {
//define sets of rules //define sets of rules
$rectorConfig->sets([ $rectorConfig->sets([
LevelSetList::UP_TO_PHP_74 LevelSetList::UP_TO_PHP_82
]); ]);
// chill rules // chill rules
$rectorConfig->rule(\Chill\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class); //$rectorConfig->rule(\Chill\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class);
// skip some path... // skip some path...
$rectorConfig->skip([ $rectorConfig->skip([
@@ -37,27 +37,5 @@ return static function (RectorConfig $rectorConfig): void {
// must merge MR500 and review a typing of "ArrayCollection" in entities // must merge MR500 and review a typing of "ArrayCollection" in entities
\Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::class, \Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector::class,
// remove all PHP80 rules, in order to activate them one by one
\Rector\Php80\Rector\ClassMethod\AddParamBasedOnParentClassMethodRector::class,
\Rector\Php80\Rector\Class_\AnnotationToAttributeRector::class,
\Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector::class,
\Rector\Php80\Rector\FuncCall\ClassOnObjectRector::class,
\Rector\Php80\Rector\ClassConstFetch\ClassOnThisVariableObjectRector::class,
\Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector::class,
\Rector\Php80\Rector\Class_\DoctrineAnnotationClassToAttributeRector::class,
\Rector\Php80\Rector\ClassMethod\FinalPrivateToPrivateVisibilityRector::class,
\Rector\Php80\Rector\Ternary\GetDebugTypeRector::class,
\Rector\Php80\Rector\FunctionLike\MixedTypeRector::class,
\Rector\Php80\Rector\Property\NestedAnnotationToAttributeRector::class,
\Rector\Php80\Rector\FuncCall\Php8ResourceReturnToObjectRector::class,
\Rector\Php80\Rector\Catch_\RemoveUnusedVariableInCatchRector::class,
\Rector\Php80\Rector\ClassMethod\SetStateToStaticRector::class,
\Rector\Php80\Rector\NotIdentical\StrContainsRector::class,
\Rector\Php80\Rector\Identical\StrEndsWithRector::class,
\Rector\Php80\Rector\Identical\StrStartsWithRector::class,
\Rector\Php80\Rector\Class_\StringableForToStringRector::class,
\Rector\Php80\Rector\FuncCall\TokenGetAllToObjectRector::class,
\Rector\Php80\Rector\FunctionLike\UnionTypesRector::class
]); ]);
}; };

View File

@@ -18,11 +18,17 @@ use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository; use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\ActivityBundle\Repository\ActivityUserJobRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\LocationRepository; use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent; use Chill\PersonBundle\Privacy\PrivacyEvent;
@@ -47,76 +53,32 @@ use function array_key_exists;
final class ActivityController extends AbstractController final class ActivityController extends AbstractController
{ {
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
private ActivityRepository $activityRepository;
private ActivityTypeCategoryRepository $activityTypeCategoryRepository;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private CenterResolverManagerInterface $centerResolver;
private EntityManagerInterface $entityManager;
private EventDispatcherInterface $eventDispatcher;
private LocationRepository $locationRepository;
private LoggerInterface $logger;
private PersonRepository $personRepository;
private SerializerInterface $serializer;
private ThirdPartyRepository $thirdPartyRepository;
private TranslatorInterface $translator;
private UserRepositoryInterface $userRepository;
public function __construct( public function __construct(
ActivityACLAwareRepositoryInterface $activityACLAwareRepository, private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
ActivityTypeRepositoryInterface $activityTypeRepository, private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
ActivityTypeCategoryRepository $activityTypeCategoryRepository, private readonly ActivityTypeCategoryRepository $activityTypeCategoryRepository,
PersonRepository $personRepository, private readonly PersonRepository $personRepository,
ThirdPartyRepository $thirdPartyRepository, private readonly ThirdPartyRepository $thirdPartyRepository,
LocationRepository $locationRepository, private readonly LocationRepository $locationRepository,
ActivityRepository $activityRepository, private readonly ActivityRepository $activityRepository,
AccompanyingPeriodRepository $accompanyingPeriodRepository, private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository,
EntityManagerInterface $entityManager, private readonly EntityManagerInterface $entityManager,
EventDispatcherInterface $eventDispatcher, private readonly EventDispatcherInterface $eventDispatcher,
LoggerInterface $logger, private readonly LoggerInterface $logger,
SerializerInterface $serializer, private readonly SerializerInterface $serializer,
UserRepositoryInterface $userRepository, private readonly UserRepositoryInterface $userRepository,
CenterResolverManagerInterface $centerResolver, private readonly CenterResolverManagerInterface $centerResolver,
TranslatorInterface $translator private readonly TranslatorInterface $translator,
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly PaginatorFactory $paginatorFactory,
) { ) {
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->activityTypeRepository = $activityTypeRepository;
$this->activityTypeCategoryRepository = $activityTypeCategoryRepository;
$this->personRepository = $personRepository;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->locationRepository = $locationRepository;
$this->activityRepository = $activityRepository;
$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
$this->entityManager = $entityManager;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
$this->serializer = $serializer;
$this->userRepository = $userRepository;
$this->centerResolver = $centerResolver;
$this->translator = $translator;
} }
/** /**
* Deletes a Activity entity. * Deletes a Activity entity.
*
* @param mixed $id
*/ */
public function deleteAction(Request $request, $id) public function deleteAction(Request $request, mixed $id)
{ {
$view = null; $view = null;
@@ -289,14 +251,31 @@ final class ActivityController extends AbstractController
{ {
$view = null; $view = null;
$activities = []; $activities = [];
// TODO: add pagination
[$person, $accompanyingPeriod] = $this->getEntity($request); [$person, $accompanyingPeriod] = $this->getEntity($request);
$filter = $this->buildFilterOrder($person ?? $accompanyingPeriod);
$filterArgs = [
'my_activities' => $filter->getSingleCheckboxData('my_activities'),
'types' => $filter->hasEntityChoice('activity_types') ? $filter->getEntityChoiceData('activity_types') : [],
'jobs' => $filter->hasEntityChoice('jobs') ? $filter->getEntityChoiceData('jobs') : [],
'before' => $filter->getDateRangeData('activity_date')['to'],
'after' => $filter->getDateRangeData('activity_date')['from'],
];
if ($person instanceof Person) { if ($person instanceof Person) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
$count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE, $filterArgs);
$paginator = $this->paginatorFactory->create($count);
$activities = $this->activityACLAwareRepository $activities = $this->activityACLAwareRepository
->findByPerson($person, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); ->findByPerson(
$person,
ActivityVoter::SEE,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
['date' => 'DESC', 'id' => 'DESC'],
$filterArgs
);
$event = new PrivacyEvent($person, [ $event = new PrivacyEvent($person, [
'element_class' => Activity::class, 'element_class' => Activity::class,
@@ -308,10 +287,21 @@ final class ActivityController extends AbstractController
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);
$count = $this->activityACLAwareRepository->countByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, $filterArgs);
$paginator = $this->paginatorFactory->create($count);
$activities = $this->activityACLAwareRepository $activities = $this->activityACLAwareRepository
->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']); ->findByAccompanyingPeriod(
$accompanyingPeriod,
ActivityVoter::SEE,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
['date' => 'DESC', 'id' => 'DESC'],
$filterArgs
);
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
} else {
throw new \LogicException("Unsupported");
} }
return $this->render( return $this->render(
@@ -320,10 +310,47 @@ final class ActivityController extends AbstractController
'activities' => $activities, 'activities' => $activities,
'person' => $person, 'person' => $person,
'accompanyingCourse' => $accompanyingPeriod, 'accompanyingCourse' => $accompanyingPeriod,
'filter' => $filter,
'paginator' => $paginator,
] ]
); );
} }
private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper
{
$filterBuilder = $this->filterOrderHelperFactory->create(self::class);
$types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated);
$jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated);
$filterBuilder
->addDateRange('activity_date', 'activity.date')
->addSingleCheckbox('my_activities', 'activity_filter.My activities');
if (1 < count($types)) {
$filterBuilder
->addEntityChoice('activity_types', 'activity_filter.Types', \Chill\ActivityBundle\Entity\ActivityType::class, $types, [
'choice_label' => function (\Chill\ActivityBundle\Entity\ActivityType $activityType) {
$text = match ($activityType->hasCategory()) {
true => $this->translatableStringHelper->localize($activityType->getCategory()->getName()) . ' > ',
false => '',
};
return $text . $this->translatableStringHelper->localize($activityType->getName());
}
]);
}
if (1 < count($jobs)) {
$filterBuilder
->addEntityChoice('jobs', 'activity_filter.Jobs', UserJob::class, $jobs, [
'choice_label' => fn (UserJob $u) => $this->translatableStringHelper->localize($u->getLabel())
]);
}
return $filterBuilder->build();
}
public function newAction(Request $request): Response public function newAction(Request $request): Response
{ {
$view = null; $view = null;
@@ -650,8 +677,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

@@ -47,10 +47,8 @@ class ActivityReasonCategoryController extends AbstractController
/** /**
* Displays a form to edit an existing ActivityReasonCategory entity. * Displays a form to edit an existing ActivityReasonCategory entity.
*
* @param mixed $id
*/ */
public function editAction($id) public function editAction(mixed $id)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@@ -98,10 +96,8 @@ class ActivityReasonCategoryController extends AbstractController
/** /**
* Finds and displays a ActivityReasonCategory entity. * Finds and displays a ActivityReasonCategory entity.
*
* @param mixed $id
*/ */
public function showAction($id) public function showAction(mixed $id)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@@ -118,10 +114,8 @@ class ActivityReasonCategoryController extends AbstractController
/** /**
* Edits an existing ActivityReasonCategory entity. * Edits an existing ActivityReasonCategory entity.
*
* @param mixed $id
*/ */
public function updateAction(Request $request, $id) public function updateAction(Request $request, mixed $id)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();

View File

@@ -24,11 +24,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
*/ */
class ActivityReasonController extends AbstractController class ActivityReasonController extends AbstractController
{ {
private ActivityReasonRepository $activityReasonRepository; public function __construct(private readonly ActivityReasonRepository $activityReasonRepository)
public function __construct(ActivityReasonRepository $activityReasonRepository)
{ {
$this->activityReasonRepository = $activityReasonRepository;
} }
/** /**
@@ -56,10 +53,8 @@ class ActivityReasonController extends AbstractController
/** /**
* Displays a form to edit an existing ActivityReason entity. * Displays a form to edit an existing ActivityReason entity.
*
* @param mixed $id
*/ */
public function editAction($id) public function editAction(mixed $id)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@@ -107,10 +102,8 @@ class ActivityReasonController extends AbstractController
/** /**
* Finds and displays a ActivityReason entity. * Finds and displays a ActivityReason entity.
*
* @param mixed $id
*/ */
public function showAction($id) public function showAction(mixed $id)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@@ -127,10 +120,8 @@ class ActivityReasonController extends AbstractController
/** /**
* Edits an existing ActivityReason entity. * Edits an existing ActivityReason entity.
*
* @param mixed $id
*/ */
public function updateAction(Request $request, $id) public function updateAction(Request $request, mixed $id)
{ {
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();

View File

@@ -25,17 +25,14 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
{ {
use \Symfony\Component\DependencyInjection\ContainerAwareTrait; use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
private EntityManagerInterface $em;
/** /**
* @var \Faker\Generator * @var \Faker\Generator
*/ */
private $faker; private $faker;
public function __construct(EntityManagerInterface $em) public function __construct(private readonly EntityManagerInterface $em)
{ {
$this->faker = FakerFactory::create('fr_FR'); $this->faker = FakerFactory::create('fr_FR');
$this->em = $em;
} }
public function getOrder() public function getOrder()

View File

@@ -65,9 +65,9 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
use TrackUpdateTrait; use TrackUpdateTrait;
public const SENTRECEIVED_RECEIVED = 'received'; final public const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent'; final public const SENTRECEIVED_SENT = 'sent';
/** /**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod") * @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod")
@@ -104,6 +104,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"}) * @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
* @Assert\Valid(traverse=true) * @Assert\Valid(traverse=true)
* @var Collection<StoredObject>
*/ */
private Collection $documents; private Collection $documents;
@@ -140,8 +141,9 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
* @Groups({"read", "docgen:read"}) * @Groups({"read", "docgen:read"})
* @var Collection<Person>
*/ */
private ?Collection $persons = null; private Collection $persons;
/** /**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable", columnPrefix="privateComment_") * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable", columnPrefix="privateComment_")
@@ -151,6 +153,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason") * @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason")
* @Groups({"docgen:read"}) * @Groups({"docgen:read"})
* @var Collection<ActivityReason>
*/ */
private Collection $reasons; private Collection $reasons;
@@ -170,6 +173,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction") * @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction")
* @Groups({"read", "docgen:read"}) * @Groups({"read", "docgen:read"})
* @var Collection<SocialAction>
*/ */
private Collection $socialActions; private Collection $socialActions;
@@ -177,14 +181,16 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue") * @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue")
* @Groups({"read", "docgen:read"}) * @Groups({"read", "docgen:read"})
* @var Collection<SocialIssue>
*/ */
private Collection $socialIssues; private Collection $socialIssues;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") * @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
* @Groups({"read", "docgen:read"}) * @Groups({"read", "docgen:read"})
* @var Collection<ThirdParty>
*/ */
private ?Collection $thirdParties = null; private Collection $thirdParties;
/** /**
* @ORM\Column(type="time", nullable=true) * @ORM\Column(type="time", nullable=true)
@@ -200,8 +206,9 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User") * @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User")
* @Groups({"read", "docgen:read"}) * @Groups({"read", "docgen:read"})
* @var Collection<User>
*/ */
private ?Collection $users = null; private Collection $users;
public function __construct() public function __construct()
{ {

View File

@@ -23,10 +23,9 @@ use Doctrine\ORM\Mapping as ORM;
class ActivityReason class ActivityReason
{ {
/** /**
* @var bool
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private $active = true; private bool $active = true;
/** /**
* @var ActivityReasonCategory * @var ActivityReasonCategory
@@ -34,7 +33,7 @@ class ActivityReason
* targetEntity="Chill\ActivityBundle\Entity\ActivityReasonCategory", * targetEntity="Chill\ActivityBundle\Entity\ActivityReasonCategory",
* inversedBy="reasons") * inversedBy="reasons")
*/ */
private $category; private ?ActivityReasonCategory $category = null;
/** /**
* @var int * @var int
@@ -43,13 +42,13 @@ class ActivityReason
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
*/ */
private $id; private ?int $id = null;
/** /**
* @var array * @var array
* @ORM\Column(type="json") * @ORM\Column(type="json")
*/ */
private $name; private array $name;
/** /**
* Get active. * Get active.
@@ -81,27 +80,9 @@ class ActivityReason
/** /**
* Get name. * Get name.
*
* @param mixed|null $locale
*
* @return array | string
*/ */
public function getName($locale = null) public function getName(): array
{ {
if ($locale) {
if (isset($this->name[$locale])) {
return $this->name[$locale];
}
foreach ($this->name as $name) {
if (!empty($name)) {
return $name;
}
}
return '';
}
return $this->name; return $this->name;
} }

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Entity; namespace Chill\ActivityBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
/** /**
@@ -21,7 +22,7 @@ use Doctrine\ORM\Mapping as ORM;
* @ORM\Table(name="activityreasoncategory") * @ORM\Table(name="activityreasoncategory")
* @ORM\HasLifecycleCallbacks * @ORM\HasLifecycleCallbacks
*/ */
class ActivityReasonCategory class ActivityReasonCategory implements \Stringable
{ {
/** /**
* @var bool * @var bool
@@ -47,12 +48,12 @@ class ActivityReasonCategory
/** /**
* Array of ActivityReason. * Array of ActivityReason.
* *
* @var ArrayCollection * @var Collection<ActivityReason>
* @ORM\OneToMany( * @ORM\OneToMany(
* targetEntity="Chill\ActivityBundle\Entity\ActivityReason", * targetEntity="Chill\ActivityBundle\Entity\ActivityReason",
* mappedBy="category") * mappedBy="category")
*/ */
private $reasons; private Collection $reasons;
/** /**
* ActivityReasonCategory constructor. * ActivityReasonCategory constructor.
@@ -65,7 +66,7 @@ class ActivityReasonCategory
/** /**
* @return string * @return string
*/ */
public function __toString() public function __toString(): string
{ {
return 'ActivityReasonCategory(' . $this->getName('x') . ')'; return 'ActivityReasonCategory(' . $this->getName('x') . ')';
} }

View File

@@ -27,11 +27,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
*/ */
class ActivityType class ActivityType
{ {
public const FIELD_INVISIBLE = 0; final public const FIELD_INVISIBLE = 0;
public const FIELD_OPTIONAL = 1; final public const FIELD_OPTIONAL = 1;
public const FIELD_REQUIRED = 2; final public const FIELD_REQUIRED = 2;
/** /**
* @deprecated not in use * @deprecated not in use
@@ -275,10 +275,8 @@ class ActivityType
/** /**
* @Assert\Callback * @Assert\Callback
*
* @param mixed $payload
*/ */
public function checkSocialActionsVisibility(ExecutionContextInterface $context, $payload) public function checkSocialActionsVisibility(ExecutionContextInterface $context, mixed $payload)
{ {
if ($this->socialIssuesVisible !== $this->socialActionsVisible) { if ($this->socialIssuesVisible !== $this->socialActionsVisible) {
if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) { if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) {

View File

@@ -22,14 +22,8 @@ use function in_array;
class ActivityEntityListener class ActivityEntityListener
{ {
private EntityManagerInterface $em; public function __construct(private readonly EntityManagerInterface $em, private readonly AccompanyingPeriodWorkRepository $workRepository)
private AccompanyingPeriodWorkRepository $workRepository;
public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository)
{ {
$this->em = $em;
$this->workRepository = $workRepository;
} }
public function persistActionToCourse(Activity $activity) public function persistActionToCourse(Activity $activity)

View File

@@ -20,16 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ByCreatorAggregator implements AggregatorInterface class ByCreatorAggregator implements AggregatorInterface
{ {
private UserRender $userRender; public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender)
{
private UserRepositoryInterface $userRepository;
public function __construct(
UserRepositoryInterface $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,16 +21,8 @@ use function in_array;
class BySocialActionAggregator implements AggregatorInterface class BySocialActionAggregator implements AggregatorInterface
{ {
private SocialActionRender $actionRender; public function __construct(private readonly SocialActionRender $actionRender, private readonly SocialActionRepository $actionRepository)
{
private SocialActionRepository $actionRepository;
public function __construct(
SocialActionRender $actionRender,
SocialActionRepository $actionRepository
) {
$this->actionRender = $actionRender;
$this->actionRepository = $actionRepository;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,16 +21,8 @@ use function in_array;
class BySocialIssueAggregator implements AggregatorInterface class BySocialIssueAggregator implements AggregatorInterface
{ {
private SocialIssueRender $issueRender; public function __construct(private readonly SocialIssueRepository $issueRepository, private readonly SocialIssueRender $issueRender)
{
private SocialIssueRepository $issueRepository;
public function __construct(
SocialIssueRepository $issueRepository,
SocialIssueRender $issueRender
) {
$this->issueRepository = $issueRepository;
$this->issueRender = $issueRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,16 +21,8 @@ use function in_array;
class ByThirdpartyAggregator implements AggregatorInterface class ByThirdpartyAggregator implements AggregatorInterface
{ {
private ThirdPartyRender $thirdPartyRender; public function __construct(private readonly ThirdPartyRepository $thirdPartyRepository, private readonly ThirdPartyRender $thirdPartyRender)
{
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(
ThirdPartyRepository $thirdPartyRepository,
ThirdPartyRender $thirdPartyRender
) {
$this->thirdPartyRepository = $thirdPartyRepository;
$this->thirdPartyRender = $thirdPartyRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,16 +21,8 @@ use function in_array;
class CreatorScopeAggregator implements AggregatorInterface class CreatorScopeAggregator implements AggregatorInterface
{ {
private ScopeRepository $scopeRepository; public function __construct(private readonly ScopeRepository $scopeRepository, private readonly TranslatableStringHelper $translatableStringHelper)
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ScopeRepository $scopeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -94,17 +94,9 @@ class DateAggregator implements AggregatorInterface
return ''; return '';
} }
switch ($data['frequency']) { return match ($data['frequency']) {
case 'month': default => $value,
case 'week': };
//return $this->translator->trans('for week') .' '. $value ;
case 'year':
//return $this->translator->trans('in year') .' '. $value ;
default:
return $value;
}
}; };
} }

View File

@@ -21,16 +21,8 @@ use function in_array;
class LocationTypeAggregator implements AggregatorInterface class LocationTypeAggregator implements AggregatorInterface
{ {
private LocationTypeRepository $locationTypeRepository; public function __construct(private readonly LocationTypeRepository $locationTypeRepository, private readonly TranslatableStringHelper $translatableStringHelper)
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
LocationTypeRepository $locationTypeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->locationTypeRepository = $locationTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -0,0 +1,80 @@
<?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\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
final readonly class ActivityLocationAggregator implements AggregatorInterface
{
public const KEY = 'activity_location_aggregator';
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actloc', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.location', 'actloc');
}
$qb->addSelect(sprintf('actloc.name AS %s', self::KEY));
$qb->addGroupBy(self::KEY);
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form required for this aggregator
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'export.aggregator.activity.by_location.Activity Location';
}
if (null === $value || '' === $value) {
return '';
}
return $value;
};
}
public function getQueryKeys($data): array
{
return [self::KEY];
}
public function getTitle()
{
return 'export.aggregator.activity.by_location.Title';
}
}

View File

@@ -22,18 +22,10 @@ use function in_array;
class ActivityTypeAggregator implements AggregatorInterface class ActivityTypeAggregator implements AggregatorInterface
{ {
public const KEY = 'activity_type_aggregator'; final public const KEY = 'activity_type_aggregator';
protected ActivityTypeRepositoryInterface $activityTypeRepository; public function __construct(protected ActivityTypeRepositoryInterface $activityTypeRepository, protected TranslatableStringHelperInterface $translatableStringHelper)
{
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ActivityTypeRepositoryInterface $activityTypeRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,18 +21,10 @@ use Symfony\Component\Form\FormBuilderInterface;
class ActivityUserAggregator implements AggregatorInterface class ActivityUserAggregator implements AggregatorInterface
{ {
public const KEY = 'activity_user_id'; final public const KEY = 'activity_user_id';
private UserRender $userRender; public function __construct(private readonly UserRepository $userRepository, private readonly UserRender $userRender)
{
private UserRepository $userRepository;
public function __construct(
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,14 +21,8 @@ use function in_array;
class ActivityUsersAggregator implements AggregatorInterface class ActivityUsersAggregator implements AggregatorInterface
{ {
private UserRender $userRender; public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender)
private UserRepositoryInterface $userRepository;
public function __construct(UserRepositoryInterface $userRepository, UserRender $userRender)
{ {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -20,14 +20,8 @@ use function in_array;
class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorInterface class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorInterface
{ {
private TranslatableStringHelperInterface $translatableStringHelper; public function __construct(private readonly UserJobRepositoryInterface $userJobRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper)
private UserJobRepositoryInterface $userJobRepository;
public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper)
{ {
$this->userJobRepository = $userJobRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -20,14 +20,8 @@ use function in_array;
class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\AggregatorInterface class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\AggregatorInterface
{ {
private ScopeRepositoryInterface $scopeRepository; public function __construct(private readonly ScopeRepositoryInterface $scopeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper)
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper)
{ {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -30,20 +30,8 @@ use function in_array;
class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface
{ {
protected ActivityReasonCategoryRepository $activityReasonCategoryRepository; public function __construct(protected ActivityReasonCategoryRepository $activityReasonCategoryRepository, protected ActivityReasonRepository $activityReasonRepository, protected TranslatableStringHelper $translatableStringHelper)
{
protected ActivityReasonRepository $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ActivityReasonCategoryRepository $activityReasonCategoryRepository,
ActivityReasonRepository $activityReasonRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->activityReasonCategoryRepository = $activityReasonCategoryRepository;
$this->activityReasonRepository = $activityReasonRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string
@@ -117,21 +105,11 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)
{ {
// for performance reason, we load data from db only once match ($data['level']) {
switch ($data['level']) { 'reasons' => $this->activityReasonRepository->findBy(['id' => $values]),
case 'reasons': 'categories' => $this->activityReasonCategoryRepository->findBy(['id' => $values]),
$this->activityReasonRepository->findBy(['id' => $values]); default => throw new RuntimeException(sprintf("The level data '%s' is invalid.", $data['level'])),
};
break;
case 'categories':
$this->activityReasonCategoryRepository->findBy(['id' => $values]);
break;
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) {

View File

@@ -20,11 +20,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class SentReceivedAggregator implements AggregatorInterface class SentReceivedAggregator implements AggregatorInterface
{ {
private TranslatorInterface $translator; public function __construct(private readonly TranslatorInterface $translator)
public function __construct(TranslatorInterface $translator)
{ {
$this->translator = $translator;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -16,9 +16,9 @@ namespace Chill\ActivityBundle\Export;
*/ */
abstract class Declarations abstract class Declarations
{ {
public const ACTIVITY = 'activity'; final public const ACTIVITY = 'activity';
public const ACTIVITY_ACP = 'activity_linked_to_acp'; final public const ACTIVITY_ACP = 'activity_linked_to_acp';
public const ACTIVITY_PERSON = 'activity_linked_to_person'; final public const ACTIVITY_PERSON = 'activity_linked_to_person';
} }

View File

@@ -24,20 +24,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ListActivity implements ListInterface, GroupedExportInterface class ListActivity implements ListInterface, GroupedExportInterface
{ {
private EntityManagerInterface $entityManager; public function __construct(private readonly ListActivityHelper $helper, private readonly EntityManagerInterface $entityManager, private readonly TranslatableStringExportLabelHelper $translatableStringExportLabelHelper)
{
private ListActivityHelper $helper;
private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper;
public function __construct(
ListActivityHelper $helper,
EntityManagerInterface $entityManager,
TranslatableStringExportLabelHelper $translatableStringExportLabelHelper
) {
$this->helper = $helper;
$this->entityManager = $entityManager;
$this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
@@ -66,22 +54,17 @@ class ListActivity implements ListInterface, GroupedExportInterface
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)
{ {
switch ($key) { return match ($key) {
case 'acpId': 'acpId' => static function ($value) {
return static function ($value) { if ('_header' === $value) {
if ('_header' === $value) { return ListActivityHelper::MSG_KEY . 'accompanying course id';
return ListActivityHelper::MSG_KEY . 'accompanying course id'; }
}
return $value ?? ''; return $value ?? '';
}; },
'scopesNames' => $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles'),
case 'scopesNames': default => $this->helper->getLabels($key, $values, $data),
return $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles'); };
default:
return $this->helper->getLabels($key, $values, $data);
}
} }
public function getQueryKeys($data) public function getQueryKeys($data)

View File

@@ -24,12 +24,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class CountActivity implements ExportInterface, GroupedExportInterface class CountActivity implements ExportInterface, GroupedExportInterface
{ {
protected ActivityRepository $activityRepository; public function __construct(protected ActivityRepository $activityRepository)
{
public function __construct(
ActivityRepository $activityRepository
) {
$this->activityRepository = $activityRepository;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)

View File

@@ -36,8 +36,6 @@ use function in_array;
class ListActivity implements ListInterface, GroupedExportInterface class ListActivity implements ListInterface, GroupedExportInterface
{ {
protected EntityManagerInterface $entityManager;
protected array $fields = [ protected array $fields = [
'id', 'id',
'date', 'date',
@@ -52,22 +50,8 @@ class ListActivity implements ListInterface, GroupedExportInterface
'person_id', 'person_id',
]; ];
protected TranslatableStringHelperInterface $translatableStringHelper; public function __construct(protected EntityManagerInterface $entityManager, protected TranslatorInterface $translator, protected TranslatableStringHelperInterface $translatableStringHelper, private readonly ActivityRepository $activityRepository)
{
protected TranslatorInterface $translator;
private ActivityRepository $activityRepository;
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper,
ActivityRepository $activityRepository
) {
$this->entityManager = $em;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->activityRepository = $activityRepository;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)

View File

@@ -30,24 +30,18 @@ use Symfony\Component\Form\FormBuilderInterface;
*/ */
class StatActivityDuration implements ExportInterface, GroupedExportInterface class StatActivityDuration implements ExportInterface, GroupedExportInterface
{ {
public const SUM = 'sum'; final public const SUM = 'sum';
/**
* The action for this report.
*/
protected string $action;
private ActivityRepository $activityRepository;
/** /**
* @param string $action the stat to perform * @param string $action the stat to perform
*/ */
public function __construct( public function __construct(
ActivityRepository $activityRepository, private readonly ActivityRepository $activityRepository,
string $action = 'sum' /**
* The action for this report.
*/
protected string $action = 'sum'
) { ) {
$this->action = $action;
$this->activityRepository = $activityRepository;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)

View File

@@ -30,46 +30,19 @@ use const SORT_NUMERIC;
class ListActivityHelper class ListActivityHelper
{ {
public const MSG_KEY = 'export.list.activity.'; final public const MSG_KEY = 'export.list.activity.';
private ActivityPresenceRepositoryInterface $activityPresenceRepository;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private DateTimeHelper $dateTimeHelper;
private LabelPersonHelper $labelPersonHelper;
private LabelThirdPartyHelper $labelThirdPartyHelper;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatableStringExportLabelHelper $translatableStringLabelHelper;
private TranslatorInterface $translator;
private UserHelper $userHelper;
public function __construct( public function __construct(
ActivityPresenceRepositoryInterface $activityPresenceRepository, private readonly ActivityPresenceRepositoryInterface $activityPresenceRepository,
ActivityTypeRepositoryInterface $activityTypeRepository, private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
DateTimeHelper $dateTimeHelper, private readonly DateTimeHelper $dateTimeHelper,
LabelPersonHelper $labelPersonHelper, private readonly LabelPersonHelper $labelPersonHelper,
LabelThirdPartyHelper $labelThirdPartyHelper, private readonly LabelThirdPartyHelper $labelThirdPartyHelper,
TranslatorInterface $translator, private readonly TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatableStringHelperInterface $translatableStringHelper,
TranslatableStringExportLabelHelper $translatableStringLabelHelper, private readonly TranslatableStringExportLabelHelper $translatableStringLabelHelper,
UserHelper $userHelper private readonly UserHelper $userHelper
) { ) {
$this->activityPresenceRepository = $activityPresenceRepository;
$this->activityTypeRepository = $activityTypeRepository;
$this->dateTimeHelper = $dateTimeHelper;
$this->labelPersonHelper = $labelPersonHelper;
$this->labelThirdPartyHelper = $labelThirdPartyHelper;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->translatableStringLabelHelper = $translatableStringLabelHelper;
$this->userHelper = $userHelper;
} }
public function addSelect(QueryBuilder $qb): void public function addSelect(QueryBuilder $qb): void
@@ -115,113 +88,78 @@ class ListActivityHelper
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)
{ {
switch ($key) { return match ($key) {
case 'createdAt': 'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key),
case 'updatedAt': 'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key),
return $this->dateTimeHelper->getLabel($key); 'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY . $key),
'attendeeName' => function ($value) {
if ('_header' === $value) {
return 'Attendee';
}
case 'createdBy': if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) {
case 'updatedBy': return '';
return $this->userHelper->getLabel($key, $values, $key); }
case 'date': return $this->translatableStringHelper->localize($presence->getName());
return $this->dateTimeHelper->getLabel(self::MSG_KEY . $key); },
'listReasons' => $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'),
'typeName' => function ($value) {
if ('_header' === $value) {
return 'Activity type';
}
case 'attendeeName': if (null === $value || null === $type = $this->activityTypeRepository->find($value)) {
return function ($value) { return '';
if ('_header' === $value) { }
return 'Attendee';
}
if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) { return $this->translatableStringHelper->localize($type->getName());
return ''; },
} 'usersNames' => $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name'),
'usersIds', 'thirdPartiesIds', 'personsIds' => static function ($value) use ($key) {
if ('_header' === $value) {
return match ($key) {
'usersIds' => self::MSG_KEY . 'users ids',
'thirdPartiesIds' => self::MSG_KEY . 'third parties ids',
'personsIds' => self::MSG_KEY . 'persons ids',
};
}
return $this->translatableStringHelper->localize($presence->getName()); $decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR);
};
case 'listReasons': return implode(
return $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'); '|',
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
SORT_NUMERIC
)
);
},
'personsNames' => $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name'),
'thirdPartiesNames' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties'),
'sentReceived' => function ($value) {
if ('_header' === $value) {
return self::MSG_KEY . 'sent received';
}
case 'typeName': if (null === $value) {
return function ($value) { return '';
if ('_header' === $value) { }
return 'Activity type';
}
if (null === $value || null === $type = $this->activityTypeRepository->find($value)) { return $this->translator->trans($value);
return ''; },
} default => function ($value) use ($key) {
if ('_header' === $value) {
return self::MSG_KEY . $key;
}
return $this->translatableStringHelper->localize($type->getName()); if (null === $value) {
}; return '';
}
case 'usersNames': return $this->translator->trans($value);
return $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name'); },
};
case 'usersIds':
case 'thirdPartiesIds':
case 'personsIds':
return static function ($value) use ($key) {
if ('_header' === $value) {
switch ($key) {
case 'usersIds':
return self::MSG_KEY . 'users ids';
case 'thirdPartiesIds':
return self::MSG_KEY . 'third parties ids';
case 'personsIds':
return self::MSG_KEY . 'persons ids';
default:
throw new LogicException('key not supported');
}
}
$decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR);
return implode(
'|',
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
SORT_NUMERIC
)
);
};
case 'personsNames':
return $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name');
case 'thirdPartiesNames':
return $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties');
case 'sentReceived':
return function ($value) {
if ('_header' === $value) {
return self::MSG_KEY . 'sent received';
}
if (null === $value) {
return '';
}
return $this->translator->trans($value);
};
default:
return function ($value) use ($key) {
if ('_header' === $value) {
return self::MSG_KEY . $key;
}
if (null === $value) {
return '';
}
return $this->translator->trans($value);
};
}
} }
public function getQueryKeys($data) public function getQueryKeys($data)

View File

@@ -23,16 +23,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ActivityTypeFilter implements FilterInterface class ActivityTypeFilter implements FilterInterface
{ {
private ActivityTypeRepositoryInterface $activityTypeRepository; public function __construct(private readonly ActivityTypeRepositoryInterface $activityTypeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper)
{
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ActivityTypeRepositoryInterface $activityTypeRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ByCreatorFilter implements FilterInterface class ByCreatorFilter implements FilterInterface
{ {
private UserRender $userRender; public function __construct(private readonly UserRender $userRender)
public function __construct(UserRender $userRender)
{ {
$this->userRender = $userRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -22,11 +22,8 @@ use function in_array;
class BySocialActionFilter implements FilterInterface class BySocialActionFilter implements FilterInterface
{ {
private SocialActionRender $actionRender; public function __construct(private readonly SocialActionRender $actionRender)
public function __construct(SocialActionRender $actionRender)
{ {
$this->actionRender = $actionRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -22,11 +22,8 @@ use function in_array;
class BySocialIssueFilter implements FilterInterface class BySocialIssueFilter implements FilterInterface
{ {
private SocialIssueRender $issueRender; public function __construct(private readonly SocialIssueRender $issueRender)
public function __construct(SocialIssueRender $issueRender)
{ {
$this->issueRender = $issueRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -28,11 +28,8 @@ class EmergencyFilter implements FilterInterface
private const DEFAULT_CHOICE = false; private const DEFAULT_CHOICE = false;
private TranslatorInterface $translator; public function __construct(private readonly TranslatorInterface $translator)
public function __construct(TranslatorInterface $translator)
{ {
$this->translator = $translator;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -22,11 +22,8 @@ use function in_array;
class LocationTypeFilter implements FilterInterface class LocationTypeFilter implements FilterInterface
{ {
private TranslatableStringHelper $translatableStringHelper; public function __construct(private readonly TranslatableStringHelper $translatableStringHelper)
public function __construct(TranslatableStringHelper $translatableStringHelper)
{ {
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -29,11 +29,8 @@ class SentReceivedFilter implements FilterInterface
private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT; private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT;
private TranslatorInterface $translator; public function __construct(private readonly TranslatorInterface $translator)
public function __construct(TranslatorInterface $translator)
{ {
$this->translator = $translator;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -21,11 +21,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class UserFilter implements FilterInterface class UserFilter implements FilterInterface
{ {
private UserRender $userRender; public function __construct(private readonly UserRender $userRender)
public function __construct(UserRender $userRender)
{ {
$this->userRender = $userRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -23,11 +23,8 @@ use function in_array;
class UserScopeFilter implements FilterInterface class UserScopeFilter implements FilterInterface
{ {
private TranslatableStringHelper $translatableStringHelper; public function __construct(private readonly TranslatableStringHelper $translatableStringHelper)
public function __construct(TranslatableStringHelper $translatableStringHelper)
{ {
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -27,16 +27,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class ActivityDateFilter implements FilterInterface class ActivityDateFilter implements FilterInterface
{ {
protected TranslatorInterface $translator; public function __construct(protected TranslatorInterface $translator, private readonly RollingDateConverterInterface $rollingDateConverter)
{
private RollingDateConverterInterface $rollingDateConverter;
public function __construct(
TranslatorInterface $translator,
RollingDateConverterInterface $rollingDateConverter
) {
$this->translator = $translator;
$this->rollingDateConverter = $rollingDateConverter;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -26,16 +26,10 @@ use function count;
class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface
{ {
protected ActivityTypeRepositoryInterface $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct( public function __construct(
TranslatableStringHelperInterface $translatableStringHelper, protected TranslatableStringHelperInterface $translatableStringHelper,
ActivityTypeRepositoryInterface $activityTypeRepository protected ActivityTypeRepositoryInterface $activityTypeRepository
) { ) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityTypeRepository = $activityTypeRepository;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ActivityUsersFilter implements FilterInterface class ActivityUsersFilter implements FilterInterface
{ {
private UserRender $userRender; public function __construct(private readonly UserRender $userRender)
public function __construct(UserRender $userRender)
{ {
$this->userRender = $userRender;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -29,16 +29,8 @@ use function in_array;
class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface
{ {
protected ActivityReasonRepository $activityReasonRepository; public function __construct(protected TranslatableStringHelper $translatableStringHelper, protected ActivityReasonRepository $activityReasonRepository)
{
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelper $helper,
ActivityReasonRepository $activityReasonRepository
) {
$this->translatableStringHelper = $helper;
$this->activityReasonRepository = $activityReasonRepository;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -37,20 +37,8 @@ use function count;
class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface
{ {
protected ActivityReasonRepository $activityReasonRepository; public function __construct(protected TranslatableStringHelper $translatableStringHelper, protected ActivityReasonRepository $activityReasonRepository, protected TranslatorInterface $translator)
{
protected TranslatableStringHelperInterface $translatableStringHelper;
protected TranslatorInterface $translator;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRepository $activityReasonRepository,
TranslatorInterface $translator
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityReasonRepository = $activityReasonRepository;
$this->translator = $translator;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -22,11 +22,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class UsersJobFilter implements FilterInterface class UsersJobFilter implements FilterInterface
{ {
private TranslatableStringHelperInterface $translatableStringHelper; public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper)
public function __construct(TranslatableStringHelperInterface $translatableStringHelper)
{ {
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -23,16 +23,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class UsersScopeFilter implements FilterInterface class UsersScopeFilter implements FilterInterface
{ {
private ScopeRepositoryInterface $scopeRepository; public function __construct(private readonly ScopeRepositoryInterface $scopeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper)
{
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ScopeRepositoryInterface $scopeRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -58,40 +58,22 @@ use function in_array;
class ActivityType extends AbstractType class ActivityType extends AbstractType
{ {
protected AuthorizationHelper $authorizationHelper;
protected ObjectManager $om;
protected SocialActionRender $socialActionRender;
protected SocialIssueRender $socialIssueRender;
protected array $timeChoices;
protected TranslatableStringHelper $translatableStringHelper;
protected User $user; protected User $user;
public function __construct( public function __construct(
TokenStorageInterface $tokenStorage, TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper, protected AuthorizationHelper $authorizationHelper,
ObjectManager $om, protected ObjectManager $om,
TranslatableStringHelper $translatableStringHelper, protected TranslatableStringHelper $translatableStringHelper,
array $timeChoices, protected array $timeChoices,
SocialIssueRender $socialIssueRender, protected SocialIssueRender $socialIssueRender,
SocialActionRender $socialActionRender protected SocialActionRender $socialActionRender
) { ) {
if (!$tokenStorage->getToken()->getUser() instanceof User) { if (!$tokenStorage->getToken()->getUser() instanceof User) {
throw new RuntimeException('you should have a valid user'); throw new RuntimeException('you should have a valid user');
} }
$this->user = $tokenStorage->getToken()->getUser(); $this->user = $tokenStorage->getToken()->getUser();
$this->authorizationHelper = $authorizationHelper;
$this->om = $om;
$this->translatableStringHelper = $translatableStringHelper;
$this->timeChoices = $timeChoices;
$this->socialIssueRender = $socialIssueRender;
$this->socialActionRender = $socialActionRender;
} }
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void

View File

@@ -25,11 +25,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class ActivityTypeType extends AbstractType class ActivityTypeType extends AbstractType
{ {
private TranslatableStringHelper $translatableStringHelper; public function __construct(private readonly TranslatableStringHelper $translatableStringHelper)
public function __construct(TranslatableStringHelper $translatableStringHelper)
{ {
$this->translatableStringHelper = $translatableStringHelper;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)

View File

@@ -24,20 +24,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/ */
class PickActivityReasonType extends AbstractType class PickActivityReasonType extends AbstractType
{ {
private ActivityReasonRepository $activityReasonRepository;
private ActivityReasonRender $reasonRender;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct( public function __construct(
ActivityReasonRepository $activityReasonRepository, private readonly ActivityReasonRepository $activityReasonRepository,
ActivityReasonRender $reasonRender, private readonly ActivityReasonRender $reasonRender,
TranslatableStringHelperInterface $translatableStringHelper private readonly TranslatableStringHelperInterface $translatableStringHelper
) { ) {
$this->activityReasonRepository = $activityReasonRepository;
$this->reasonRender = $reasonRender;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View File

@@ -23,14 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/ */
class TranslatableActivityReasonCategoryType extends AbstractType class TranslatableActivityReasonCategoryType extends AbstractType
{ {
private TranslatableStringHelperInterface $translatableStringHelper; public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator)
private TranslatorInterface $translator;
public function __construct(TranslatableStringHelperInterface $translatableStringHelper, TranslatorInterface $translator)
{ {
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View File

@@ -20,16 +20,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class TranslatableActivityType extends AbstractType class TranslatableActivityType extends AbstractType
{ {
protected ActivityTypeRepositoryInterface $activityTypeRepository; public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper, protected ActivityTypeRepositoryInterface $activityTypeRepository)
{
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelperInterface $helper,
ActivityTypeRepositoryInterface $activityTypeRepository
) {
$this->translatableStringHelper = $helper;
$this->activityTypeRepository = $activityTypeRepository;
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View File

@@ -23,16 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/ */
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{ {
protected Security $security; public function __construct(protected Security $security, protected TranslatorInterface $translator)
{
protected TranslatorInterface $translator;
public function __construct(
Security $security,
TranslatorInterface $translator
) {
$this->security = $security;
$this->translator = $translator;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)

View File

@@ -18,13 +18,10 @@ use Symfony\Component\Security\Core\Security;
/** /**
* @implements LocalMenuBuilderInterface<array> * @implements LocalMenuBuilderInterface<array>
*/ */
final class AdminMenuBuilder implements LocalMenuBuilderInterface final readonly class AdminMenuBuilder implements LocalMenuBuilderInterface
{ {
private Security $security; public function __construct(private Security $security)
public function __construct(Security $security)
{ {
$this->security = $security;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)

View File

@@ -21,24 +21,10 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/** /**
* @implements LocalMenuBuilderInterface<array{person: Person}> * @implements LocalMenuBuilderInterface<array{person: Person}>
*/ */
final class PersonMenuBuilder implements LocalMenuBuilderInterface final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
{ {
/** public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator)
* @var AuthorizationCheckerInterface {
*/
private $authorizationChecker;
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
TranslatorInterface $translator
) {
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)

View File

@@ -16,13 +16,10 @@ use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface; use Chill\MainBundle\Notification\NotificationHandlerInterface;
final class ActivityNotificationHandler implements NotificationHandlerInterface final readonly class ActivityNotificationHandler implements NotificationHandlerInterface
{ {
private ActivityRepository $activityRepository; public function __construct(private ActivityRepository $activityRepository)
public function __construct(ActivityRepository $activityRepository)
{ {
$this->activityRepository = $activityRepository;
} }
public function getTemplate(Notification $notification, array $options = []): string public function getTemplate(Notification $notification, array $options = []): string

View File

@@ -18,67 +18,193 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType; use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\Query\ResultSetMappingBuilder; use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use function count; use function count;
use function in_array; use function in_array;
final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
{ {
private AuthorizationHelper $authorizationHelper;
private CenterResolverDispatcherInterface $centerResolverDispatcher;
private EntityManagerInterface $em;
private ActivityRepository $repository;
private Security $security;
private TokenStorageInterface $tokenStorage;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, private AuthorizationHelperForCurrentUserInterface $authorizationHelper,
CenterResolverDispatcherInterface $centerResolverDispatcher, private CenterResolverManagerInterface $centerResolverManager,
TokenStorageInterface $tokenStorage, private ActivityRepository $repository,
ActivityRepository $repository, private EntityManagerInterface $em,
EntityManagerInterface $em, private Security $security,
Security $security private RequestStack $requestStack,
) { ) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->tokenStorage = $tokenStorage;
$this->repository = $repository;
$this->em = $em;
$this->security = $security;
} }
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array /**
* @throws NonUniqueResultException
* @throws NoResultException
*/
public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int
{ {
$user = $this->security->getUser(); $qb = $this->buildBaseQuery($filters);
$center = $this->centerResolverDispatcher->resolveCenter($period);
if (0 === count($orderBy)) { $qb
$orderBy = ['date' => 'DESC']; ->select('COUNT(a)')
->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
return $qb->getQuery()->getSingleScalarResult();
}
public function countByPerson(Person $person, string $role, array $filters = []): int
{
$qb = $this->buildBaseQuery($filters);
$qb = $this->filterBaseQueryByPerson($qb, $person, $role);
$qb->select('COUNT(a)');
return $qb->getQuery()->getSingleScalarResult();
}
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array
{
$qb = $this->buildBaseQuery($filters);
$qb->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
foreach ($orderBy as $field => $order) {
$qb->addOrderBy('a.' . $field, $order);
} }
$scopes = $this->authorizationHelper if (null !== $start) {
->getReachableCircles($user, $role, $center); $qb->setFirstResult($start);
}
if (null !== $limit) {
$qb->setMaxResults($limit);
}
return $this->em->getRepository(Activity::class) return $qb->getQuery()->getResult();
->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy);
} }
public function buildBaseQuery(array $filters): QueryBuilder
{
$qb = $this->repository
->createQueryBuilder('a')
;
if (($filters['my_activities'] ?? false) and ($user = $this->security->getUser()) instanceof User) {
$qb->andWhere(
$qb->expr()->orX(
'a.createdBy = :user',
'a.user = :user',
':user MEMBER OF a.users'
)
)->setParameter('user', $user);
}
if ([] !== ($types = $filters['types'] ?? [])) {
$qb->andWhere('a.activityType IN (:types)')->setParameter('types', $types);
}
if ([] !== ($jobs = $filters['jobs'] ?? [])) {
$qb
->leftJoin('a.createdBy', 'creator')
->leftJoin('a.user', 'activity_u')
->andWhere(
$qb->expr()->orX(
'creator.userJob IN (:jobs)',
'activity_u.userJob IN (:jobs)',
'EXISTS (SELECT 1 FROM ' . User::class . ' activity_user WHERE activity_user MEMBER OF a.users AND activity_user.userJob IN (:jobs))'
)
)
->setParameter('jobs', $jobs);
}
if (null !== ($after = $filters['after'] ?? null)) {
$qb->andWhere('a.date >= :after')->setParameter('after', $after);
}
if (null !== ($before = $filters['before'] ?? null)) {
$qb->andWhere('a.date <= :before')->setParameter('before', $before);
}
return $qb;
}
/**
* @param AccompanyingPeriod|Person $associated
* @return array<ActivityType>
*/
public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array
{
$in = $this->em->createQueryBuilder();
$in
->select('1')
->from(Activity::class, 'a');
if ($associated instanceof Person) {
$in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE);
} else {
$in->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $associated);
}
// join between the embedded exist query and the main query
$in->andWhere('a.activityType = t');
$qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
$qb
->select('t')
->from(ActivityType::class, 't')
->where(
$qb->expr()->exists($in->getDQL())
);
return $qb->getQuery()->getResult();
}
public function findUserJobByAssociated(Person|AccompanyingPeriod $associated): array
{
$in = $this->em->createQueryBuilder();
$in->select('IDENTITY(u.userJob)')
->from(User::class, 'u')
->join(
Activity::class,
'a',
Join::WITH,
'a.createdBy = u OR a.user = u OR u MEMBER OF a.users'
);
if ($associated instanceof Person) {
$in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE);
} else {
$in->andWhere('a.accompanyingPeriod = :associated');
$in->setParameter('associated', $associated);
}
$qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
$qb->select('ub', 'JSON_EXTRACT(ub.label, :lang) AS HIDDEN lang')
->from(UserJob::class, 'ub')
->where($qb->expr()->in('ub.id', $in->getDQL()))
->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale())
->orderBy('lang')
;
return $qb->getQuery()->getResult();
}
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array
{ {
$rsm = new ResultSetMappingBuilder($this->em); $rsm = new ResultSetMappingBuilder($this->em);
@@ -159,25 +285,73 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
return $nq->getResult(AbstractQuery::HYDRATE_ARRAY); return $nq->getResult(AbstractQuery::HYDRATE_ARRAY);
} }
/** public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): array
* @param array $orderBy
*
* @return Activity[]|array
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
{ {
$user = $this->security->getUser(); $qb = $this->buildBaseQuery($filters);
$center = $this->centerResolverDispatcher->resolveCenter($person);
if (0 === count($orderBy)) { $qb = $this->filterBaseQueryByPerson($qb, $person, $role);
$orderBy = ['date' => 'DESC'];
foreach ($orderBy as $field => $direction) {
$qb->addOrderBy('a.' . $field, $direction);
} }
$reachableScopes = $this->authorizationHelper if (null !== $start) {
->getReachableCircles($user, $role, $center); $qb->setFirstResult($start);
}
if (null !== $limit) {
$qb->setMaxResults($limit);
}
return $this->em->getRepository(Activity::class) return $qb->getQuery()->getResult();
->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start); }
private function filterBaseQueryByPerson(QueryBuilder $qb, Person $person, string $role): QueryBuilder
{
$orX = $qb->expr()->orX();
$counter = 0;
foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
$scopes = $this->authorizationHelper->getReachableScopes($role, $center);
if ([] === $scopes) {
continue;
}
$orX->add(sprintf('a.person = :person AND a.scope IN (:scopes_%d)', $counter));
$qb->setParameter(sprintf('scopes_%d', $counter), $scopes);
$qb->setParameter('person', $person);
$counter++;
}
foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
continue;
}
$and = $qb->expr()->andX(
sprintf('a.accompanyingPeriod = :period_%d', $counter),
sprintf('a.date >= :participation_start_%d', $counter)
);
$qb
->setParameter(sprintf('period_%d', $counter), $participation->getAccompanyingPeriod())
->setParameter(sprintf('participation_start_%d', $counter), $participation->getStartDate());
if (null !== $participation->getEndDate()) {
$and->add(sprintf('a.date < :participation_end_%d', $counter));
$qb
->setParameter(sprintf('participation_end_%d', $counter), $participation->getEndDate());
}
$orX->add($and);
$counter++;
}
if (0 === $orX->count()) {
$qb->andWhere('FALSE = TRUE');
} else {
$qb->andWhere($orX);
}
return $qb;
} }
public function queryTimelineIndexer(string $context, array $args = []): array public function queryTimelineIndexer(string $context, array $args = []): array
@@ -226,7 +400,6 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
// acls: // acls:
$reachableCenters = $this->authorizationHelper->getReachableCenters( $reachableCenters = $this->authorizationHelper->getReachableCenters(
$this->tokenStorage->getToken()->getUser(),
ActivityVoter::SEE ActivityVoter::SEE
); );
@@ -251,7 +424,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
continue; continue;
} }
// we get all the reachable scopes for this center // we get all the reachable scopes for this center
$reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), ActivityVoter::SEE, $center); $reachableScopes = $this->authorizationHelper->getReachableScopes(ActivityVoter::SEE, $center);
// we get the ids for those scopes // we get the ids for those scopes
$reachablesScopesId = array_map( $reachablesScopesId = array_map(
static fn (Scope $scope) => $scope->getId(), static fn (Scope $scope) => $scope->getId(),

View File

@@ -11,15 +11,32 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository; namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
interface ActivityACLAwareRepositoryInterface interface ActivityACLAwareRepositoryInterface
{ {
/** /**
* @return Activity[]|array * Return all the activities associated to an accompanying period and that the user is allowed to apply the given role.
*
*
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
* @return array<Activity>
*/ */
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array;
/**
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
*/
public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int;
/**
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
*/
public function countByPerson(Person $person, string $role, array $filters = []): int;
/** /**
* Return a list of activities, simplified as array (not object). * Return a list of activities, simplified as array (not object).
@@ -31,7 +48,28 @@ interface ActivityACLAwareRepositoryInterface
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array; public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array;
/** /**
* @return Activity[]|array * @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
* @return array<Activity>
*/ */
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array; public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array;
/**
* Return a list of the type for the activities associated to person or accompanying period
*
* @return array<ActivityType>
*/
public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array;
/**
* Return a list of the user job for the activities associated to person or accompanying period
*
* Associated mean the job:
* - of the creator;
* - of the user (activity.user)
* - of all the users
*
* @return array<UserJob>
*/
public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array;
} }

View File

@@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository;
class ActivityPresenceRepository implements ActivityPresenceRepositoryInterface class ActivityPresenceRepository implements ActivityPresenceRepositoryInterface
{ {
private EntityRepository $repository; private readonly EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager) public function __construct(EntityManagerInterface $entityManager)
{ {

View File

@@ -23,15 +23,11 @@ use Symfony\Component\HttpFoundation\RequestStack;
*/ */
class ActivityReasonRepository extends ServiceEntityRepository class ActivityReasonRepository extends ServiceEntityRepository
{ {
private RequestStack $requestStack;
public function __construct( public function __construct(
ManagerRegistry $registry, ManagerRegistry $registry,
RequestStack $requestStack private readonly RequestStack $requestStack
) { ) {
parent::__construct($registry, ActivityReason::class); parent::__construct($registry, ActivityReason::class);
$this->requestStack = $requestStack;
} }
/** /**

View File

@@ -11,13 +11,17 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository; namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Entity\ActivityType;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
final class ActivityTypeRepository implements ActivityTypeRepositoryInterface final class ActivityTypeRepository implements ActivityTypeRepositoryInterface
{ {
private EntityRepository $repository; private readonly EntityRepository $repository;
public function __construct(EntityManagerInterface $em) public function __construct(EntityManagerInterface $em)
{ {

View File

@@ -12,12 +12,14 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository; namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Entity\ActivityType;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
interface ActivityTypeRepositoryInterface extends ObjectRepository interface ActivityTypeRepositoryInterface extends ObjectRepository
{ {
/** /**
* @return array|ActivityType[] * @return array<ActivityType>
*/ */
public function findAllActive(): array; public function findAllActive(): array;
} }

View File

@@ -80,12 +80,15 @@
<div class="context-{{ context }}"> <div class="context-{{ context }}">
{{ filter|chill_render_filter_order_helper }}
{% if activities|length == 0 %} {% if activities|length == 0 %}
<p class="chill-no-data-statement"> <p class="chill-no-data-statement">
{{ "There isn't any activities."|trans }} {{ "There isn't any activities."|trans }}
</p> </p>
{% else %} {% else %}
<div class="flex-table activity-list"> <div class="flex-table activity-list">
{% for activity in activities %} {% for activity in activities %}
{% include 'ChillActivityBundle:Activity:_list_item.html.twig' with { {% include 'ChillActivityBundle:Activity:_list_item.html.twig' with {
@@ -96,4 +99,6 @@
</div> </div>
{% endif %} {% endif %}
{{ chill_pagination(paginator) }}
</div> </div>

View File

@@ -20,9 +20,9 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{ {
public const LISTS = 'CHILL_ACTIVITY_LIST'; final public const LISTS = 'CHILL_ACTIVITY_LIST';
public const STATS = 'CHILL_ACTIVITY_STATS'; final public const STATS = 'CHILL_ACTIVITY_STATS';
protected VoterHelperInterface $helper; protected VoterHelperInterface $helper;

View File

@@ -35,7 +35,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
* *
* It is safe for usage in template and controller * It is safe for usage in template and controller
*/ */
public const CREATE = 'CHILL_ACTIVITY_CREATE'; final public const CREATE = 'CHILL_ACTIVITY_CREATE';
/** /**
* role to allow to create an activity associated win an accompanying course. * role to allow to create an activity associated win an accompanying course.
@@ -44,7 +44,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
* *
* @internal * @internal
*/ */
public const CREATE_ACCOMPANYING_COURSE = 'CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE'; final public const CREATE_ACCOMPANYING_COURSE = 'CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE';
/** /**
* role to allow to create an activity associated with a person. * role to allow to create an activity associated with a person.
@@ -53,17 +53,17 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
* *
* @internal * @internal
*/ */
public const CREATE_PERSON = 'CHILL_ACTIVITY_CREATE_PERSON'; final public const CREATE_PERSON = 'CHILL_ACTIVITY_CREATE_PERSON';
public const DELETE = 'CHILL_ACTIVITY_DELETE'; final public const DELETE = 'CHILL_ACTIVITY_DELETE';
public const FULL = 'CHILL_ACTIVITY_FULL'; final public const FULL = 'CHILL_ACTIVITY_FULL';
public const SEE = 'CHILL_ACTIVITY_SEE'; final public const SEE = 'CHILL_ACTIVITY_SEE';
public const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS'; final public const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS';
public const UPDATE = 'CHILL_ACTIVITY_UPDATE'; final public const UPDATE = 'CHILL_ACTIVITY_UPDATE';
private const ALL = [ private const ALL = [
self::CREATE, self::CREATE,
@@ -74,15 +74,12 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
self::FULL, self::FULL,
]; ];
protected Security $security;
protected VoterHelperInterface $voterHelper; protected VoterHelperInterface $voterHelper;
public function __construct( public function __construct(
Security $security, protected Security $security,
VoterHelperFactoryInterface $voterHelperFactory VoterHelperFactoryInterface $voterHelperFactory
) { ) {
$this->security = $security;
$this->voterHelper = $voterHelperFactory->generate(self::class) $this->voterHelper = $voterHelperFactory->generate(self::class)
->addCheckFor(Person::class, [self::SEE, self::CREATE]) ->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE]) ->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE])

View File

@@ -42,48 +42,17 @@ class ActivityContext implements
DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface DocGeneratorContextWithPublicFormInterface
{ {
private BaseContextData $baseContextData;
private DocumentCategoryRepository $documentCategoryRepository;
private EntityManagerInterface $em;
private NormalizerInterface $normalizer;
private PersonRenderInterface $personRender;
private PersonRepository $personRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct( public function __construct(
DocumentCategoryRepository $documentCategoryRepository, private readonly NormalizerInterface $normalizer,
NormalizerInterface $normalizer, private readonly TranslatableStringHelperInterface $translatableStringHelper,
TranslatableStringHelperInterface $translatableStringHelper, private readonly EntityManagerInterface $em,
EntityManagerInterface $em, private readonly PersonRenderInterface $personRender,
PersonRenderInterface $personRender, private readonly PersonRepository $personRepository,
PersonRepository $personRepository, private readonly TranslatorInterface $translator,
TranslatorInterface $translator, private readonly BaseContextData $baseContextData,
BaseContextData $baseContextData, private readonly ThirdPartyRender $thirdPartyRender,
ThirdPartyRender $thirdPartyRender, private readonly ThirdPartyRepository $thirdPartyRepository
ThirdPartyRepository $thirdPartyRepository
) { ) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
$this->em = $em;
$this->personRender = $personRender;
$this->personRepository = $personRepository;
$this->translator = $translator;
$this->baseContextData = $baseContextData;
$this->thirdPartyRender = $thirdPartyRender;
$this->thirdPartyRepository = $thirdPartyRepository;
} }
public function adminFormReverseTransform(array $data): array public function adminFormReverseTransform(array $data): array

View File

@@ -48,44 +48,17 @@ class ListActivitiesByAccompanyingPeriodContext implements
DocGeneratorContextWithAdminFormInterface, DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface DocGeneratorContextWithPublicFormInterface
{ {
private AccompanyingPeriodContext $accompanyingPeriodContext;
private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
private NormalizerInterface $normalizer;
private PersonRepository $personRepository;
private SocialActionRepository $socialActionRepository;
private SocialIssueRepository $socialIssueRepository;
private ThirdPartyRepository $thirdPartyRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private UserRepository $userRepository;
public function __construct( public function __construct(
AccompanyingPeriodContext $accompanyingPeriodContext, private readonly AccompanyingPeriodContext $accompanyingPeriodContext,
ActivityACLAwareRepositoryInterface $activityACLAwareRepository, private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
NormalizerInterface $normalizer, private readonly NormalizerInterface $normalizer,
PersonRepository $personRepository, private readonly PersonRepository $personRepository,
SocialActionRepository $socialActionRepository, private readonly SocialActionRepository $socialActionRepository,
SocialIssueRepository $socialIssueRepository, private readonly SocialIssueRepository $socialIssueRepository,
ThirdPartyRepository $thirdPartyRepository, private readonly ThirdPartyRepository $thirdPartyRepository,
TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatableStringHelperInterface $translatableStringHelper,
UserRepository $userRepository, private readonly UserRepository $userRepository
) { ) {
$this->accompanyingPeriodContext = $accompanyingPeriodContext;
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->normalizer = $normalizer;
$this->personRepository = $personRepository;
$this->socialActionRepository = $socialActionRepository;
$this->socialIssueRepository = $socialIssueRepository;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->userRepository = $userRepository;
} }
public function adminFormReverseTransform(array $data): array public function adminFormReverseTransform(array $data): array

View File

@@ -28,7 +28,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\MappingException; use Doctrine\ORM\Mapping\MappingException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface final readonly class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface
{ {
public const KEY = 'accompanying_period_activity_document'; public const KEY = 'accompanying_period_activity_document';

View File

@@ -19,16 +19,10 @@ use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
use Chill\DocStoreBundle\Repository\StoredObjectRepository; use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface final readonly class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface
{ {
private StoredObjectRepository $objectRepository; public function __construct(private StoredObjectRepository $objectRepository, private ActivityRepository $activityRepository)
private ActivityRepository $activityRepository;
public function __construct(StoredObjectRepository $storedObjectRepository, ActivityRepository $activityRepository)
{ {
$this->objectRepository = $storedObjectRepository;
$this->activityRepository = $activityRepository;
} }
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool public function supports(GenericDocDTO $genericDocDTO, $options = []): bool

View File

@@ -76,10 +76,8 @@ final class ActivityControllerTest extends WebTestCase
/** /**
* @dataProvider getSecuredPagesUnauthenticated * @dataProvider getSecuredPagesUnauthenticated
*
* @param mixed $url
*/ */
public function testAccessIsDeniedForUnauthenticated($url) public function testAccessIsDeniedForUnauthenticated(mixed $url)
{ {
$client = $this->createClient(); $client = $this->createClient();
@@ -216,7 +214,7 @@ final class ActivityControllerTest extends WebTestCase
->setName('social without activity'); ->setName('social without activity');
//copy role scopes where ACTIVITY is not present //copy role scopes where ACTIVITY is not present
foreach ($socialPermissionGroup->getRoleScopes() as $roleScope) { foreach ($socialPermissionGroup->getRoleScopes() as $roleScope) {
if (!strpos($roleScope->getRole(), 'ACTIVITY')) { if (!strpos((string) $roleScope->getRole(), 'ACTIVITY')) {
$withoutActivityPermissionGroup->addRoleScope($roleScope); $withoutActivityPermissionGroup->addRoleScope($roleScope);
} }
} }
@@ -262,11 +260,9 @@ final class ActivityControllerTest extends WebTestCase
} }
/** /**
* @param mixed $username
*
* @return \Symfony\Component\BrowserKit\Client * @return \Symfony\Component\BrowserKit\Client
*/ */
private function getAuthenticatedClient($username = 'center a_social') private function getAuthenticatedClient(mixed $username = 'center a_social')
{ {
return self::createClient([], [ return self::createClient([], [
'PHP_AUTH_USER' => $username, 'PHP_AUTH_USER' => $username,

View File

@@ -19,7 +19,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
*/ */
final class ActivityReasonCategoryControllerTest extends WebTestCase final class ActivityReasonCategoryControllerTest extends WebTestCase
{ {
public function testToWrite() public function testToWrite(): never
{ {
$this->markTestSkipped(); $this->markTestSkipped();
} }

View File

@@ -19,7 +19,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
*/ */
final class ActivityReasonControllerTest extends WebTestCase final class ActivityReasonControllerTest extends WebTestCase
{ {
public function testToWrite() public function testToWrite(): never
{ {
$this->markTestSkipped(); $this->markTestSkipped();
} }

View File

@@ -19,7 +19,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
*/ */
final class ActivityTypeControllerTest extends WebTestCase final class ActivityTypeControllerTest extends WebTestCase
{ {
public function testToWrite() public function testToWrite(): never
{ {
$this->markTestSkipped(); $this->markTestSkipped();
} }

View File

@@ -34,7 +34,7 @@ final class TranslatableActivityReasonTest extends TypeTestCase
parent::setUp(); parent::setUp();
} }
public function testSimple() public function testSimple(): never
{ {
$translatableActivityReasonType = new PickActivityReasonType( $translatableActivityReasonType = new PickActivityReasonType(
$this->getTranslatableStringHelper() $this->getTranslatableStringHelper()

View File

@@ -89,11 +89,9 @@ final class TranslatableActivityTypeTest extends KernelTestCase
} }
/** /**
* @param mixed $active
*
* @return \Chill\ActivityBundle\Entity\ActivityType * @return \Chill\ActivityBundle\Entity\ActivityType
*/ */
protected function getRandomType($active = true) protected function getRandomType(mixed $active = true)
{ {
$types = $this->container->get('doctrine.orm.entity_manager') $types = $this->container->get('doctrine.orm.entity_manager')
->getRepository(ActivityType::class) ->getRepository(ActivityType::class)

View File

@@ -0,0 +1,325 @@
<?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\Repository;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityACLAwareRepository;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security;
/**
* @internal
* @coversNothing
*/
class ActivityACLAwareRepositoryTest extends KernelTestCase
{
use ProphecyTrait;
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser;
private CenterResolverManagerInterface $centerResolverManager;
private ActivityRepository $activityRepository;
private EntityManagerInterface $entityManager;
private Security $security;
private RequestStack $requestStack;
protected function setUp(): void
{
self::bootKernel();
$this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class);
$this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class);
$this->activityRepository = self::$container->get(ActivityRepository::class);
$this->entityManager = self::$container->get(EntityManagerInterface::class);
$this->security = self::$container->get(Security::class);
$this->requestStack = $requestStack = new RequestStack();
$request = $this->prophesize(Request::class);
$request->getLocale()->willReturn('fr');
$request->getDefaultLocale()->willReturn('fr');
$requestStack->push($request->reveal());
}
/**
* @dataProvider provideDataFindByAccompanyingPeriod
*/
public function testFindByAccompanyingPeriod(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void
{
$security = $this->prophesize(Security::class);
$security->isGranted($role, $period)->willReturn(true);
$security->getUser()->willReturn($user);
$repository = new ActivityACLAwareRepository(
$this->authorizationHelperForCurrentUser,
$this->centerResolverManager,
$this->activityRepository,
$this->entityManager,
$security->reveal(),
$this->requestStack
);
$actual = $repository->findByAccompanyingPeriod($period, $role, $start, $limit, $orderBy, $filters);
self::assertIsArray($actual);
}
/**
* @dataProvider provideDataFindByAccompanyingPeriod
*/
public function testFindActivityTypeByAccompanyingPeriod(AccompanyingPeriod $period, User $user, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): void
{
$security = $this->prophesize(Security::class);
$security->isGranted($role, $period)->willReturn(true);
$security->getUser()->willReturn($user);
$repository = new ActivityACLAwareRepository(
$this->authorizationHelperForCurrentUser,
$this->centerResolverManager,
$this->activityRepository,
$this->entityManager,
$security->reveal(),
$this->requestStack
);
$actual = $repository->findActivityTypeByAssociated($period);
self::assertIsArray($actual);
}
/**
* @dataProvider provideDataFindByPerson
*/
public function testFindActivityTypeByPerson(Person $person, User $user, array $centers, array $scopes, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): void
{
$role = ActivityVoter::SEE;
$centerResolver = $this->prophesize(CenterResolverManagerInterface::class);
$centerResolver->resolveCenters($person)->willReturn($centers);
$authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
$authorizationHelper->getReachableScopes($role, Argument::type(Center::class))
->willReturn($scopes);
$security = $this->prophesize(Security::class);
$security->isGranted($role, Argument::type(AccompanyingPeriod::class))->willReturn(true);
$security->getUser()->willReturn($user);
$repository = new ActivityACLAwareRepository(
$authorizationHelper->reveal(),
$centerResolver->reveal(),
$this->activityRepository,
$this->entityManager,
$security->reveal(),
$this->requestStack
);
$actual = $repository->findByPerson($person, $role, $start, $limit, $orderBy, $filters);
self::assertIsArray($actual);
}
/**
* @dataProvider provideDataFindByPerson
*/
public function testFindByPerson(Person $person, User $user, array $centers, array $scopes, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): void
{
$centerResolver = $this->prophesize(CenterResolverManagerInterface::class);
$centerResolver->resolveCenters($person)->willReturn($centers);
$authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
$authorizationHelper->getReachableScopes($role, Argument::type(Center::class))
->willReturn($scopes);
$security = $this->prophesize(Security::class);
$security->isGranted($role, Argument::type(AccompanyingPeriod::class))->willReturn(true);
$security->getUser()->willReturn($user);
$repository = new ActivityACLAwareRepository(
$authorizationHelper->reveal(),
$centerResolver->reveal(),
$this->activityRepository,
$this->entityManager,
$security->reveal(),
$this->requestStack
);
$actual = $repository->findByPerson($person, $role, $start, $limit, $orderBy, $filters);
self::assertIsArray($actual);
}
public function provideDataFindByPerson(): iterable
{
$this->setUp();
/** @var Person $person */
if (null === $person = $this->entityManager->createQueryBuilder()
->select('p')->from(Person::class, 'p')->setMaxResults(1)
->getQuery()->getSingleResult()) {
throw new \RuntimeException("person not found");
}
/** @var AccompanyingPeriod $period1 */
if (null === $period1 = $this->entityManager
->createQueryBuilder()
->select('a')
->from(AccompanyingPeriod::class, 'a')
->setMaxResults(1)
->getQuery()
->getSingleResult()) {
throw new \RuntimeException("no period found");
}
/** @var AccompanyingPeriod $period2 */
if (null === $period2 = $this->entityManager
->createQueryBuilder()
->select('a')
->from(AccompanyingPeriod::class, 'a')
->where('a.id > :pid')
->setParameter('pid', $period1->getId())
->setMaxResults(1)
->getQuery()
->getSingleResult()) {
throw new \RuntimeException("no second period found");
}
// add a period
$period1->addPerson($person);
$period2->addPerson($person);
$period1->getParticipationsContainsPerson($person)->first()->setEndDate(
(new \DateTime('now'))->add(new \DateInterval('P1M'))
);
if ([] === $types = $this->entityManager
->createQueryBuilder()
->select('t')
->from(ActivityType::class, 't')
->setMaxResults(2)
->getQuery()
->getResult()) {
throw new \RuntimeException("no types");
}
if ([] === $jobs = $this->entityManager
->createQueryBuilder()
->select('j')
->from(UserJob::class, 'j')
->setMaxResults(2)
->getQuery()
->getResult()
) {
throw new \RuntimeException("no jobs found");
}
if (null === $user = $this->entityManager
->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->setMaxResults(1)
->getQuery()
->getSingleResult()
) {
throw new \RuntimeException("no user found");
}
if ([] === $centers = $this->entityManager->createQueryBuilder()
->select('c')->from(Center::class, 'c')->setMaxResults(2)->getQuery()
->getResult()) {
throw new \RuntimeException("no centers found");
}
if ([] === $scopes = $this->entityManager->createQueryBuilder()
->select('s')->from(Scope::class, 's')->setMaxResults(2)->getQuery()
->getResult()) {
throw new \RuntimeException("no scopes found");
}
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], []];
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['my_activities' => true]];
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['types' => $types]];
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['jobs' => $jobs]];
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]];
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
}
public function provideDataFindByAccompanyingPeriod(): iterable
{
$this->setUp();
if (null === $period = $this->entityManager
->createQueryBuilder()
->select('a')
->from(AccompanyingPeriod::class, 'a')
->setMaxResults(1)
->getQuery()
->getSingleResult()) {
throw new \RuntimeException("no period found");
}
if ([] === $types = $this->entityManager
->createQueryBuilder()
->select('t')
->from(ActivityType::class, 't')
->setMaxResults(2)
->getQuery()
->getResult()) {
throw new \RuntimeException("no types");
}
if ([] === $jobs = $this->entityManager
->createQueryBuilder()
->select('j')
->from(UserJob::class, 'j')
->setMaxResults(2)
->getQuery()
->getResult()
) {
throw new \RuntimeException("no jobs found");
}
if (null === $user = $this->entityManager
->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->setMaxResults(1)
->getQuery()
->getSingleResult()
) {
throw new \RuntimeException("no user found");
}
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], []];
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['my_activities' => true]];
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['types' => $types]];
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['jobs' => $jobs]];
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]];
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]];
yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]];
}
}

View File

@@ -19,7 +19,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
*/ */
final class TimelineProviderTest extends WebTestCase final class TimelineProviderTest extends WebTestCase
{ {
public function testAnActivityIsShownOnTimeline() public function testAnActivityIsShownOnTimeline(): never
{ {
$this->markTestSkipped('we have to write fixtures before writing this tests'); $this->markTestSkipped('we have to write fixtures before writing this tests');
} }

View File

@@ -33,24 +33,14 @@ class TimelineActivityProvider implements TimelineProviderInterface
{ {
private const SUPPORTED_CONTEXTS = ['center', 'person']; private const SUPPORTED_CONTEXTS = ['center', 'person'];
protected ActivityACLAwareRepository $aclAwareRepository;
protected EntityManagerInterface $em;
protected AuthorizationHelperInterface $helper;
protected UserInterface $user; protected UserInterface $user;
public function __construct( public function __construct(
EntityManagerInterface $em, protected EntityManagerInterface $em,
AuthorizationHelperInterface $helper, protected AuthorizationHelperInterface $helper,
TokenStorageInterface $storage, TokenStorageInterface $storage,
ActivityACLAwareRepository $aclAwareRepository protected ActivityACLAwareRepository $aclAwareRepository
) { ) {
$this->em = $em;
$this->helper = $helper;
$this->aclAwareRepository = $aclAwareRepository;
if (!$storage->getToken()->getUser() instanceof User) { if (!$storage->getToken()->getUser() instanceof User) {
throw new RuntimeException('A user should be authenticated !'); throw new RuntimeException('A user should be authenticated !');
} }

View File

@@ -18,9 +18,9 @@ use Symfony\Component\Validator\Constraint;
*/ */
class ActivityValidity extends Constraint class ActivityValidity extends Constraint
{ {
public const IS_REQUIRED_MESSAGE = ' is required'; final public const IS_REQUIRED_MESSAGE = ' is required';
public const ROOT_MESSAGE = 'For this type of activity, '; final public const ROOT_MESSAGE = 'For this type of activity, ';
public $noPersonsMessage = 'For this type of activity, you must add at least one person'; public $noPersonsMessage = 'For this type of activity, you must add at least one person';

View File

@@ -148,6 +148,10 @@ services:
tags: tags:
- { name: chill.export_aggregator, alias: activity_common_type_aggregator } - { name: chill.export_aggregator, alias: activity_common_type_aggregator }
Chill\ActivityBundle\Export\Aggregator\ActivityLocationAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_common_location_aggregator }
chill.activity.export.user_aggregator: chill.activity.export.user_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator
tags: tags:

View File

@@ -83,12 +83,20 @@ Third persons: Tiers non-pro.
Others persons: Usagers Others persons: Usagers
Third parties: Tiers professionnels Third parties: Tiers professionnels
Users concerned: T(M)S Users concerned: T(M)S
activity: activity:
date: Date de l'échange
Insert a document: Insérer un document Insert a document: Insérer un document
Remove a document: Supprimer le document Remove a document: Supprimer le document
comment: Commentaire comment: Commentaire
No documents: Aucun document No documents: Aucun document
# activity filter in list page
activity_filter:
My activities: Mes échanges (où j'interviens)
Types: Par type d'échange
Jobs: Par métier impliqué
#timeline #timeline
'%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"' '%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"'
@@ -378,6 +386,9 @@ export:
is sent: envoyé is sent: envoyé
is received: reçu is received: reçu
Group activity by sentreceived: Grouper les échanges par envoyé / reçu Group activity by sentreceived: Grouper les échanges par envoyé / reçu
by_location:
Activity Location: Localisation de l'échange
Title: Grouper les échanges par localisation de l'échange
generic_doc: generic_doc:
filter: filter:

View File

@@ -21,11 +21,8 @@ use Symfony\Component\HttpFoundation\Request;
final class AsideActivityController extends CRUDController final class AsideActivityController extends CRUDController
{ {
private AsideActivityCategoryRepository $categoryRepository; public function __construct(private readonly AsideActivityCategoryRepository $categoryRepository)
public function __construct(AsideActivityCategoryRepository $categoryRepository)
{ {
$this->categoryRepository = $categoryRepository;
} }
public function createEntity(string $action, Request $request): object public function createEntity(string $action, Request $request): object

View File

@@ -24,11 +24,8 @@ use function random_int;
class LoadAsideActivity extends Fixture implements DependentFixtureInterface class LoadAsideActivity extends Fixture implements DependentFixtureInterface
{ {
private UserRepository $userRepository; public function __construct(private readonly UserRepository $userRepository)
public function __construct(UserRepository $userRepository)
{ {
$this->userRepository = $userRepository;
} }
public function getDependencies(): array public function getDependencies(): array

View File

@@ -25,8 +25,9 @@ class AsideActivityCategory
{ {
/** /**
* @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent") * @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent")
* @var Collection<AsideActivityCategory>
*/ */
private $children; private Collection $children;
/** /**
* @ORM\Id * @ORM\Id
@@ -36,7 +37,6 @@ class AsideActivityCategory
private int $id; private int $id;
/** /**
* @ORM\OneToMany(targetEntity=AsideActivityCategory::class, mappedBy="parent")
* @ORM\Column(type="boolean") * @ORM\Column(type="boolean")
*/ */
private bool $isActive = true; private bool $isActive = true;
@@ -114,10 +114,8 @@ class AsideActivityCategory
/** /**
* @Assert\Callback * @Assert\Callback
*
* @param mixed $payload
*/ */
public function preventRecursiveParent(ExecutionContextInterface $context, $payload) public function preventRecursiveParent(ExecutionContextInterface $context, mixed $payload)
{ {
if (!$this->hasParent()) { if (!$this->hasParent()) {
return; return;

View File

@@ -20,14 +20,10 @@ use Symfony\Component\Form\FormBuilderInterface;
class ByActivityTypeAggregator implements AggregatorInterface class ByActivityTypeAggregator implements AggregatorInterface
{ {
private AsideActivityCategoryRepository $asideActivityCategoryRepository; public function __construct(
private readonly AsideActivityCategoryRepository $asideActivityCategoryRepository,
private TranslatableStringHelper $translatableStringHelper; private readonly TranslatableStringHelper $translatableStringHelper
) {
public function __construct(AsideActivityCategoryRepository $asideActivityCategoryRepository, TranslatableStringHelper $translatableStringHelper)
{
$this->asideActivityCategoryRepository = $asideActivityCategoryRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -22,14 +22,8 @@ use function in_array;
class ByUserJobAggregator implements AggregatorInterface class ByUserJobAggregator implements AggregatorInterface
{ {
private TranslatableStringHelperInterface $translatableStringHelper; public function __construct(private readonly UserJobRepositoryInterface $userJobRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper)
private UserJobRepositoryInterface $userJobRepository;
public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper)
{ {
$this->userJobRepository = $userJobRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -22,14 +22,8 @@ use function in_array;
class ByUserScopeAggregator implements AggregatorInterface class ByUserScopeAggregator implements AggregatorInterface
{ {
private ScopeRepositoryInterface $scopeRepository; public function __construct(private readonly ScopeRepositoryInterface $scopeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper)
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper)
{ {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
} }
public function addRole(): ?string public function addRole(): ?string

View File

@@ -16,5 +16,5 @@ namespace Chill\AsideActivityBundle\Export;
*/ */
abstract class Declarations abstract class Declarations
{ {
public const ASIDE_ACTIVITY_TYPE = 'aside_activity'; final public const ASIDE_ACTIVITY_TYPE = 'aside_activity';
} }

View File

@@ -23,12 +23,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterface class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterface
{ {
private AsideActivityRepository $repository; public function __construct(private readonly AsideActivityRepository $repository)
{
public function __construct(
AsideActivityRepository $repository
) {
$this->repository = $repository;
} }
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)

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