mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 17:28:23 +00:00 
			
		
		
		
	Compare commits
	
		
			202 Commits
		
	
	
		
			2.1.0
			...
			user_filte
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2721872da8 | |||
| ef1eb2031e | |||
| 197d69ef4a | |||
| 9423f4d055 | |||
| 99d6e9e6b8 | |||
| 8929f4b8a3 | |||
| 43b7139488 | |||
| d3251075e9 | |||
| 93a598b549 | |||
| 9b6e6ec20f | |||
| 77d4b13c1b | |||
| 2861945a52 | |||
| 5b42b85b50 | |||
| e40b1b9853 | |||
| c19232de35 | |||
| c95dc23c51 | |||
| 0361743ae0 | |||
| af4e7f1226 | |||
| ff1629cbb7 | |||
| 779eb812b0 | |||
| a990591e0c | |||
| 52d51264ba | |||
| 4e934653be | |||
| 1ee0e8e350 | |||
| 4da7040a49 | |||
| a34b5f8588 | |||
| 0d626fb345 | |||
| 25d4b6acbb | |||
| 7f9738975c | |||
| a56370d851 | |||
| 3e63b4abf3 | |||
| dd344aed52 | |||
| 1485d1ce7a | |||
| a7dbdc2b9d | |||
| b3d993165d | |||
| 9ccc57bbcb | |||
| cc0e832cc9 | |||
| c8b62d990a | |||
| b7df62d4f5 | |||
| 5a395b160f | |||
| 393e59e22b | |||
| 4a5ac170ba | |||
| c019fffbe7 | |||
| 31745bc252 | |||
| 56940d830c | |||
| 347eda05df | |||
| 90e8687799 | |||
| da50fbc1fb | |||
| 5bbc50976e | |||
| 01dee54fab | |||
| abe020f116 | |||
| 4632c18d93 | |||
| 7a1feaa8cb | |||
| 687ff63ce7 | |||
| a7c3089736 | |||
| 90be68002a | |||
| a93051d157 | |||
| f19b939bd4 | |||
| c0ae2f8ed9 | |||
| cd7a80b680 | |||
| 9f0fdb031a | |||
| 0e9597bf77 | |||
| 9978b6a6e4 | |||
| 0e5f1b4ab9 | |||
| f7c11d3567 | |||
| 51544cfc48 | |||
| 659dff3d2c | |||
| 27f797d736 | |||
| 471898e6d8 | |||
| 146103e87c | |||
| 45724100d4 | |||
| 9d2dfbe610 | |||
| 70e6aee3c5 | |||
| 6c16967cdc | |||
| 4359c2dddc | |||
| 5a102d4989 | |||
| 9073f118db | |||
| 21f11fb034 | |||
| 50de389bc7 | |||
| 960acb8c0a | |||
| c52ba06ea0 | |||
| 3fb97c3945 | |||
| 88a544fabe | |||
| bf2a4bafd8 | |||
| fe936ac0f2 | |||
| 398b633863 | |||
| 8fabfdd5c0 | |||
| 8a91be4ef3 | |||
| 3b9fae3b49 | |||
| 7349be94c8 | |||
| d815b44280 | |||
| 56957250ba | |||
| a6b451df98 | |||
| 3879e5cd9b | |||
| cb4de1f3d2 | |||
| 21f0f70350 | |||
| fadc007bfe | |||
| 1b664d0be2 | |||
| a136a278da | |||
| e0c21e46ae | |||
| 270e068ace | |||
| c683123eca | |||
| 5495b1cb44 | |||
| 24049b9dfc | |||
| 34a333f6a3 | |||
| c4dd46a03c | |||
| 29140d9374 | |||
| 5cbdea29e9 | |||
| 909d2dfb60 | |||
| 1f1ebb6adb | |||
| 4456fb3749 | |||
| 727e9d0f74 | |||
| c2a734b30b | |||
| 68c850026b | |||
| 88f48d474f | |||
| aa6479fbf9 | |||
| 05822e7e4a | |||
| b4614974c0 | |||
| c3ac382711 | |||
| ad82685c02 | |||
| 7e8dbbe873 | |||
| 12fdfd81c4 | |||
| cf576dca7b | |||
| 7fab411b96 | |||
| 88d363fc0c | |||
| a28740c46c | |||
| 1a03718014 | |||
| 1e8fec5cbd | |||
| fb1b28407a | |||
| cc30c81fd7 | |||
| 938027cc1e | |||
| db14221729 | |||
| f10c50231f | |||
| 73bc95306e | |||
| 933e9f75b3 | |||
| 3adf3625dc | |||
| ea77adc640 | |||
| d5ee158caa | |||
| c8bab1218f | |||
| 02afcb30d4 | |||
| cb0a6bbd21 | |||
| fb0afc7e0a | |||
| d1e1b1c4ce | |||
| 2aeb72811a | |||
| 7eb4fb4e56 | |||
| 5dc1cbce48 | |||
| 59e1e02b92 | |||
| 5196d26a3e | |||
| 9a3fcf081e | |||
| ef04a04056 | |||
| ba55fa349b | |||
| eea5cedc5f | |||
| c07e26785e | |||
| 4155af6686 | |||
| 9eb9a9a214 | |||
| 47a3e30ec5 | |||
| 20489813f0 | |||
| cb718a80de | |||
| ed556d9ee8 | |||
| 2b57807565 | |||
| da36c59616 | |||
| 40ddd1f1ee | |||
| e9fdabf931 | |||
| 40af1e64ac | |||
| c245ffe559 | |||
| bd074ebade | |||
| d09e5d33db | |||
| 101cca8662 | |||
| 90a5a735aa | |||
| eb107f5a15 | |||
| a3d3588b75 | |||
| 08874d734e | |||
| ff03299f80 | |||
| 72f5b0b275 | |||
| 60aeb57c45 | |||
| 2b5d007fda | |||
| e550817ded | |||
| 9c85ad74ce | |||
| fec2dd0f74 | |||
| 8dbe2d6ec2 | |||
| afcd6e0605 | |||
| 53aa887da5 | |||
| ef6a5e0b6b | |||
| 5931b2f709 | |||
| 9d1e54d3a0 | |||
| a8945f5701 | |||
|  | 1a66a08142 | ||
|  | 71e8e2baee | ||
|  | 0a2f41c8a0 | ||
|  | 67b32341d1 | ||
|  | cb37e8c006 | ||
| ba43b6b025 | |||
|  | 3576f7f14f | ||
|  | d8d517017d | ||
|  | afb25276ee | ||
|  | e850f67b00 | ||
|  | f4a7145627 | ||
|  | 6b5423ded3 | ||
|  | eb765585b2 | ||
|  | 1a759cabe4 | ||
|  | 457d71b4f3 | ||
|  | ecb8ef0146 | 
							
								
								
									
										5
									
								
								.changes/unreleased/DX-20230623-122408.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/unreleased/DX-20230623-122408.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| kind: DX | ||||
| body: '[FilterOrderHelper] add entity choice and singleCheckbox' | ||||
| time: 2023-06-23T12:24:08.133491895+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										5
									
								
								.changes/unreleased/Feature-20230623-122530.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/unreleased/Feature-20230623-122530.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| kind: Feature | ||||
| body: '[activity list] add filtering for activities list' | ||||
| time: 2023-06-23T12:25:30.49643551+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20230623-122702.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20230623-122702.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Feature | ||||
| body: '[activity list] in person context, show also the activities from the accompanying | ||||
|   periods where the person participates' | ||||
| time: 2023-06-23T12:27:02.159041095+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										5
									
								
								.changes/unreleased/Feature-20230623-124438.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/unreleased/Feature-20230623-124438.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| kind: Feature | ||||
| body: '[activity list] add pagination to the list of activities' | ||||
| time: 2023-06-23T12:44:38.879098862+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										5
									
								
								.changes/unreleased/Feature-20230705-140336.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/unreleased/Feature-20230705-140336.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| kind: Feature | ||||
| body: Allow filtering on the basis of a user within general tasks list | ||||
| time: 2023-07-05T14:03:36.664880092+02:00 | ||||
| custom: | ||||
|   Issue: "" | ||||
							
								
								
									
										12
									
								
								.changes/v2.2.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.changes/v2.2.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| ## v2.2.0 - 2023-06-18 | ||||
| ### Feature | ||||
| * When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document | ||||
| * Add notification to accompanying period work and work's evaluation's documents | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates | ||||
| ### Fixed | ||||
| * use the correct annotation for the association between PersonCurrentCenter and Person | ||||
| * ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox | ||||
| * ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter | ||||
| ### DX | ||||
| * DQL function OVERLAPSI: simplify expression in postgresql | ||||
							
								
								
									
										3
									
								
								.changes/v2.2.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.changes/v2.2.1.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## v2.2.1 - 2023-06-19 | ||||
| ### Fixed | ||||
| * ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation | ||||
							
								
								
									
										5
									
								
								.changes/v2.2.2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changes/v2.2.2.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| ## v2.2.2 - 2023-06-26 | ||||
| ### Fixed | ||||
| * [Accompanying period comments]: order comments from the most recent to the oldest, in the list | ||||
| * Api: filter social action to keep only the currently activated | ||||
| * ([#82](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/82)) Fix deletion and re-creation of filiation relationship | ||||
							
								
								
									
										42
									
								
								.changes/v2.3.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								.changes/v2.3.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| ## v2.3.0 - 2023-06-27 | ||||
| ### Feature | ||||
| * ([#110](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/110)) Edit saved exports options: the saved exports options (forms, filters, aggregators) are now editable. | ||||
| * ([#103](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/103)) Get an unified list of document in person and accompanying period context | ||||
| * [export] Set the default date of calculation of the accompanying period's list as "today" | ||||
| * Force accompanying period user history to be unique for the same period and stardate/enddate [:warning: may encounter migration issue] | ||||
|  | ||||
|   If some issue is encountered during migration, use this SQL to find the line which are in conflict, examine the problem and delete some of the concerning line | ||||
| * | ||||
|   ```sql | ||||
|   -- to see the line which are in conflict with another one | ||||
|   SELECT o.* | ||||
|   FROM chill_person_accompanying_period_user_history o | ||||
|   JOIN chill_person_accompanying_period_user_history c ON o.id < c.id AND o.accompanyingperiod_id = c.accompanyingperiod_id | ||||
|   WHERE tsrange(o.startdate, o.enddate, '[)') && tsrange(c.startdate, c.enddate, '[)') | ||||
|   ORDER BY accompanyingperiod_id; | ||||
|   -- to examine line in conflict for a given accompanyingperiod_id (given by the previous query) | ||||
|   SELECT * FROM chill_person_accompanying_period_user_history WHERE accompanyingperiod_id = IIIIDDDD order by startdate, enddate; | ||||
|   ``` | ||||
| * Rename label of filter in French: "parcours actif" => "parcours ouvert", and "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" | ||||
|  | ||||
| ### Traduction francophone des principaux changements | ||||
|  | ||||
| * Les exports enregistrés sont éditables par l'utilisateur; | ||||
| * L'onglet "Document" dans les parcours et les dossiers d'usager affiche désormais les documents ajoutés à différents endroits. | ||||
|  | ||||
|   Pour les parcours, il s'agit de: | ||||
|  | ||||
|   - documents ajoutés directement dans le parcours; | ||||
|   - documents des échanges; | ||||
|   - documents des rendez-vous; | ||||
|   - documents des évaluations; | ||||
|   - documents directement ajoutés dans le dossier des usagers concernés par le parcours; | ||||
|  | ||||
|   Pour les usagers, il s'agit de: | ||||
|  | ||||
|   - documents des échanges; | ||||
|   - documents des parcours; | ||||
|   - documents des rendez-vous; | ||||
|   - documents des actions, des échanges, des rendez-vous, des évaluations ajoutés dans les parcours. | ||||
| * Dans la liste des parcours, la date de calcul des éléments associés est "aujourd'hui" par défaut. | ||||
| * Dans les exports, renommage des libellés des filtres: "parcours actif" => "parcours ouvert", et "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" | ||||
							
								
								
									
										36
									
								
								.changes/v2.4.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								.changes/v2.4.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| ## v2.4.0 - 2023-07-07 | ||||
|  | ||||
| ### Feature | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] on "filter by user working" on accompanying period, add two dates to filters intervention within a period | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add an aggregator by user's job working on a course | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add an aggregator by user's scope working on a course | ||||
| * [export] on aggregator "user working on a course" | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a center aggregator for Person | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a filter on "job working on a course" | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add a filter on "scope working on a course" | ||||
| * ([#121](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/121)) Create a role "See Confidential Periods", separated from the "Reassign courses" role | ||||
| * ([#124](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/124)) Sync user absence / presence through microsoft outlook / graph api. | ||||
|  | ||||
| ### Fixed | ||||
| * ([#116](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/116)) On the accompanying course page, open the action on view mode if the user does not have right to update them (i.e. if the accompanying period is closed) | ||||
| * [export] Rename label for CurrentActionFilter (on accompanying period work) to make precision between "ouvert" and "sans date de fin" | ||||
| * Force the db to have either a person_location or a address_location, and avoid to have both also internally in the entity | ||||
| * [export] set rolling date on person age aggregator | ||||
| * [export] fix list when a person locating a course is without address | ||||
| * [export] remove unused condition on course about duration participation | ||||
| * Command to subscribe on MS Graph users calendars: improve the loop to be more efficient | ||||
|  | ||||
| ### DX | ||||
| * Rolling Date: can receive a null parameter | ||||
|  | ||||
| ### Traduction francophone des principaux changements | ||||
|  | ||||
| - sur le "filtre par intervenant", ajoute deux dates pour limiter la période d'intervention; | ||||
| - ajout d'un regroupement par métier des intervenants sur un parcours; | ||||
| - ajout d'un regroupement par service des intervenants sur un parcours; | ||||
| - ajout d'un regroupement par utilisateur intervenant sur un parcours | ||||
| - ajout d'un regroupement "par centre de l'usager"; | ||||
| - ajout d'un filtre "par métier intervenant sur un parcours"; | ||||
| - ajout d'un filtre "par service intervenant sur un parcours"; | ||||
| - création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); | ||||
| - synchronisation de l'absence des utilisateurs par microsoft graph api | ||||
| @@ -5,8 +5,11 @@ changelogPath: CHANGELOG.md | ||||
| versionExt: md | ||||
| versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}' | ||||
| 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. | ||||
| changeFormat: >- | ||||
|     * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{- end }}{{.Body}} | ||||
|     * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if not (eq .Custom.Long "") }} | ||||
|  | ||||
|       {{ .Custom.Long }}{{ end }} | ||||
| custom: | ||||
|     -   key: Issue | ||||
|         label: Issue number (on chill-bundles repository) (optional) | ||||
|   | ||||
| @@ -34,6 +34,7 @@ variables: | ||||
| stages: | ||||
|     - Composer install | ||||
|     - Tests | ||||
|     - Deploy | ||||
|  | ||||
| build: | ||||
|     stage: Composer install | ||||
| @@ -121,3 +122,14 @@ unit_tests: | ||||
|         paths: | ||||
|             - bin | ||||
|             - tests/app/vendor/ | ||||
|  | ||||
| release: | ||||
|     stage: Deploy | ||||
|     image: registry.gitlab.com/gitlab-org/release-cli:latest | ||||
|     rules: | ||||
|         - if: $CI_COMMIT_TAG | ||||
|     script: | ||||
|         - echo "running release_job" | ||||
|     release: | ||||
|         tag_name: '$CI_COMMIT_TAG' | ||||
|         description: "./.changes/v$CI_COMMIT_TAG.md" | ||||
|   | ||||
| @@ -13,6 +13,7 @@ $finder = PhpCsFixer\Finder::create(); | ||||
|  | ||||
| $finder | ||||
|     ->in(__DIR__.'/src') | ||||
|     ->in(__DIR__.'/utils') | ||||
|     ->append([__FILE__]) | ||||
|     ->exclude(['docs/', 'tests/app']) | ||||
|     ->notPath('tests/app') | ||||
|   | ||||
							
								
								
									
										103
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -6,6 +6,109 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), | ||||
| and is generated by [Changie](https://github.com/miniscruff/changie). | ||||
|  | ||||
|  | ||||
| ## v2.4.0 - 2023-07-07 | ||||
|  | ||||
| ### Feature | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] on "filter by user working" on accompanying period, add two dates to filters intervention within a period | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add an aggregator by user's job working on a course | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add an aggregator by user's scope working on a course | ||||
| * [export] on aggregator "user working on a course" | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a center aggregator for Person | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a filter on "job working on a course" | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add a filter on "scope working on a course" | ||||
| * ([#121](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/121)) Create a role "See Confidential Periods", separated from the "Reassign courses" role | ||||
| * ([#124](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/124)) Sync user absence / presence through microsoft outlook / graph api. | ||||
|  | ||||
| ### Fixed | ||||
| * ([#116](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/116)) On the accompanying course page, open the action on view mode if the user does not have right to update them (i.e. if the accompanying period is closed) | ||||
| * [export] Rename label for CurrentActionFilter (on accompanying period work) to make precision between "ouvert" and "sans date de fin" | ||||
| * Force the db to have either a person_location or a address_location, and avoid to have both also internally in the entity | ||||
| * [export] set rolling date on person age aggregator | ||||
| * [export] fix list when a person locating a course is without address | ||||
| * [export] remove unused condition on course about duration participation | ||||
| * Command to subscribe on MS Graph users calendars: improve the loop to be more efficient | ||||
|  | ||||
| ### DX | ||||
| * Rolling Date: can receive a null parameter | ||||
|  | ||||
| ### Traduction francophone des principaux changements | ||||
|  | ||||
| - sur le "filtre par intervenant", ajoute deux dates pour limiter la période d'intervention; | ||||
| - ajout d'un regroupement par métier des intervenants sur un parcours; | ||||
| - ajout d'un regroupement par service des intervenants sur un parcours; | ||||
| - ajout d'un regroupement par utilisateur intervenant sur un parcours | ||||
| - ajout d'un regroupement "par centre de l'usager"; | ||||
| - ajout d'un filtre "par métier intervenant sur un parcours"; | ||||
| - ajout d'un filtre "par service intervenant sur un parcours"; | ||||
| - création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); | ||||
| - synchronisation de l'absence des utilisateurs par microsoft graph api | ||||
|  | ||||
| ## v2.3.0 - 2023-06-27 | ||||
| ### Feature | ||||
| * ([#110](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/110)) Edit saved exports options: the saved exports options (forms, filters, aggregators) are now editable. | ||||
| * ([#103](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/103)) Get an unified list of document in person and accompanying period context | ||||
| * [export] Set the default date of calculation of the accompanying period's list as "today" | ||||
| * Force accompanying period user history to be unique for the same period and stardate/enddate [:warning: may encounter migration issue] | ||||
|  | ||||
|   If some issue is encountered during migration, use this SQL to find the line which are in conflict, examine the problem and delete some of the concerning line | ||||
| * | ||||
|   ```sql | ||||
|   -- to see the line which are in conflict with another one | ||||
|   SELECT o.* | ||||
|   FROM chill_person_accompanying_period_user_history o | ||||
|   JOIN chill_person_accompanying_period_user_history c ON o.id < c.id AND o.accompanyingperiod_id = c.accompanyingperiod_id | ||||
|   WHERE tsrange(o.startdate, o.enddate, '[)') && tsrange(c.startdate, c.enddate, '[)') | ||||
|   ORDER BY accompanyingperiod_id; | ||||
|   -- to examine line in conflict for a given accompanyingperiod_id (given by the previous query) | ||||
|   SELECT * FROM chill_person_accompanying_period_user_history WHERE accompanyingperiod_id = IIIIDDDD order by startdate, enddate; | ||||
|   ``` | ||||
| * Rename label of filter in French: "parcours actif" => "parcours ouvert", and "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" | ||||
|  | ||||
| ### Traduction francophone des principaux changements | ||||
|  | ||||
| * Les exports enregistrés sont éditables par l'utilisateur; | ||||
| * L'onglet "Document" dans les parcours et les dossiers d'usager affiche désormais les documents ajoutés à différents endroits. | ||||
|  | ||||
|   Pour les parcours, il s'agit de: | ||||
|  | ||||
|   - documents ajoutés directement dans le parcours; | ||||
|   - documents des échanges; | ||||
|   - documents des rendez-vous; | ||||
|   - documents des évaluations; | ||||
|   - documents directement ajoutés dans le dossier des usagers concernés par le parcours; | ||||
|  | ||||
|   Pour les usagers, il s'agit de: | ||||
|  | ||||
|   - documents des échanges; | ||||
|   - documents des parcours; | ||||
|   - documents des rendez-vous; | ||||
|   - documents des actions, des échanges, des rendez-vous, des évaluations ajoutés dans les parcours. | ||||
| * Dans la liste des parcours, la date de calcul des éléments associés est "aujourd'hui" par défaut. | ||||
| * Dans les exports, renommage des libellés des filtres: "parcours actif" => "parcours ouvert", et "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture" | ||||
|  | ||||
| ## v2.2.2 - 2023-06-26 | ||||
| ### Fixed | ||||
| * [Accompanying period comments]: order comments from the most recent to the oldest, in the list | ||||
| * Api: filter social action to keep only the currently activated | ||||
| * ([#82](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/82)) Fix deletion and re-creation of filiation relationship | ||||
|  | ||||
| ## v2.2.1 - 2023-06-19 | ||||
| ### Fixed | ||||
| * ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation | ||||
|  | ||||
| ## v2.2.0 - 2023-06-18 | ||||
| ### Feature | ||||
| * When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document | ||||
| * Add notification to accompanying period work and work's evaluation's documents | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps | ||||
| * ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates | ||||
| ### Fixed | ||||
| * use the correct annotation for the association between PersonCurrentCenter and Person | ||||
| * ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox | ||||
| * ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter | ||||
| ### DX | ||||
| * DQL function OVERLAPSI: simplify expression in postgresql | ||||
|  | ||||
| ## v2.1.0 - 2023-06-12 | ||||
|  | ||||
| ### Feature | ||||
|   | ||||
| @@ -67,6 +67,7 @@ | ||||
|         "fakerphp/faker": "^1.13", | ||||
|         "jangregor/phpstan-prophecy": "^1.0", | ||||
|         "nelmio/alice": "^3.8", | ||||
|         "nikic/php-parser": "^4.15", | ||||
|         "phpspec/prophecy-phpunit": "^2.0", | ||||
|         "phpstan/extension-installer": "^1.2", | ||||
|         "phpstan/phpstan": "^1.9", | ||||
| @@ -103,14 +104,16 @@ | ||||
|             "Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle", | ||||
|             "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", | ||||
|             "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", | ||||
|             "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src" | ||||
|             "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src", | ||||
|             "Chill\\Utils\\Rector\\": "utils/rector/src" | ||||
|         } | ||||
|     }, | ||||
|     "autoload-dev": { | ||||
|         "psr-4": { | ||||
|             "App\\": "tests/app/src/", | ||||
|             "Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests", | ||||
|             "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests" | ||||
|             "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests", | ||||
|             "Chill\\Utils\\Rector\\Tests\\": "utils/rector/tests" | ||||
|         } | ||||
|     }, | ||||
|     "config": { | ||||
|   | ||||
| @@ -62,7 +62,6 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac | ||||
|     { | ||||
|         $builder->add('date_from', DateType::class, [ | ||||
|             'label' => 'Born after this date', | ||||
|             'data' => new DateTime(), | ||||
|             'attr' => ['class' => 'datepicker'], | ||||
|             'widget' => 'single_text', | ||||
|             'format' => 'dd-MM-yyyy', | ||||
| @@ -70,12 +69,15 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac | ||||
|  | ||||
|         $builder->add('date_to', DateType::class, [ | ||||
|             'label' => 'Born before this date', | ||||
|             'data' => new DateTime(), | ||||
|             'attr' => ['class' => 'datepicker'], | ||||
|             'widget' => 'single_text', | ||||
|             'format' => 'dd-MM-yyyy', | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['date_from' => new DateTime(), 'date_to' => new DateTime()]; | ||||
|     } | ||||
|  | ||||
|     // here, we create a simple string which will describe the action of | ||||
|     // the filter in the Response | ||||
|   | ||||
| @@ -36,6 +36,10 @@ class CountPerson implements ExportInterface | ||||
|     { | ||||
|         // this export does not add any form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|   | ||||
| @@ -18,6 +18,7 @@ These are alias conventions : | ||||
| |                                         | SocialIssue::class                      | acp.socialIssues                           | acpsocialissue                         | | ||||
| |                                         | User::class                             | acp.user                                   | acpuser                                | | ||||
| |                                         | AccompanyingPeriopStepHistory::class    | acp.stepHistories                          | acpstephistories                       | | ||||
| |                                         | AccompanyingPeriodInfo::class           | not existing (using custom WITH clause)    | acpinfo                                | | ||||
| | AccompanyingPeriodWork::class           |                                         |                                            | acpw                                   | | ||||
| |                                         | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations     | workeval                               | | ||||
| |                                         | User::class                             | acpw.referrers                             | acpwuser                               | | ||||
| @@ -28,6 +29,8 @@ These are alias conventions : | ||||
| |                                         | Person::class                           | acppart.person                             | partperson                             | | ||||
| | AccompanyingPeriodWorkEvaluation::class |                                         |                                            | workeval                               | | ||||
| |                                         | Evaluation::class                       | workeval.evaluation                        | eval                                   | | ||||
| | AccompanyingPeriodInfo::class           |                                         |                                            | acpinfo                                | | ||||
| |                                         | User::class                             | acpinfo.user                               | acpinfo_user                           | | ||||
| | Goal::class                             |                                         |                                            | goal                                   | | ||||
| |                                         | Result::class                           | goal.results                               | goalresult                             | | ||||
| | Person::class                           |                                         |                                            | person                                 | | ||||
|   | ||||
| @@ -2,6 +2,7 @@ parameters: | ||||
|     level: 5 | ||||
|     paths: | ||||
|         - src/ | ||||
|         - utils/ | ||||
|     tmpDir: .cache/ | ||||
|     reportUnmatchedIgnoredErrors: false | ||||
|     excludePaths: | ||||
|   | ||||
							
								
								
									
										29
									
								
								phpunit.rector.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								phpunit.rector.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd" | ||||
|          bootstrap="tests/app/vendor/autoload.php" | ||||
|          cacheResultFile=".cache/phpunit/test-results-rector" | ||||
|          executionOrder="depends,defects" | ||||
|          forceCoversAnnotation="true" | ||||
|          beStrictAboutCoversAnnotation="true" | ||||
|          beStrictAboutOutputDuringTests="true" | ||||
|          beStrictAboutTodoAnnotatedTests="true" | ||||
|          convertDeprecationsToExceptions="true" | ||||
|          failOnRisky="true" | ||||
|          failOnWarning="true" | ||||
|          verbose="true" | ||||
|          colors="true" | ||||
| > | ||||
|     <testsuites> | ||||
|         <testsuite name="default"> | ||||
|             <directory>utils/rector/tests</directory> | ||||
|         </testsuite> | ||||
|     </testsuites> | ||||
|  | ||||
|     <coverage cacheDirectory=".cache/phpunit/code-coverage-rector" | ||||
|               processUncoveredFiles="true"> | ||||
|         <include> | ||||
|             <directory suffix=".php">utils/rector/src</directory> | ||||
|         </include> | ||||
|     </coverage> | ||||
| </phpunit> | ||||
| @@ -24,6 +24,9 @@ return static function (RectorConfig $rectorConfig): void { | ||||
|         LevelSetList::UP_TO_PHP_74 | ||||
|     ]); | ||||
|  | ||||
|     // chill rules | ||||
|     $rectorConfig->rule(\Chill\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class); | ||||
|  | ||||
|     // skip some path... | ||||
|     $rectorConfig->skip([ | ||||
|         // make rector stuck for some files | ||||
|   | ||||
| @@ -18,11 +18,17 @@ use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface; | ||||
| use Chill\ActivityBundle\Repository\ActivityRepository; | ||||
| use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository; | ||||
| use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; | ||||
| use Chill\ActivityBundle\Repository\ActivityUserJobRepository; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| 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\UserRepositoryInterface; | ||||
| 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\Person; | ||||
| use Chill\PersonBundle\Privacy\PrivacyEvent; | ||||
| @@ -47,68 +53,26 @@ use function array_key_exists; | ||||
|  | ||||
| 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( | ||||
|         ActivityACLAwareRepositoryInterface $activityACLAwareRepository, | ||||
|         ActivityTypeRepositoryInterface $activityTypeRepository, | ||||
|         ActivityTypeCategoryRepository $activityTypeCategoryRepository, | ||||
|         PersonRepository $personRepository, | ||||
|         ThirdPartyRepository $thirdPartyRepository, | ||||
|         LocationRepository $locationRepository, | ||||
|         ActivityRepository $activityRepository, | ||||
|         AccompanyingPeriodRepository $accompanyingPeriodRepository, | ||||
|         EntityManagerInterface $entityManager, | ||||
|         EventDispatcherInterface $eventDispatcher, | ||||
|         LoggerInterface $logger, | ||||
|         SerializerInterface $serializer, | ||||
|         UserRepositoryInterface $userRepository, | ||||
|         CenterResolverManagerInterface $centerResolver, | ||||
|         TranslatorInterface $translator | ||||
|         private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository, | ||||
|         private readonly ActivityTypeRepositoryInterface $activityTypeRepository, | ||||
|         private readonly ActivityTypeCategoryRepository $activityTypeCategoryRepository, | ||||
|         private readonly PersonRepository $personRepository, | ||||
|         private readonly ThirdPartyRepository $thirdPartyRepository, | ||||
|         private readonly LocationRepository $locationRepository, | ||||
|         private readonly ActivityRepository $activityRepository, | ||||
|         private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, | ||||
|         private readonly EntityManagerInterface $entityManager, | ||||
|         private readonly EventDispatcherInterface $eventDispatcher, | ||||
|         private readonly LoggerInterface $logger, | ||||
|         private readonly SerializerInterface $serializer, | ||||
|         private readonly UserRepositoryInterface $userRepository, | ||||
|         private readonly CenterResolverManagerInterface $centerResolver, | ||||
|         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; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -289,14 +253,31 @@ final class ActivityController extends AbstractController | ||||
|     { | ||||
|         $view = null; | ||||
|         $activities = []; | ||||
|         // TODO: add pagination | ||||
|  | ||||
|         [$person, $accompanyingPeriod] = $this->getEntity($request); | ||||
|         $filter = $this->buildFilterOrder($person ?? $accompanyingPeriod); | ||||
|  | ||||
|         $filterArgs = [ | ||||
|             'my_activities' => $filter->getSingleCheckboxData('my_activities'), | ||||
|             'types' => $filter->getEntityChoiceData('activity_types'), | ||||
|             'jobs' => $filter->getEntityChoiceData('jobs'), | ||||
|             'before' => $filter->getDateRangeData('activity_date')['to'], | ||||
|             'after' => $filter->getDateRangeData('activity_date')['from'], | ||||
|         ]; | ||||
|  | ||||
|         if ($person instanceof Person) { | ||||
|             $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); | ||||
|             $count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE, $filterArgs); | ||||
|             $paginator = $this->paginatorFactory->create($count); | ||||
|             $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, [ | ||||
|                 'element_class' => Activity::class, | ||||
| @@ -308,10 +289,21 @@ final class ActivityController extends AbstractController | ||||
|         } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { | ||||
|             $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); | ||||
|  | ||||
|             $count = $this->activityACLAwareRepository->countByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, $filterArgs); | ||||
|             $paginator = $this->paginatorFactory->create($count); | ||||
|             $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'; | ||||
|         } else { | ||||
|             throw new \LogicException("Unsupported"); | ||||
|         } | ||||
|  | ||||
|         return $this->render( | ||||
| @@ -320,10 +312,40 @@ final class ActivityController extends AbstractController | ||||
|                 'activities' => $activities, | ||||
|                 'person' => $person, | ||||
|                 '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') | ||||
|             ->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()); | ||||
|                 } | ||||
|             ]) | ||||
|             ->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 | ||||
|     { | ||||
|         $view = null; | ||||
|   | ||||
| @@ -40,6 +40,10 @@ class ByActivityNumberAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // No form needed | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -52,6 +52,10 @@ class ByCreatorAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class BySocialActionAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class BySocialIssueAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class ByThirdpartyAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class CreatorScopeAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -29,14 +29,6 @@ class DateAggregator implements AggregatorInterface | ||||
|  | ||||
|     private const DEFAULT_CHOICE = 'year'; | ||||
|  | ||||
|     private TranslatorInterface $translator; | ||||
|  | ||||
|     public function __construct( | ||||
|         TranslatorInterface $translator | ||||
|     ) { | ||||
|         $this->translator = $translator; | ||||
|     } | ||||
|  | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
| @@ -84,9 +76,12 @@ class DateAggregator implements AggregatorInterface | ||||
|             'multiple' => false, | ||||
|             'expanded' => true, | ||||
|             'empty_data' => self::DEFAULT_CHOICE, | ||||
|             'data' => self::DEFAULT_CHOICE, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['frequency' => self::DEFAULT_CHOICE]; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class LocationTypeAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -60,6 +60,10 @@ class ActivityTypeAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form required for this aggregator | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): Closure | ||||
|     { | ||||
|   | ||||
| @@ -58,6 +58,10 @@ class ActivityUserAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // nothing to add | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, $values, $data): Closure | ||||
|     { | ||||
|   | ||||
| @@ -56,6 +56,10 @@ class ActivityUsersAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // nothing to add on the form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -55,6 +55,10 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI | ||||
|     { | ||||
|         // nothing to add in the form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -55,6 +55,10 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato | ||||
|     { | ||||
|         // nothing to add in the form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -110,6 +110,10 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali | ||||
|             ] | ||||
|         ); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -47,6 +47,10 @@ class SentReceivedAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // No form needed | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): callable | ||||
|     { | ||||
|   | ||||
| @@ -39,6 +39,10 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -40,6 +40,10 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac | ||||
|     { | ||||
|         // TODO: Implement buildForm() method. | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -39,6 +39,10 @@ class CountActivity implements ExportInterface, GroupedExportInterface | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -44,6 +44,10 @@ class ListActivity implements ListInterface, GroupedExportInterface | ||||
|     { | ||||
|         $this->helper->buildForm($builder); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|   | ||||
| @@ -40,6 +40,10 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface | ||||
|     { | ||||
|         // TODO: Implement buildForm() method. | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -40,6 +40,10 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac | ||||
|     { | ||||
|         // TODO: Implement buildForm() method. | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -35,6 +35,10 @@ class CountActivity implements ExportInterface, GroupedExportInterface | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|   | ||||
| @@ -88,6 +88,10 @@ class ListActivity implements ListInterface, GroupedExportInterface | ||||
|             ])], | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|   | ||||
| @@ -53,6 +53,10 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|   | ||||
| @@ -68,6 +68,10 @@ class ActivityTypeFilter implements FilterInterface | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -52,6 +52,10 @@ class ByCreatorFilter implements FilterInterface | ||||
|             'multiple' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -60,6 +60,10 @@ class BySocialActionFilter implements FilterInterface | ||||
|             'multiple' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -60,6 +60,10 @@ class BySocialIssueFilter implements FilterInterface | ||||
|             'multiple' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -68,9 +68,12 @@ class EmergencyFilter implements FilterInterface | ||||
|             'multiple' => false, | ||||
|             'expanded' => true, | ||||
|             'empty_data' => self::DEFAULT_CHOICE, | ||||
|             'data' => self::DEFAULT_CHOICE, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['accepted_emergency' => self::DEFAULT_CHOICE]; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -44,6 +44,10 @@ class HasNoActivityFilter implements FilterInterface | ||||
|     { | ||||
|         //no form needed | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -46,6 +46,10 @@ class LocationFilter implements FilterInterface | ||||
|             'label' => 'pick location', | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -65,6 +65,10 @@ class LocationTypeFilter implements FilterInterface | ||||
|             //'label' => false, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -69,9 +69,12 @@ class SentReceivedFilter implements FilterInterface | ||||
|             'multiple' => false, | ||||
|             'expanded' => true, | ||||
|             'empty_data' => self::DEFAULT_CHOICE, | ||||
|             'data' => self::DEFAULT_CHOICE, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['accepted_sentreceived' => self::DEFAULT_CHOICE]; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -61,6 +61,10 @@ class UserFilter implements FilterInterface | ||||
|             'label' => 'Creators', | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -71,6 +71,10 @@ class UserScopeFilter implements FilterInterface | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -80,11 +80,9 @@ class ActivityDateFilter implements FilterInterface | ||||
|         $builder | ||||
|             ->add('date_from', PickRollingDateType::class, [ | ||||
|                 'label' => 'Activities after this date', | ||||
|                 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), | ||||
|             ]) | ||||
|             ->add('date_to', PickRollingDateType::class, [ | ||||
|                 'label' => 'Activities before this date', | ||||
|                 'data' => new RollingDate(RollingDate::T_TODAY), | ||||
|             ]); | ||||
|  | ||||
|         $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { | ||||
| @@ -127,6 +125,10 @@ class ActivityDateFilter implements FilterInterface | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -78,6 +78,10 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter | ||||
|             ], | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -56,6 +56,10 @@ class ActivityUsersFilter implements FilterInterface | ||||
|             'label' => 'Users', | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -82,6 +82,10 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt | ||||
|             'expanded' => false, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -112,7 +112,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt | ||||
|     { | ||||
|         $builder->add('date_from', DateType::class, [ | ||||
|             'label' => 'Implied in an activity after this date', | ||||
|             'data' => new DateTime(), | ||||
|             'attr' => ['class' => 'datepicker'], | ||||
|             'widget' => 'single_text', | ||||
|             'format' => 'dd-MM-yyyy', | ||||
| @@ -120,7 +119,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt | ||||
|  | ||||
|         $builder->add('date_to', DateType::class, [ | ||||
|             'label' => 'Implied in an activity before this date', | ||||
|             'data' => new DateTime(), | ||||
|             'attr' => ['class' => 'datepicker'], | ||||
|             'widget' => 'single_text', | ||||
|             'format' => 'dd-MM-yyyy', | ||||
| @@ -130,7 +128,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt | ||||
|             'class' => ActivityReason::class, | ||||
|             'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()), | ||||
|             'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()), | ||||
|             'data' => $this->activityReasonRepository->findAll(), | ||||
|             'multiple' => true, | ||||
|             'expanded' => false, | ||||
|             'label' => 'Activity reasons for those activities', | ||||
| @@ -176,6 +173,10 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['date_from' => new DateTime(), 'date_to' => new DateTime(), 'reasons' => $this->activityReasonRepository->findAll()]; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -60,6 +60,10 @@ class UsersJobFilter implements FilterInterface | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -67,6 +67,10 @@ class UsersScopeFilter implements FilterInterface | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -18,67 +18,193 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\MainBundle\Entity\Location; | ||||
| use Chill\MainBundle\Entity\LocationType; | ||||
| use Chill\MainBundle\Entity\Scope; | ||||
| use Chill\MainBundle\Security\Authorization\AuthorizationHelper; | ||||
| use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface; | ||||
| 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\DBAL\Types\Types; | ||||
| use Doctrine\ORM\AbstractQuery; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\NonUniqueResultException; | ||||
| use Doctrine\ORM\NoResultException; | ||||
| use Doctrine\ORM\Query\Expr\Join; | ||||
| use Doctrine\ORM\Query\ResultSetMappingBuilder; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; | ||||
| use Symfony\Component\Security\Core\Role\Role; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\HttpFoundation\RequestStack; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| use function count; | ||||
| 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( | ||||
|         AuthorizationHelper $authorizationHelper, | ||||
|         CenterResolverDispatcherInterface $centerResolverDispatcher, | ||||
|         TokenStorageInterface $tokenStorage, | ||||
|         ActivityRepository $repository, | ||||
|         EntityManagerInterface $em, | ||||
|         Security $security | ||||
|         private AuthorizationHelperForCurrentUserInterface $authorizationHelper, | ||||
|         private CenterResolverManagerInterface $centerResolverManager, | ||||
|         private ActivityRepository $repository, | ||||
|         private EntityManagerInterface $em, | ||||
|         private 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(); | ||||
|         $center = $this->centerResolverDispatcher->resolveCenter($period); | ||||
|         $qb = $this->buildBaseQuery($filters); | ||||
|  | ||||
|         if (0 === count($orderBy)) { | ||||
|             $orderBy = ['date' => 'DESC']; | ||||
|         $qb | ||||
|             ->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 | ||||
|             ->getReachableCircles($user, $role, $center); | ||||
|         if (null !== $start) { | ||||
|             $qb->setFirstResult($start); | ||||
|         } | ||||
|         if (null !== $limit) { | ||||
|             $qb->setMaxResults($limit); | ||||
|         } | ||||
|  | ||||
|         return $this->em->getRepository(Activity::class) | ||||
|             ->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy); | ||||
|         return $qb->getQuery()->getResult(); | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|     { | ||||
|         $rsm = new ResultSetMappingBuilder($this->em); | ||||
| @@ -159,25 +285,73 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte | ||||
|         return $nq->getResult(AbstractQuery::HYDRATE_ARRAY); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param array $orderBy | ||||
|      * | ||||
|      * @return Activity[]|array | ||||
|      */ | ||||
|     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 = [], array $filters = []): array | ||||
|     { | ||||
|         $user = $this->security->getUser(); | ||||
|         $center = $this->centerResolverDispatcher->resolveCenter($person); | ||||
|         $qb = $this->buildBaseQuery($filters); | ||||
|  | ||||
|         if (0 === count($orderBy)) { | ||||
|             $orderBy = ['date' => 'DESC']; | ||||
|         $qb = $this->filterBaseQueryByPerson($qb, $person, $role); | ||||
|  | ||||
|         foreach ($orderBy as $field => $direction) { | ||||
|             $qb->addOrderBy('a.' . $field, $direction); | ||||
|         } | ||||
|  | ||||
|         $reachableScopes = $this->authorizationHelper | ||||
|             ->getReachableCircles($user, $role, $center); | ||||
|         if (null !== $start) { | ||||
|             $qb->setFirstResult($start); | ||||
|         } | ||||
|         if (null !== $limit) { | ||||
|             $qb->setMaxResults($limit); | ||||
|         } | ||||
|  | ||||
|         return $this->em->getRepository(Activity::class) | ||||
|             ->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start); | ||||
|         return $qb->getQuery()->getResult(); | ||||
|     } | ||||
|  | ||||
|     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 | ||||
| @@ -226,7 +400,6 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte | ||||
|  | ||||
|         // acls: | ||||
|         $reachableCenters = $this->authorizationHelper->getReachableCenters( | ||||
|             $this->tokenStorage->getToken()->getUser(), | ||||
|             ActivityVoter::SEE | ||||
|         ); | ||||
|  | ||||
| @@ -251,7 +424,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte | ||||
|                 continue; | ||||
|             } | ||||
|             // 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 | ||||
|             $reachablesScopesId = array_map( | ||||
|                 static fn (Scope $scope) => $scope->getId(), | ||||
|   | ||||
| @@ -11,15 +11,32 @@ declare(strict_types=1); | ||||
|  | ||||
| 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\Person; | ||||
|  | ||||
| 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). | ||||
| @@ -31,7 +48,28 @@ interface ActivityACLAwareRepositoryInterface | ||||
|     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; | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,198 @@ | ||||
| <?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\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; | ||||
| use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider; | ||||
| use Chill\DocStoreBundle\Entity\PersonDocument; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQuery; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; | ||||
| use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider; | ||||
| use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface; | ||||
| use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter; | ||||
| use Chill\MainBundle\Entity\Scope; | ||||
| use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; | ||||
| use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter; | ||||
| use DateTimeImmutable; | ||||
| use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\Mapping\MappingException; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\HttpKernel\HttpCache\Store; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| final readonly class ActivityDocumentACLAwareRepository implements ActivityDocumentACLAwareRepositoryInterface | ||||
| { | ||||
|     public function __construct( | ||||
|         private EntityManagerInterface $em, | ||||
|         private CenterResolverManagerInterface $centerResolverManager, | ||||
|         private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, | ||||
|         private Security $security | ||||
|     ) { | ||||
|     } | ||||
|  | ||||
|     public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface | ||||
|     { | ||||
|         $query = $this->buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); | ||||
|  | ||||
|         return $this->addFetchQueryByPersonACL($query, $person); | ||||
|     } | ||||
|  | ||||
|     public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery | ||||
|     { | ||||
|         $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); | ||||
|         $activityMetadata = $this->em->getClassMetadata(Activity::class); | ||||
|  | ||||
|         $query = new FetchQuery( | ||||
|             PersonActivityGenericDocProvider::KEY, | ||||
|             sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), | ||||
|             sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), | ||||
|             sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) | ||||
|         ); | ||||
|  | ||||
|         $query->addJoinClause( | ||||
|             'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' | ||||
|         ); | ||||
|  | ||||
|         $query->addJoinClause( | ||||
|             'JOIN public.activity activity ON activity.id = activity_doc.activity_id' | ||||
|         ); | ||||
|  | ||||
|         $query->addWhereClause( | ||||
|             sprintf('activity.%s = ?', $activityMetadata->getSingleAssociationJoinColumnName('person')), | ||||
|             [$person->getId()], | ||||
|             [Types::INTEGER] | ||||
|         ); | ||||
|  | ||||
|         return $this->addWhereClauses($query, $startDate, $endDate, $content); | ||||
|     } | ||||
|  | ||||
|     public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery | ||||
|     { | ||||
|         $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); | ||||
|         $activityMetadata = $this->em->getClassMetadata(Activity::class); | ||||
|  | ||||
|         $query = new FetchQuery( | ||||
|             AccompanyingPeriodActivityGenericDocProvider::KEY, | ||||
|             sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), | ||||
|             sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')), | ||||
|             sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName()) | ||||
|         ); | ||||
|  | ||||
|         $query->addJoinClause( | ||||
|             'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id' | ||||
|         ); | ||||
|  | ||||
|         $query->addJoinClause( | ||||
|             'JOIN public.activity activity ON activity.id = activity_doc.activity_id' | ||||
|         ); | ||||
|  | ||||
|         // add documents of activities from parcours context | ||||
|         $or = []; | ||||
|         $orParams = []; | ||||
|         $orTypes = []; | ||||
|         foreach ($person->getAccompanyingPeriodParticipations() as $participation) { | ||||
|             if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             $or[] = sprintf( | ||||
|                 '(activity.%s = ? AND stored_obj.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))', | ||||
|                 $activityMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'), | ||||
|                 $storedObjectMetadata->getColumnName('createdAt') | ||||
|             ); | ||||
|             $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), | ||||
|                 DateTimeImmutable::createFromInterface($participation->getStartDate()), | ||||
|                 null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())]; | ||||
|             $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE]; | ||||
|         } | ||||
|  | ||||
|         if ([] === $or) { | ||||
|             $query->addWhereClause('TRUE = FALSE'); | ||||
|  | ||||
|             return $query; | ||||
|         } | ||||
|  | ||||
|         $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes); | ||||
|  | ||||
|         return $this->addWhereClauses($query, $startDate, $endDate, $content); | ||||
|     } | ||||
|  | ||||
|     private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery | ||||
|     { | ||||
|         $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); | ||||
|  | ||||
|         if (null !== $startDate) { | ||||
|             $query->addWhereClause( | ||||
|                 sprintf('stored_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), | ||||
|                 [$startDate], | ||||
|                 [Types::DATE_IMMUTABLE] | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         if (null !== $endDate) { | ||||
|             $query->addWhereClause( | ||||
|                 sprintf('stored_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), | ||||
|                 [$endDate], | ||||
|                 [Types::DATE_IMMUTABLE] | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         if (null !== $content and '' !== $content) { | ||||
|             $query->addWhereClause( | ||||
|                 'stored_obj.title ilike ?', | ||||
|                 ['%' . $content . '%'], | ||||
|                 [Types::STRING] | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return $query; | ||||
|     } | ||||
|  | ||||
|     private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery | ||||
|     { | ||||
|         $activityMetadata = $this->em->getClassMetadata(Activity::class); | ||||
|  | ||||
|         $reachableScopes = []; | ||||
|  | ||||
|         foreach ($this->centerResolverManager->resolveCenters($person) as $center) { | ||||
|             $reachableScopes = [ | ||||
|                 ...$reachableScopes, | ||||
|                 ...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center) | ||||
|             ]; | ||||
|         } | ||||
|  | ||||
|         if ([] === $reachableScopes) { | ||||
|             $fetchQuery->addWhereClause('FALSE = TRUE'); | ||||
|  | ||||
|             return $fetchQuery; | ||||
|         } | ||||
|  | ||||
|         $fetchQuery->addWhereClause( | ||||
|             sprintf( | ||||
|                 'activity.%s IN (%s)', | ||||
|                 $activityMetadata->getSingleAssociationJoinColumnName('scope'), | ||||
|                 implode(', ', array_fill(0, count($reachableScopes), '?')) | ||||
|             ), | ||||
|             array_map(static fn (Scope $s) => $s->getId(), $reachableScopes), | ||||
|             array_fill(0, count($reachableScopes), Types::INTEGER) | ||||
|         ); | ||||
|  | ||||
|         return $fetchQuery; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQuery; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use DateTimeImmutable; | ||||
|  | ||||
| /** | ||||
|  * Gives queries usable for fetching documents, with ACL aware | ||||
|  */ | ||||
| interface ActivityDocumentACLAwareRepositoryInterface | ||||
| { | ||||
|     /** | ||||
|      * Return a fetch query for querying document's activities for a person | ||||
|      * | ||||
|      * This method must check the rights to see a document: the user must be allowed to see the given activities | ||||
|      */ | ||||
|     public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface; | ||||
|  | ||||
|     /** | ||||
|      * Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person | ||||
|      * | ||||
|      * This method must check the rights to see a document: the user must be allowed to see the given accompanying periods | ||||
|      */ | ||||
|     public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery; | ||||
| } | ||||
| @@ -11,9 +11,13 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Entity\ActivityType; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\EntityRepository; | ||||
| use Doctrine\ORM\Query\Expr\Join; | ||||
|  | ||||
| final class ActivityTypeRepository implements ActivityTypeRepositoryInterface | ||||
| { | ||||
|   | ||||
| @@ -12,12 +12,14 @@ declare(strict_types=1); | ||||
| namespace Chill\ActivityBundle\Repository; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\ActivityType; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Doctrine\Persistence\ObjectRepository; | ||||
|  | ||||
| interface ActivityTypeRepositoryInterface extends ObjectRepository | ||||
| { | ||||
|     /** | ||||
|      * @return array|ActivityType[] | ||||
|      * @return array<ActivityType> | ||||
|      */ | ||||
|     public function findAllActive(): array; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| // Access to Bootstrap variables and mixins | ||||
| @import '~ChillMainAssets/module/bootstrap/shared'; | ||||
| @import '~ChillPersonAssets/chill/scss/mixins.scss'; | ||||
| @import 'bootstrap/scss/_badge.scss'; | ||||
|  | ||||
| //// ACTIVITY CREATION | ||||
| // first step: select type page | ||||
| @@ -96,3 +98,25 @@ li.document-list-item { | ||||
|     justify-content: space-between; | ||||
|     margin-bottom: 0.3rem; | ||||
| } | ||||
|  | ||||
| .badge-activity-type { | ||||
|     display: inline-block; | ||||
|     background-color: #f3f3f3; | ||||
|  | ||||
|     .title_label { | ||||
|         @include chill_badge(#9acd32); | ||||
|     } | ||||
|  | ||||
|     .title_action { | ||||
|         padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); | ||||
|         margin-right: 1rem; | ||||
|  | ||||
|         font-size: var(--bs-badge-font-size); | ||||
|         font-weight: var(--bs-badge-font-weight); | ||||
|         line-height: 1; | ||||
|         color: var(--bs-badge-color); | ||||
|         text-align: center; | ||||
|         white-space: nowrap; | ||||
|         vertical-align: baseline; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -80,12 +80,15 @@ | ||||
|  | ||||
| <div class="context-{{ context }}"> | ||||
|  | ||||
|     {{ filter|chill_render_filter_order_helper }} | ||||
|  | ||||
|     {% if activities|length == 0 %} | ||||
|         <p class="chill-no-data-statement"> | ||||
|             {{ "There isn't any activities."|trans }} | ||||
|         </p> | ||||
|  | ||||
|     {% else %} | ||||
|  | ||||
|         <div class="flex-table activity-list"> | ||||
|             {% for activity in activities %} | ||||
|                 {% include 'ChillActivityBundle:Activity:_list_item.html.twig' with { | ||||
| @@ -96,4 +99,6 @@ | ||||
|         </div> | ||||
|     {% endif %} | ||||
|  | ||||
|     {{ chill_pagination(paginator) }} | ||||
|  | ||||
| </div> | ||||
|   | ||||
| @@ -0,0 +1,83 @@ | ||||
| {% import "@ChillDocStore/Macro/macro.html.twig" as m %} | ||||
| {% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %} | ||||
| {% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %} | ||||
|  | ||||
| {% set person_id = null %} | ||||
| {% if activity.person %} | ||||
|     {% set person_id = activity.person.id %} | ||||
| {% endif %} | ||||
|  | ||||
| {% set accompanying_course_id = null %} | ||||
| {% if activity.accompanyingPeriod %} | ||||
|     {% set accompanying_course_id = activity.accompanyingPeriod.id %} | ||||
| {% endif %} | ||||
|  | ||||
| <div class="item-bloc activity-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}"> | ||||
|     <div class="item-row"> | ||||
|         <div class="item-col" style="width: unset"> | ||||
|             {% if document.isPending %} | ||||
|                 <div class="badge text-bg-info" data-docgen-is-pending="{{ document.id }}">{{ 'docgen.Doc generation is pending'|trans }}</div> | ||||
|             {% elseif document.isFailure %} | ||||
|                 <div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div> | ||||
|             {% endif %} | ||||
|  | ||||
|             <div> | ||||
|                 {% if activity.accompanyingPeriod is not null and context == 'person' %} | ||||
|                     <span class="badge bg-primary"> | ||||
|                         <i class="fa fa-random"></i> {{ activity.accompanyingPeriod.id }} | ||||
|                     </span>  | ||||
|                 {% endif %} | ||||
|                 <div class="badge-activity-type"> | ||||
|                     <span class="title_label"></span> | ||||
|                     <span class="title_action"> | ||||
|                     {{ activity.type.name | localize_translatable_string }} | ||||
|                         {% if activity.emergency %} | ||||
|                             <span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span> | ||||
|                         {% endif %} | ||||
|                     </span> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="denomination h2"> | ||||
|                 {{ document.title|chill_print_or_message("No title") }} | ||||
|             </div> | ||||
|             {% if document.hasTemplate %} | ||||
|                 <div> | ||||
|                     <p>{{ document.template.name|localize_translatable_string }}</p> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|  | ||||
|         <div class="item-col"> | ||||
|             <div class="container"> | ||||
|                 <div class="dates row text-end"> | ||||
|                     <span>{{ document.createdAt|format_date('short') }}</span> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|  | ||||
|     <div class="item-row separator"> | ||||
|         <div class="item-col item-meta"> | ||||
|             {{ mmm.createdBy(document) }} | ||||
|         </div> | ||||
|         <ul class="item-col record_actions flex-shrink-1"> | ||||
|             {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %} | ||||
|                 <li> | ||||
|                     {{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false})  }} | ||||
|                 </li> | ||||
|             {% endif %} | ||||
|             {% if is_granted('CHILL_ACTIVITY_SEE', activity)%} | ||||
|                 <li> | ||||
|                     <a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-show"></a> | ||||
|                 </li> | ||||
|             {% endif %} | ||||
|             {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %} | ||||
|                 <li> | ||||
|                     <a href="{{ chill_path_add_return_path('chill_activity_activity_edit', {'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-edit"></a> | ||||
|                 </li> | ||||
|             {% endif %} | ||||
|         </ul> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
| @@ -0,0 +1,114 @@ | ||||
| <?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\Service\GenericDoc\Providers; | ||||
|  | ||||
| use Chill\ActivityBundle\Entity\Activity; | ||||
| use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQuery; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; | ||||
| use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface; | ||||
| use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; | ||||
| use DateTimeImmutable; | ||||
| use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\Mapping\MappingException; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface | ||||
| { | ||||
|     public const KEY = 'accompanying_period_activity_document'; | ||||
|  | ||||
|     public function __construct( | ||||
|         private EntityManagerInterface $em, | ||||
|         private Security $security, | ||||
|         private ActivityDocumentACLAwareRepositoryInterface $activityDocumentACLAwareRepository, | ||||
|     ) { | ||||
|     } | ||||
|  | ||||
|     public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface | ||||
|     { | ||||
|         $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class); | ||||
|         $activityMetadata = $this->em->getClassMetadata(Activity::class); | ||||
|  | ||||
|         $query = new FetchQuery( | ||||
|             self::KEY, | ||||
|             sprintf("jsonb_build_object('id', doc_obj.%s, 'activity_id', activity.%s)", $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()), | ||||
|             'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'), | ||||
|             $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj' | ||||
|         ); | ||||
|  | ||||
|         $query->addJoinClause( | ||||
|             'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id' | ||||
|         ); | ||||
|  | ||||
|         $query->addJoinClause( | ||||
|             'JOIN public.activity activity ON activity.id = activity_doc.activity_id' | ||||
|         ); | ||||
|  | ||||
|         $query->addWhereClause( | ||||
|             'activity.accompanyingperiod_id = ?', | ||||
|             [$accompanyingPeriod->getId()], | ||||
|             [Types::INTEGER] | ||||
|         ); | ||||
|  | ||||
|         if (null !== $startDate) { | ||||
|             $query->addWhereClause( | ||||
|                 sprintf('doc_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')), | ||||
|                 [$startDate], | ||||
|                 [Types::DATE_IMMUTABLE] | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         if (null !== $endDate) { | ||||
|             $query->addWhereClause( | ||||
|                 sprintf('doc_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')), | ||||
|                 [$endDate], | ||||
|                 [Types::DATE_IMMUTABLE] | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         if (null !== $content) { | ||||
|             $query->addWhereClause( | ||||
|                 'doc_obj.title ilike ?', | ||||
|                 ['%' . $content . '%'], | ||||
|                 [Types::STRING] | ||||
|             ); | ||||
|         } | ||||
|  | ||||
|         return $query; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param AccompanyingPeriod $accompanyingPeriod | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool | ||||
|     { | ||||
|         return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod); | ||||
|     } | ||||
|  | ||||
|     public function isAllowedForPerson(Person $person): bool | ||||
|     { | ||||
|         return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person); | ||||
|     } | ||||
|  | ||||
|     public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface | ||||
|     { | ||||
|         return $this->activityDocumentACLAwareRepository | ||||
|             ->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,53 @@ | ||||
| <?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\Service\GenericDoc\Providers; | ||||
|  | ||||
| use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepository; | ||||
| use Chill\ActivityBundle\Repository\ActivityDocumentACLAwareRepositoryInterface; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface; | ||||
| use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface; | ||||
| use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface; | ||||
| use Chill\PersonBundle\Entity\Person; | ||||
| use DateTimeImmutable; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| final readonly class PersonActivityGenericDocProvider implements GenericDocForPersonProviderInterface | ||||
| { | ||||
|     public const KEY = 'person_activity_document'; | ||||
|  | ||||
|     public function __construct( | ||||
|         private Security                                    $security, | ||||
|         private ActivityDocumentACLAwareRepositoryInterface $personActivityDocumentACLAwareRepository, | ||||
|     ) { | ||||
|     } | ||||
|  | ||||
|     public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface | ||||
|     { | ||||
|         return $this->personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext( | ||||
|             $person, | ||||
|             $startDate, | ||||
|             $endDate, | ||||
|             $content | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param Person $person | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isAllowedForPerson(Person $person): bool | ||||
|     { | ||||
|         return $this->security->isGranted(ActivityVoter::SEE, $person); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,52 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\ActivityBundle\Service\GenericDoc\Renderers; | ||||
|  | ||||
| use Chill\ActivityBundle\Repository\ActivityRepository; | ||||
| use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider; | ||||
| use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider; | ||||
| use Chill\DocStoreBundle\GenericDoc\GenericDocDTO; | ||||
| use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface; | ||||
| use Chill\DocStoreBundle\Repository\StoredObjectRepository; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
|  | ||||
| final class AccompanyingPeriodActivityGenericDocRenderer implements GenericDocRendererInterface | ||||
| { | ||||
|     private StoredObjectRepository $objectRepository; | ||||
|  | ||||
|     private ActivityRepository $activityRepository; | ||||
|  | ||||
|     public function __construct(StoredObjectRepository $storedObjectRepository, ActivityRepository $activityRepository) | ||||
|     { | ||||
|         $this->objectRepository = $storedObjectRepository; | ||||
|         $this->activityRepository = $activityRepository; | ||||
|     } | ||||
|  | ||||
|     public function supports(GenericDocDTO $genericDocDTO, $options = []): bool | ||||
|     { | ||||
|         return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY || $genericDocDTO->key === PersonActivityGenericDocProvider::KEY; | ||||
|     } | ||||
|  | ||||
|     public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string | ||||
|     { | ||||
|         return '@ChillActivity/GenericDoc/activity_document.html.twig'; | ||||
|     } | ||||
|  | ||||
|     public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array | ||||
|     { | ||||
|         return [ | ||||
|             'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']), | ||||
|             'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']), | ||||
|             'context' => $genericDocDTO->getContext(), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -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')]]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,126 @@ | ||||
| <?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\Repository\ActivityDocumentACLAwareRepository; | ||||
| use Chill\ActivityBundle\Security\Authorization\ActivityVoter; | ||||
| use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder; | ||||
| use Chill\MainBundle\Entity\Scope; | ||||
| 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 phpseclib3\Math\BinaryField; | ||||
| use Prophecy\Argument; | ||||
| use Prophecy\PhpUnit\ProphecyTrait; | ||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase | ||||
| { | ||||
|     use ProphecyTrait; | ||||
|  | ||||
|     private EntityManagerInterface $entityManager; | ||||
|  | ||||
|     private CenterResolverManagerInterface $centerResolverManager; | ||||
|  | ||||
|     private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser; | ||||
|  | ||||
|     private Security $security; | ||||
|  | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|         $this->entityManager = self::$container->get(EntityManagerInterface::class); | ||||
|         $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class); | ||||
|         $this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class); | ||||
|         $this->security = self::$container->get(Security::class); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @dataProvider provideDataForPerson | ||||
|      */ | ||||
|     public function testBuildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, array $reachableScopes, bool $_unused, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void | ||||
|     { | ||||
|         $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); | ||||
|         $authorizationHelper->getReachableScopes(ActivityVoter::SEE, Argument::any()) | ||||
|             ->willReturn($reachableScopes); | ||||
|  | ||||
|         $repository = new ActivityDocumentACLAwareRepository( | ||||
|             $this->entityManager, | ||||
|             $this->centerResolverManager, | ||||
|             $authorizationHelper->reveal(), | ||||
|             $this->security | ||||
|         ); | ||||
|  | ||||
|         $query = $repository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content); | ||||
|         ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); | ||||
|  | ||||
|         $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); | ||||
|  | ||||
|         self::assertIsInt($nb); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @dataProvider provideDataForPerson | ||||
|      */ | ||||
|     public function testBuildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, array $_unused, bool $canSeePeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void | ||||
|     { | ||||
|         $security = $this->prophesize(Security::class); | ||||
|         $security->isGranted(ActivityVoter::SEE, Argument::type(AccompanyingPeriod::class)) | ||||
|             ->willReturn($canSeePeriod); | ||||
|  | ||||
|         $repository = new ActivityDocumentACLAwareRepository( | ||||
|             $this->entityManager, | ||||
|             $this->centerResolverManager, | ||||
|             $this->authorizationHelperForCurrentUser, | ||||
|             $security->reveal() | ||||
|         ); | ||||
|  | ||||
|         $query = $repository->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content); | ||||
|  | ||||
|         ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query); | ||||
|  | ||||
|         $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types); | ||||
|  | ||||
|         self::assertIsInt($nb); | ||||
|     } | ||||
|  | ||||
|     public function provideDataForPerson(): iterable | ||||
|     { | ||||
|         $this->setUp(); | ||||
|  | ||||
|         if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ") | ||||
|             ->setMaxResults(1) | ||||
|             ->getSingleResult()) { | ||||
|             throw new \RuntimeException("no person in dtabase"); | ||||
|         } | ||||
|  | ||||
|         if ([] === $scopes = $this->entityManager->createQuery("SELECT s FROM " . Scope::class . " s ")->setMaxResults(5)->getResult()) { | ||||
|             throw new \RuntimeException("no scopes in database"); | ||||
|         } | ||||
|  | ||||
|         yield [$person, [], true, null, null, null]; | ||||
|         yield [$person, $scopes, true, null, null, null]; | ||||
|         yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), null, null]; | ||||
|         yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), null]; | ||||
|         yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; | ||||
|         yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"]; | ||||
|         yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"]; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -161,6 +161,7 @@ class TimelineActivityProvider implements TimelineProviderInterface | ||||
|  | ||||
|         // loop on reachable scopes | ||||
|         foreach ($reachableScopes as $scope) { | ||||
|             /** @phpstan-ignore-next-line  */ | ||||
|             if (in_array($scope->getId(), $scopes_ids, true)) { | ||||
|                 continue; | ||||
|             } | ||||
|   | ||||
| @@ -38,3 +38,6 @@ services: | ||||
|  | ||||
|     Chill\ActivityBundle\Service\EntityInfo\: | ||||
|         resource: '../Service/EntityInfo/' | ||||
|  | ||||
|     Chill\ActivityBundle\Service\GenericDoc\: | ||||
|         resource: '../Service/GenericDoc/' | ||||
|   | ||||
| @@ -83,12 +83,23 @@ Third persons: Tiers non-pro. | ||||
| Others persons: Usagers | ||||
| Third parties: Tiers professionnels | ||||
| Users concerned: T(M)S | ||||
|  | ||||
| activity: | ||||
|     date: Date de l'échange | ||||
|     Insert a document: Insérer un document | ||||
|     Remove a document: Supprimer le document | ||||
|     comment: Commentaire | ||||
| 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é | ||||
|     By: Filtrer par | ||||
|     Search: Chercher dans la liste | ||||
|     By date: Filtrer par date | ||||
|  | ||||
| #timeline | ||||
| '%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"' | ||||
|  | ||||
| @@ -372,3 +383,8 @@ export: | ||||
|                 is sent: envoyé | ||||
|                 is received: reçu | ||||
|                 Group activity by sentreceived: Grouper les échanges par envoyé / reçu | ||||
|  | ||||
| generic_doc: | ||||
|     filter: | ||||
|         keys: | ||||
|             accompanying_period_activity_document: Document des échanges des parcours | ||||
|   | ||||
| @@ -50,6 +50,10 @@ class ByActivityTypeAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // No form needed | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class ByUserJobAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // nothing to add in the form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -57,6 +57,10 @@ class ByUserScopeAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // nothing to add in the form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|   | ||||
| @@ -34,6 +34,10 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -34,6 +34,10 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -73,6 +73,10 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes() | ||||
|     { | ||||
|   | ||||
| @@ -34,6 +34,10 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac | ||||
|     public function buildForm(FormBuilderInterface $builder) | ||||
|     { | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|   | ||||
| @@ -76,6 +76,10 @@ class ByActivityTypeFilter implements FilterInterface | ||||
|                 }, | ||||
|             ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -72,11 +72,9 @@ class ByDateFilter implements FilterInterface | ||||
|         $builder | ||||
|             ->add('date_from', PickRollingDateType::class, [ | ||||
|                 'label' => 'export.filter.Aside activities after this date', | ||||
|                 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), | ||||
|             ]) | ||||
|             ->add('date_to', PickRollingDateType::class, [ | ||||
|                 'label' => 'export.filter.Aside activities before this date', | ||||
|                 'data' => new RollingDate(RollingDate::T_TODAY), | ||||
|             ]); | ||||
|  | ||||
|         $builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) { | ||||
| @@ -119,6 +117,10 @@ class ByDateFilter implements FilterInterface | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -53,6 +53,10 @@ class ByUserFilter implements FilterInterface | ||||
|             'label' => 'Creators', | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string'): array | ||||
|     { | ||||
|   | ||||
| @@ -60,6 +60,10 @@ class ByUserJobFilter implements FilterInterface | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -67,6 +67,10 @@ class ByUserScopeFilter implements FilterInterface | ||||
|             'expanded' => true, | ||||
|         ]); | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function describeAction($data, $format = 'string') | ||||
|     { | ||||
|   | ||||
| @@ -176,11 +176,12 @@ export: | ||||
|         agent_id: Utilisateur | ||||
|         creator_id: Créateur | ||||
|         main_scope: Service principal de l'utilisateur | ||||
|         main_center: Centre principal de l'utilisteur | ||||
|         main_center: Centre principal de l'utilisateur | ||||
|         aside_activity_type: Catégorie d'activité annexe | ||||
|         date: Date | ||||
|         duration: Durée | ||||
|         note: Note | ||||
|         id: Identifiant | ||||
|  | ||||
|     Exports of aside activities: Exports des activités annexes | ||||
|     Count aside activities: Nombre d'activités annexes | ||||
|   | ||||
| @@ -18,9 +18,12 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\CalendarBundle\Command; | ||||
|  | ||||
| use Chill\CalendarBundle\Exception\UserAbsenceSyncException; | ||||
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\EventsOnUserSubscriptionCreator; | ||||
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser; | ||||
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSGraphUserRepository; | ||||
| use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync; | ||||
| use Chill\MainBundle\Repository\UserRepositoryInterface; | ||||
| use DateInterval; | ||||
| use DateTimeImmutable; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| @@ -30,32 +33,17 @@ use Symfony\Component\Console\Input\InputInterface; | ||||
| use Symfony\Component\Console\Input\InputOption; | ||||
| use Symfony\Component\Console\Output\OutputInterface; | ||||
|  | ||||
| class MapAndSubscribeUserCalendarCommand extends Command | ||||
| final class MapAndSubscribeUserCalendarCommand extends Command | ||||
| { | ||||
|     private EntityManagerInterface $em; | ||||
|  | ||||
|     private EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator; | ||||
|  | ||||
|     private LoggerInterface $logger; | ||||
|  | ||||
|     private MapCalendarToUser $mapCalendarToUser; | ||||
|  | ||||
|     private MSGraphUserRepository $userRepository; | ||||
|  | ||||
|     public function __construct( | ||||
|         EntityManagerInterface $em, | ||||
|         EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator, | ||||
|         LoggerInterface $logger, | ||||
|         MapCalendarToUser $mapCalendarToUser, | ||||
|         MSGraphUserRepository $userRepository | ||||
|         private readonly EntityManagerInterface $em, | ||||
|         private readonly EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator, | ||||
|         private readonly LoggerInterface $logger, | ||||
|         private readonly MapCalendarToUser $mapCalendarToUser, | ||||
|         private readonly UserRepositoryInterface $userRepository, | ||||
|         private readonly MSUserAbsenceSync $userAbsenceSync, | ||||
|     ) { | ||||
|         parent::__construct('chill:calendar:msgraph-user-map-subscribe'); | ||||
|  | ||||
|         $this->em = $em; | ||||
|         $this->eventsOnUserSubscriptionCreator = $eventsOnUserSubscriptionCreator; | ||||
|         $this->logger = $logger; | ||||
|         $this->mapCalendarToUser = $mapCalendarToUser; | ||||
|         $this->userRepository = $userRepository; | ||||
|     } | ||||
|  | ||||
|     public function execute(InputInterface $input, OutputInterface $output): int | ||||
| @@ -67,83 +55,109 @@ class MapAndSubscribeUserCalendarCommand extends Command | ||||
|         /** @var DateInterval $interval the interval before the end of the expiration */ | ||||
|         $interval = new DateInterval('P1D'); | ||||
|         $expiration = (new DateTimeImmutable('now'))->add(new DateInterval($input->getOption('subscription-duration'))); | ||||
|         $total = $this->userRepository->countByMostOldSubscriptionOrWithoutSubscriptionOrData($interval); | ||||
|         $users = $this->userRepository->findAllAsArray('fr'); | ||||
|         $created = 0; | ||||
|         $renewed = 0; | ||||
|  | ||||
|         $this->logger->info(self::class . ' the number of user to get - renew', [ | ||||
|             'total' => $total, | ||||
|         $this->logger->info(self::class . ' start user to get - renew', [ | ||||
|             'expiration' => $expiration->format(DateTimeImmutable::ATOM), | ||||
|         ]); | ||||
|  | ||||
|         while ($offset < $total) { | ||||
|             $users = $this->userRepository->findByMostOldSubscriptionOrWithoutSubscriptionOrData( | ||||
|                 $interval, | ||||
|                 $limit, | ||||
|                 $offset | ||||
|             ); | ||||
|         foreach ($users as $u) { | ||||
|             ++$offset; | ||||
|  | ||||
|             foreach ($users as $user) { | ||||
|                 if (!$this->mapCalendarToUser->hasUserId($user)) { | ||||
|                     $this->mapCalendarToUser->writeMetadata($user); | ||||
|                 } | ||||
|  | ||||
|                 if ($this->mapCalendarToUser->hasUserId($user)) { | ||||
|                     // we first try to renew an existing subscription, if any. | ||||
|                     // if not, or if it fails, we try to create a new one | ||||
|                     if ($this->mapCalendarToUser->hasActiveSubscription($user)) { | ||||
|                         $this->logger->debug(self::class . ' renew a subscription for', [ | ||||
|                             'userId' => $user->getId(), | ||||
|                             'username' => $user->getUsernameCanonical(), | ||||
|                         ]); | ||||
|  | ||||
|                         ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] | ||||
|                             = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration); | ||||
|                         $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); | ||||
|  | ||||
|                         if (0 !== $expirationTs) { | ||||
|                             ++$renewed; | ||||
|                         } else { | ||||
|                             $this->logger->warning(self::class . ' could not renew subscription for a user', [ | ||||
|                                 'userId' => $user->getId(), | ||||
|                                 'username' => $user->getUsernameCanonical(), | ||||
|                             ]); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     if (!$this->mapCalendarToUser->hasActiveSubscription($user)) { | ||||
|                         $this->logger->debug(self::class . ' create a subscription for', [ | ||||
|                             'userId' => $user->getId(), | ||||
|                             'username' => $user->getUsernameCanonical(), | ||||
|                         ]); | ||||
|  | ||||
|                         ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] | ||||
|                             = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration); | ||||
|                         $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); | ||||
|  | ||||
|                         if (0 !== $expirationTs) { | ||||
|                             ++$created; | ||||
|                         } else { | ||||
|                             $this->logger->warning(self::class . ' could not create subscription for a user', [ | ||||
|                                 'userId' => $user->getId(), | ||||
|                                 'username' => $user->getUsernameCanonical(), | ||||
|                             ]); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 ++$offset; | ||||
|             if (false === $u['enabled']) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             $this->em->flush(); | ||||
|             $this->em->clear(); | ||||
|             $user = $this->userRepository->find($u['id']); | ||||
|  | ||||
|             if (null === $user) { | ||||
|                 $this->logger->error("could not find user by id", ['uid' => $u['id']]); | ||||
|                 $output->writeln("could not find user by id : " . $u['id']); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if (!$this->mapCalendarToUser->hasUserId($user)) { | ||||
|                 $user = $this->mapCalendarToUser->writeMetadata($user); | ||||
|  | ||||
|                 // if user still does not have userid, continue | ||||
|                 if (!$this->mapCalendarToUser->hasUserId($user)) { | ||||
|                     $this->logger->warning("user does not have a counterpart on ms api", ['userId' => $user->getId(), 'email' => $user->getEmail()]); | ||||
|                     $output->writeln(sprintf("giving up for user with email %s and id %s", $user->getEmail(), $user->getId())); | ||||
|  | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // sync user absence | ||||
|             try { | ||||
|                 $this->userAbsenceSync->syncUserAbsence($user); | ||||
|             } catch (UserAbsenceSyncException $e) { | ||||
|                 $this->logger->error("could not sync user absence", ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), "message" => $e->getMessage()]); | ||||
|                 $output->writeln(sprintf("Could not sync user absence: id: %s and email: %s", $user->getId(), $user->getEmail())); | ||||
|                 throw $e; | ||||
|             } | ||||
|  | ||||
|             // we first try to renew an existing subscription, if any. | ||||
|             // if not, or if it fails, we try to create a new one | ||||
|             if ($this->mapCalendarToUser->hasActiveSubscription($user)) { | ||||
|                 $this->logger->debug(self::class . ' renew a subscription for', [ | ||||
|                     'userId' => $user->getId(), | ||||
|                     'username' => $user->getUsernameCanonical(), | ||||
|                 ]); | ||||
|  | ||||
|                 ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] | ||||
|                     = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration); | ||||
|                 $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); | ||||
|  | ||||
|                 if (0 !== $expirationTs) { | ||||
|                     ++$renewed; | ||||
|                 } else { | ||||
|                     $this->logger->warning(self::class . ' could not renew subscription for a user', [ | ||||
|                         'userId' => $user->getId(), | ||||
|                         'username' => $user->getUsernameCanonical(), | ||||
|                     ]); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (!$this->mapCalendarToUser->hasActiveSubscription($user)) { | ||||
|                 $this->logger->debug(self::class . ' create a subscription for', [ | ||||
|                     'userId' => $user->getId(), | ||||
|                     'username' => $user->getUsernameCanonical(), | ||||
|                 ]); | ||||
|  | ||||
|                 ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs] | ||||
|                     = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration); | ||||
|                 $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret); | ||||
|  | ||||
|                 if (0 !== $expirationTs) { | ||||
|                     ++$created; | ||||
|                 } else { | ||||
|                     $this->logger->warning(self::class . ' could not create subscription for a user', [ | ||||
|                         'userId' => $user->getId(), | ||||
|                         'username' => $user->getUsernameCanonical(), | ||||
|                     ]); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|  | ||||
|             if (0 === $offset % $limit) { | ||||
|                 $this->em->flush(); | ||||
|                 $this->em->clear(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $this->em->flush(); | ||||
|         $this->em->clear(); | ||||
|  | ||||
|         $this->logger->warning(self::class . ' process executed', [ | ||||
|             'created' => $created, | ||||
|             'renewed' => $renewed, | ||||
|         ]); | ||||
|  | ||||
|         $output->writeln("users synchronized"); | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
| @@ -152,7 +166,7 @@ class MapAndSubscribeUserCalendarCommand extends Command | ||||
|         parent::configure(); | ||||
|  | ||||
|         $this | ||||
|             ->setDescription('MSGraph: collect user metadata and create subscription on events for users') | ||||
|             ->setDescription('MSGraph: collect user metadata and create subscription on events for users, and sync the user absence-presence') | ||||
|             ->addOption( | ||||
|                 'renew-before-end-interval', | ||||
|                 'r', | ||||
|   | ||||
| @@ -0,0 +1,20 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| /* | ||||
|  * Chill is a software for social workers | ||||
|  * | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| namespace Chill\CalendarBundle\Exception; | ||||
|  | ||||
| class UserAbsenceSyncException extends \LogicException | ||||
| { | ||||
|     public function __construct(string $message = "", int $code = 20_230_706, ?\Throwable $previous = null) | ||||
|     { | ||||
|         parent::__construct($message, $code, $previous); | ||||
|     } | ||||
| } | ||||
| @@ -58,6 +58,10 @@ final class AgentAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): Closure | ||||
|     { | ||||
|   | ||||
| @@ -59,6 +59,10 @@ class CancelReasonAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): Closure | ||||
|     { | ||||
|   | ||||
| @@ -58,6 +58,10 @@ final class JobAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): Closure | ||||
|     { | ||||
|   | ||||
| @@ -52,6 +52,10 @@ final class LocationAggregator implements AggregatorInterface | ||||
|     { | ||||
|         // no form | ||||
|     } | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): Closure | ||||
|     { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user