mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-10 16:55:00 +00:00
Compare commits
254 Commits
Author | SHA1 | Date | |
---|---|---|---|
80ea712475 | |||
0e47a1cf59 | |||
6a554c1c8c | |||
0bd8ad84a8 | |||
115d71ad34 | |||
7ca2380dad | |||
ac9704f154 | |||
87ea841aaa | |||
dccc2bf4a5 | |||
599510c3b9 | |||
18971b3204 | |||
735a4aa97c | |||
7cdbee2d0f | |||
95dfb4ba52 | |||
22241ca7b1 | |||
13faa572ef | |||
72f0c460ab | |||
c546c4a207 | |||
5cea4a647d | |||
207e109d1e | |||
758ad9bc87 | |||
90c8f1e3cf | |||
05d86f1a70 | |||
99cf7b8e8d | |||
68f2c7df25 | |||
6f1dd5b3f2 | |||
e7a9b10d0d | |||
b4f8c7d261 | |||
65b585d4a9 | |||
2cec4a770e | |||
52c5807cd5 | |||
bf7f375daf | |||
cd85e37c1b | |||
67e9127f8e | |||
754515f185 | |||
15fbfbb621 | |||
cf294541ae | |||
b4f9501af5 | |||
c1a586dfb9 | |||
57a1a49fac | |||
19789acbbe | |||
77180ebb0c | |||
6e2377b7ee | |||
4d111cd6cf | |||
9600543fec | |||
86315b1579 | |||
0355699a69 | |||
efd43c3791 | |||
f770664798 | |||
74645c065b | |||
c1d70ec63e | |||
36ca0fcbfc | |||
91c4c3c270 | |||
92b1b17679 | |||
c19d1ff0c7 | |||
94f26df81e | |||
ae9f1e3905 | |||
b6fe830f1e
|
|||
665f0b4ccb
|
|||
30fa57b999
|
|||
0a6131abcc
|
|||
bd4f3e38eb
|
|||
75bca46b98
|
|||
fe1fe31b68
|
|||
46a5464429
|
|||
67ea62f33d
|
|||
0e16ab4c18
|
|||
a68fef8547
|
|||
fe89e527e6 | |||
7b28bb7561
|
|||
8f793d8985
|
|||
264e19e20a
|
|||
f30b4ff452 | |||
f3a37d435f | |||
f0c6d00953
|
|||
fe5e1e5557
|
|||
16fcd03a02
|
|||
34ee2b3ba5
|
|||
03baee4286 | |||
bc5c420f92 | |||
b256c3176e | |||
53cc43f8d3
|
|||
40a72d9fca
|
|||
82b3025842 | |||
7c7c5862c6 | |||
6301505563 | |||
ba41ab98f7 | |||
fa47dcd5b6 | |||
f3fbd5314a
|
|||
e9f90e2dd2 | |||
dcae15e8d8 | |||
9ccbba2deb | |||
87cdf2e53b | |||
bf27e81e35 | |||
6275623f5e | |||
bc4af576bc | |||
01e9a15bd7 | |||
256579af89 | |||
3af065e530 | |||
fd79692f6d | |||
7758d62f86 | |||
2921073da3 | |||
f02d97ddb0 | |||
95b3933650 | |||
b7a6a88e70 | |||
9846cd5acb | |||
032eed0424 | |||
767ed89f80 | |||
15bf4d6d6d | |||
c7cdb3a092 | |||
bb187f5463 | |||
1ebf68683f
|
|||
98c2ee3ab2
|
|||
4d7b4500c1
|
|||
ca2ba9d8e4
|
|||
f34f8b1eaa | |||
2b96160200
|
|||
933c353357
|
|||
e6da727a11 | |||
0719a541a6 | |||
883147ed9c | |||
718de2fad0 | |||
643d8f99be
|
|||
3954d69c94 | |||
78a5e81e33
|
|||
b05e128b5e
|
|||
8f39d0320c | |||
1cc61cee36
|
|||
a21cefab31
|
|||
f2673d6c83
|
|||
18535ee85f
|
|||
aea6796ba1 | |||
872d5e8ebf
|
|||
c0901947ca
|
|||
860e076b89 | |||
ea0e5dfa14
|
|||
e0cdf06e99 | |||
59da93fd75 | |||
1d9b8729ab | |||
22ded77bde | |||
995e2dac80 | |||
28c41aaf85
|
|||
ce2ce42530 | |||
1409a3b23a
|
|||
51e382760d | |||
b0fcffea2d
|
|||
66e1047752 | |||
7bee376718 | |||
6bc45bbca3 | |||
f3912e5544 | |||
0950074121 | |||
94e9b75e40 | |||
23c7a92546 | |||
8391dbe448 | |||
a7842b2597
|
|||
1552b3c9d7
|
|||
2259a31260 | |||
36eed4323c | |||
9bf8c9b0ed | |||
1930c48d28 | |||
29306d2b66 | |||
e0758215ba | |||
e82c7cdc6c | |||
3f66e1a862
|
|||
0423540976 | |||
fcd4c3b92e | |||
efee2d8b44 | |||
7c109d909a | |||
d825a8509b | |||
f3829d3390 | |||
e8690d249f | |||
a2e705bd92 | |||
e38b369149
|
|||
5a36a8660d | |||
f7d385eba1 | |||
edd66f6a6c | |||
ef1eb2031e | |||
2882038efc
|
|||
6065680e1e
|
|||
88114e3ba6
|
|||
bf93c1ddb2 | |||
0d365e16e5
|
|||
802ff20b5c | |||
cdfe201574 | |||
43419f9f15
|
|||
39896ea6e2
|
|||
ca62c3fd0b
|
|||
b3b84c5dc0
|
|||
6bdb3e9695
|
|||
20e64e8768 | |||
197d69ef4a
|
|||
9423f4d055 | |||
99d6e9e6b8 | |||
63f9bd5548
|
|||
c8146ded17
|
|||
17d2b795b4 | |||
7f30742fc3
|
|||
56d9072abe
|
|||
7ccff61c25
|
|||
8929f4b8a3 | |||
43b7139488
|
|||
d3251075e9
|
|||
93a598b549
|
|||
9b6e6ec20f
|
|||
77d4b13c1b
|
|||
2861945a52
|
|||
5b42b85b50
|
|||
cc97199c5d | |||
20d5fabc18 | |||
e40b1b9853
|
|||
c19232de35
|
|||
c95dc23c51
|
|||
c04fd66163
|
|||
0361743ae0 | |||
af4e7f1226
|
|||
ff1629cbb7
|
|||
779eb812b0
|
|||
a990591e0c
|
|||
61982634a6 | |||
6c58e7eb3e | |||
4b25970ce0 | |||
52d51264ba | |||
4e934653be | |||
1ee0e8e350 | |||
4da7040a49 | |||
a34b5f8588 | |||
0d626fb345 | |||
25d4b6acbb | |||
145c1df313 | |||
7f9738975c | |||
a56370d851
|
|||
3e63b4abf3 | |||
dd344aed52
|
|||
1485d1ce7a | |||
a7dbdc2b9d
|
|||
b3d993165d
|
|||
9ccc57bbcb
|
|||
cc0e832cc9
|
|||
c8b62d990a
|
|||
b7df62d4f5
|
|||
5a395b160f
|
|||
393e59e22b
|
|||
4a5ac170ba
|
|||
c019fffbe7
|
|||
31745bc252
|
|||
56940d830c
|
|||
347eda05df
|
|||
90e8687799
|
|||
f19b939bd4
|
|||
0e5f1b4ab9
|
|||
f7c11d3567
|
|||
51544cfc48
|
|||
659dff3d2c
|
|||
ad72192e24
|
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
|
39
.changes/v2.5.0.md
Normal file
39
.changes/v2.5.0.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## v2.5.0 - 2023-07-14
|
||||
### Feature
|
||||
* Allow filtering on the basis of a user within general tasks lists
|
||||
* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions.
|
||||
* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course
|
||||
* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples
|
||||
* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation
|
||||
* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates"
|
||||
* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours
|
||||
* Improve filtering layout
|
||||
|
||||
### Fixed
|
||||
* reimplement the visualization of all calculator results
|
||||
* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents
|
||||
* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind
|
||||
|
||||
### DX
|
||||
* Documentation for database principles
|
||||
* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution
|
||||
|
||||
### UX
|
||||
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag
|
||||
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners
|
||||
* Improve residential address position on show onthefly modale
|
||||
|
||||
### Traduction francophone des principaux changements
|
||||
|
||||
* Ajout d'un filtre "par utilisateur" aux pages de tâche
|
||||
* Filtre des actions d'accompagnement par date, type, intervenant
|
||||
* export: liste des usagers concernés avec détail de leurs parcours
|
||||
* export: ajout d'un regroupement des échanges par localisation
|
||||
* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates"
|
||||
* ajout d'une tâche cron pour associer les adresses à une adresse de référence
|
||||
* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation
|
||||
* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants
|
||||
* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position
|
||||
* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage"
|
||||
* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée
|
||||
* améliore le design et l'expérience utilisateur des filtres
|
3
.changes/v2.5.1.md
Normal file
3
.changes/v2.5.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v2.5.1 - 2023-07-14
|
||||
### Fixed
|
||||
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match
|
3
.changes/v2.5.2.md
Normal file
3
.changes/v2.5.2.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v2.5.2 - 2023-07-15
|
||||
### Fixed
|
||||
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)
|
3
.changes/v2.5.3.md
Normal file
3
.changes/v2.5.3.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v2.5.3 - 2023-07-20
|
||||
### Fixed
|
||||
* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous.
|
21
.changes/v2.6.0.md
Normal file
21
.changes/v2.6.0.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## v2.6.0 - 2023-09-14
|
||||
### Feature
|
||||
* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Add locations in Aside Activity. By default, suggest user location, otherwise a select with all locations.
|
||||
* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Adapt Aside Activity exports: display location, filter by location, group by location
|
||||
* Use the CRUD controller for center entity + add the isActive property to be able to mask instances of Center that are no longer in use.
|
||||
### Fixed
|
||||
* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) reinstate the fusion of duplicate persons
|
||||
* Missing translation in Work Actions exports
|
||||
* Reimplement the mission type filter on tasks, only for instances that have a config parameter indicating true for this.
|
||||
* ([#135](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/135)) Corrects a typing error in 2 filters, which caused an
|
||||
error when trying to reedit a saved export
|
||||
|
||||
|
||||
* ([#136](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/136)) [household] when moving a person to a sharing position to a not-sharing position on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member.
|
||||
* Add missing translation for comment field placeholder in repositionning household editor.
|
||||
|
||||
* Do not send an email to creator twice when adding a comment to a notification
|
||||
* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) Fix gestion doublon functionality to work with chill bundles v2
|
||||
### UX
|
||||
* Uniformize badge-person in household banner (background, size)
|
||||
|
@@ -7,7 +7,7 @@ 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.Long "") }}
|
||||
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if and (.Custom.Long) (not (eq .Custom.Long "")) }}
|
||||
|
||||
{{ .Custom.Long }}{{ end }}
|
||||
custom:
|
||||
@@ -30,6 +30,8 @@ kinds:
|
||||
auto: patch
|
||||
- label: DX
|
||||
auto: patch
|
||||
- label: UX
|
||||
auto: patch
|
||||
newlines:
|
||||
afterChangelogHeader: 1
|
||||
beforeChangelogVersion: 1
|
||||
|
111
CHANGELOG.md
111
CHANGELOG.md
@@ -6,6 +6,117 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v2.6.0 - 2023-09-14
|
||||
### Feature
|
||||
* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Add locations in Aside Activity. By default, suggest user location, otherwise a select with all locations.
|
||||
* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Adapt Aside Activity exports: display location, filter by location, group by location
|
||||
* Use the CRUD controller for center entity + add the isActive property to be able to mask instances of Center that are no longer in use.
|
||||
### Fixed
|
||||
* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) reinstate the fusion of duplicate persons
|
||||
* Missing translation in Work Actions exports
|
||||
* Reimplement the mission type filter on tasks, only for instances that have a config parameter indicating true for this.
|
||||
* ([#135](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/135)) Corrects a typing error in 2 filters, which caused an
|
||||
error when trying to reedit a saved export
|
||||
|
||||
|
||||
* ([#136](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/136)) [household] when moving a person to a sharing position to a not-sharing position on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member.
|
||||
* Add missing translation for comment field placeholder in repositionning household editor.
|
||||
|
||||
* Do not send an email to creator twice when adding a comment to a notification
|
||||
* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) Fix gestion doublon functionality to work with chill bundles v2
|
||||
### UX
|
||||
* Uniformize badge-person in household banner (background, size)
|
||||
|
||||
|
||||
## v2.5.3 - 2023-07-20
|
||||
### Fixed
|
||||
* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous.
|
||||
|
||||
## v2.5.2 - 2023-07-15
|
||||
### Fixed
|
||||
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)
|
||||
|
||||
## v2.5.1 - 2023-07-14
|
||||
### Fixed
|
||||
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match
|
||||
|
||||
## v2.5.0 - 2023-07-14
|
||||
### Feature
|
||||
* Allow filtering on the basis of a user within general tasks lists
|
||||
* ([#120](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/120)) Adding OrderFilter to the list of social actions.
|
||||
* ([#125](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/125)) [export] Add a list for people with their associated course
|
||||
* [export] Add ordering by person's lastname or course opening date in list which concerns accompanying course or peoples
|
||||
* ([#128](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/128)) [Export] allow to group activities by localisation
|
||||
* ([#129](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/129)) [export] Add a filter "filter course having an activity between two dates"
|
||||
* ([#112](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/112)) [addresses] Add a cronjob to re-associate addresses with addresses reference every 6 hours
|
||||
* Improve filtering layout
|
||||
|
||||
### Fixed
|
||||
* reimplement the visualization of all calculator results
|
||||
* ([#117](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/117)) Repair my unread notification list with actions and evaluations documents
|
||||
* ([#126](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/126)) Correct bug in thirdparty API search query: simplify address joins clause for child and parent kind
|
||||
|
||||
### DX
|
||||
* Documentation for database principles
|
||||
* [cronjob] when a cronjob is executed, it may return an array of data that will be passed as argument on the next execution
|
||||
|
||||
### UX
|
||||
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Better integration of address details button: look, position, title tag
|
||||
* ([#93](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/93)) Show address detail button on person and household banners
|
||||
* Improve residential address position on show onthefly modale
|
||||
|
||||
### Traduction francophone des principaux changements
|
||||
|
||||
* Ajout d'un filtre "par utilisateur" aux pages de tâche
|
||||
* Filtre des actions d'accompagnement par date, type, intervenant
|
||||
* export: liste des usagers concernés avec détail de leurs parcours
|
||||
* export: ajout d'un regroupement des échanges par localisation
|
||||
* export: ajout d'un filtre "parcours ayant reçu un échange entre deux dates"
|
||||
* ajout d'une tâche cron pour associer les adresses à une adresse de référence
|
||||
* correction: réparation de la liste des notifications sur la page d'accueil, dans le cas où une notification concerne une action ou un document dans une évaluation
|
||||
* correction: réparation de la recherche des tiers ayant des codes postaux similaires entre les parents et enfants
|
||||
* meilleure intégration du bouton "détail d'une adresse": améliration de la taille et de la position
|
||||
* bouton permettant de visualiser les détails d'une adresse (modale avec carte) dans la bannière "Usager" et "Ménage"
|
||||
* amélioration de la modale permettant de voir les détails d'un usager: les adresses de résidence sont dans la continuité des autres adresses, et non plus dans une colonne séparée
|
||||
* améliore le design et l'expérience utilisateur des filtres
|
||||
|
||||
## v2.4.0 - 2023-07-07
|
||||
|
||||
### 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.
|
||||
|
80
CONTRIBUTING.md
Normal file
80
CONTRIBUTING.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Contributing
|
||||
|
||||
Chill is an open source, community-driven project.
|
||||
|
||||
If you'd like to contribute, please read the following.
|
||||
|
||||
## What can you do ?
|
||||
|
||||
Chill is an open-source project driven by a community of developers, users and social workers. If you don't feel ready to contribute code or patches, reviewing issues and pull requests (PRs) can be a great start to get involved and give back.
|
||||
|
||||
If you don't have your own instance or don't want to use it, you can try to reproduce bugs using the instance https://demo.chill.social
|
||||
|
||||
## Core team
|
||||
|
||||
The core team is the group of developers that determine the direction and evolution of the Chill project. Their votes rule if the features and patches proposed by the community are approved or rejected.
|
||||
|
||||
All the Chill Core members are long-time contributors with solid technical expertise and they have demonstrated a strong commitment to drive the project forward.
|
||||
|
||||
The core team:
|
||||
|
||||
- elects his own members;
|
||||
- merge pull requests;
|
||||
|
||||
### members
|
||||
|
||||
Project leader: [julienfastre](https://gitlab.com/julienfastre)
|
||||
|
||||
Core members:
|
||||
|
||||
- [tchama](https://gitlab.com/tchama)
|
||||
- [LenaertsJ](https://gitlab.com/LenaertsJ)
|
||||
- [nobohan](https://gitlab.com/nobohan)
|
||||
|
||||
### Becoming a project member
|
||||
|
||||
About once a year, the core team discusses the opportunity to invite new members. To become a core team member, you must:
|
||||
|
||||
- take part on the development for at least 6 month: propose multiple merge requests and participate to the peer review process;
|
||||
- through this participation, demonstrate your technical skills and your knowledge of the software and any of their dependencies;
|
||||
|
||||
### Core Membership Revocation
|
||||
|
||||
A Symfony Core membership can be revoked for any of the following reasons:
|
||||
|
||||
- Refusal to follow the rules and policies stated in this document;
|
||||
- Lack of activity for the past six months;
|
||||
- Willful negligence or intent to harm the Chill project;
|
||||
|
||||
The decision is taken by the majority of project members.
|
||||
|
||||
## Code development rules
|
||||
|
||||
### Merge requests
|
||||
|
||||
Every merge request must contains:
|
||||
|
||||
- one more entries suitable for generating a changelog. This is done using the [changie utility](https://changie.dev);
|
||||
- a comprehensible description of the changes;
|
||||
- if applicable, automated tests should be adapted or created;
|
||||
- the code style must pass the project's rules, and non phpstan errors must be raised nor rector refactoring suggestion.
|
||||
|
||||
The pipelines must pass.
|
||||
|
||||
In case of emergency, some rules may be temporarily ignored.
|
||||
|
||||
### Merge Request Voting Policy
|
||||
|
||||
- -1 votes must always be justified by technical and objective reasons;
|
||||
- +1 (technically: approbation on the merge request) votes do not require justification, unless there is at least one -1 vote;
|
||||
- Core members can change their votes as many times as they desire during the course of a merge request discussion;
|
||||
- Core members are not allowed to vote on their own merge requests.
|
||||
|
||||
### Merge Request Merging Process
|
||||
|
||||
All code must be committed to the repository through merge requests, except for minor changes which can be committed directly to the repository.
|
||||
|
||||
### Release Policy
|
||||
|
||||
The Core members are also the release manager for every Chill version.
|
||||
|
@@ -94,7 +94,7 @@ class CountPerson implements ExportInterface
|
||||
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
|
||||
{
|
||||
// we gather all center the user choose.
|
||||
$centers = array_map(static fn($el) => $el['center'], $acl);
|
||||
$centers = array_map(static fn ($el) => $el['center'], $acl);
|
||||
|
||||
$qb = $this->entityManager->createQueryBuilder();
|
||||
|
||||
|
36
docs/source/development/FAQ.rst
Normal file
36
docs/source/development/FAQ.rst
Normal file
@@ -0,0 +1,36 @@
|
||||
.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
||||
A copy of the license is included in the section entitled "GNU
|
||||
Free Documentation License".
|
||||
|
||||
.. _faq:
|
||||
|
||||
|
||||
Frequently asked questions
|
||||
####################
|
||||
|
||||
Continuous integration
|
||||
***********
|
||||
|
||||
Pipeline fails, but php-cs-fixer doesn't alert me when running it locally ?
|
||||
========================================
|
||||
|
||||
It is possible that you run php-cs-fixer on your local instance of chill and no fixes are made.
|
||||
Everything seems fine, so you push. However once the pipeline is run in gitlab, you're notified that it failed due to php
|
||||
cs errors.
|
||||
|
||||
In this case it's likely that you have to update your version of php-cs-fixer.
|
||||
php-cs-fixer is installed when building the docker image: https://gitea.champs-libres.be/Chill-project/chill-skeleton-basic/src/branch/main/Dockerfile#L50
|
||||
|
||||
Consequently, to update php-cs-fixer we have to update the image by building it again.
|
||||
|
||||
For this the following commands can be used,
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
docker compose build --pull php
|
||||
# replace existing containers
|
||||
docker compose up -d --force-recreate php
|
84
docs/source/development/database-principles.rst
Normal file
84
docs/source/development/database-principles.rst
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
.. database-principles:
|
||||
|
||||
Principes de la base de données
|
||||
###############################
|
||||
|
||||
Cette page donne une compréhension globale de la base de donnée de Chill, et explique quelques détails d'implémentations qui permettent d'accélérer les traitements à partir de la base de donnée, ou de l'exploiter plus aisément.
|
||||
|
||||
Cette page est rédigée en français.
|
||||
|
||||
.. note::
|
||||
|
||||
La stabilité du schéma de la base de donnée n'est pas garantie.
|
||||
|
||||
Toutefois, ce dernier évolue relativement peu. Il est rare que des tables ou des colonnes soient supprimées ou renommées. Mais il n'est pas garanti que cela puisse arriver.
|
||||
|
||||
Généralités
|
||||
===========
|
||||
|
||||
Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`.
|
||||
|
||||
Schéma et conventions de nommage
|
||||
--------------------------------
|
||||
|
||||
Au début de l'histoire de Chill, les schémas postgresql n'étaient pas exploités. Les données étaient stockées dans le schéma :code:`public`.
|
||||
|
||||
Par la suite, des nouveaux bundles sont apparus, et les tables ont été classées dans des schémas dédiés.
|
||||
|
||||
A l'heure actuelle:
|
||||
|
||||
- pour les anciens bundle, ceux qui ont déjà des tables dans le schéma public, les nouvelles tables sont ajoutées à ce schéma. Elles sont préfixées par :code:`chill_<nom du bundle>_`;
|
||||
- pour les bundles plus récents, les tables sont créées dans le schéma dédié
|
||||
|
||||
Données avec de l'historicité
|
||||
-----------------------------
|
||||
|
||||
Certaines données sont historisées:
|
||||
|
||||
- les référents d'un parcours;
|
||||
- les statuts d'un parcours;
|
||||
- la liaison entre les centres et les usagers;
|
||||
- etc.
|
||||
|
||||
Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable.
|
||||
|
||||
Dans certains cas, la donnée actuelle (référent d'un parcours, par exemple) est également répétée au niveau de la table en elle-même. Par exemple, la table des parcours :code:`chill_person_accompanying_period` comporte une colonne :code:`step` (le statut du parcours) et :code:`user_id` (id du référent) en plus de l'historique. Bien que redondant, cela simplifie les traitements.
|
||||
|
||||
Relations particulières
|
||||
=======================
|
||||
|
||||
Usagers, ménages, adresses
|
||||
--------------------------
|
||||
|
||||
Les usagers ont une adresse au travers des ménages: dans l'interface, l'adresse est inscrite dans le dossier du ménage, et elle est "donnée" aux usagers membres du ménage, **et** qui partagent l'adresse de ce ménage. En effet, il est possible que des usagers "appartiennent" à un ménage sans y être domicilié: c'est le cas, par exemple, des enfants en garde alternée.
|
||||
|
||||
L'historique de l'appartenance des usagers au ménage est conservée, de même que l'historique des adresses pour un même ménage.
|
||||
|
||||
Les tables en jeu sont les suivantes:
|
||||
|
||||
- la table :code:`chill_person_person` liste les usagers;
|
||||
- la table :code:`chill_person_household_members` liste les appartenances au ménage: il s'agit de la jointure entre les usagers et les ménages:
|
||||
- les colonnes :code:`startDate` et :code:`endDate` indiquent la date de début et la date de fin de l'appartenance;
|
||||
- la colonne :code:`shareHousehold` indique si l'utilisateur partage l'adresse du ménage (si oui, sa valeur est :code:`TRUE`)
|
||||
- la table :code:`chill_person_household` liste les ménages
|
||||
- la table :code:`chill_person_household_to_addresses` associe les ménages aux adresses;
|
||||
- la table :code:`chill_main_address` contient les adresses, en indiquant la date de début de validité (:code:`validFrom`) et la fin de validité (:code:`validTo`).
|
||||
|
||||
Pour simplifier la résolution des adresses et des usagers, deux vues ont été mises en œuvre:
|
||||
|
||||
- la vue :code:`view_chill_person_household_address` reprend, pour chaque usager, l'historique des appartenances au ménage découpée par l'historique des adresses d'un ménage.
|
||||
Autrement dit, une ligne est créée à chaque fois qu'un usager change de ménage, ou qu'un ménage change d'adresse. Il est donc possible de retrouver l'historique complet des adresses pour un usager donné via cette table.
|
||||
- la vue :code:`view_chill_person_current_address` reprend l'adresse actuelle des usagers.
|
||||
|
||||
Adresses et unités géographiques
|
||||
--------------------------------
|
||||
|
||||
Chill propose des statistiques sur la localisation des adresses par rapport à des zones géographiques (:code:`chill_main_geographical_unit`).
|
||||
|
||||
Comme la résolution géographique des adresses est coûteuse en CPU et en temps de traitement, une vue matérialisée a été créée: :code:`view_chill_main_address_geographical_unit`. Elle est rafraichie quotidiennement dans la base de donnée de production.
|
||||
|
||||
Liste des tables et commentaires
|
||||
================================
|
||||
|
||||
Une liste commentée de toutes les tables :download:`est disponible au format CSV <./database/table_list.csv`.
|
155
docs/source/development/database/table_list.csv
Normal file
155
docs/source/development/database/table_list.csv
Normal file
@@ -0,0 +1,155 @@
|
||||
order,table_schema,table_name,commentaire
|
||||
1,chill_3party,party_category,Catégorie de tiers
|
||||
2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
|
||||
3,chill_3party,party_profession,Profession du tiers (déprécié)
|
||||
4,chill_3party,third_party,Tiers
|
||||
5,chill_3party,thirdparty_category,association tiers - catégories
|
||||
6,chill_asideactivity,asideactivity,Activités annexes
|
||||
7,chill_asideactivity,asideactivitycategory,Catégories d'activités annexes
|
||||
8,chill_budget,charge,Charges du budget
|
||||
9,chill_budget,charge_type,Types de charges
|
||||
10,chill_budget,resource,Ressources du budget
|
||||
11,chill_budget,resource_type,Types de ressources
|
||||
12,chill_calendar,calendar,Rendez-vous
|
||||
13,chill_calendar,calendar_doc,Document du rendez-vous
|
||||
14,chill_calendar,calendar_range,Plage de disponibilité
|
||||
15,chill_calendar,calendar_to_persons,association rendez-vous - usagers
|
||||
16,chill_calendar,calendar_to_thirdparties,association rendez-vous - tiers
|
||||
17,chill_calendar,cancel_reason,Motifs d'annulations
|
||||
18,chill_calendar,invite,Invitation aux rendez-vous
|
||||
19,chill_doc,accompanyingcourse_document,Documents associés aux parcours
|
||||
20,chill_doc,document_category,Catégories de documents
|
||||
21,chill_doc,person_document,Documents associés à l'usagers
|
||||
22,chill_doc,stored_object,Documents
|
||||
23,chill_task,recurring_task,Tâches récurrentes (non utilisé)
|
||||
24,chill_task,single_task,Tâches
|
||||
25,chill_task,single_task_place_event,Historique des transitions des tâches
|
||||
26,chill_vendee,adressederelais,
|
||||
27,chill_vendee,center_polygon
|
||||
28,chill_vendee,entourage,
|
||||
29,chill_vendee,geographical_unit
|
||||
30,chill_vendee,geographical_unit_association
|
||||
31,chill_vendee,mobilite
|
||||
32,chill_vendee,niveauetude
|
||||
33,chill_vendee,security_profile
|
||||
34,chill_vendee,security_profile_action
|
||||
35,chill_vendee,security_profile_jobs
|
||||
36,chill_vendee,situationprofessionelle
|
||||
37,chill_vendee,statutlogement
|
||||
38,chill_vendee,tempsdetravail
|
||||
39,chill_vendee,titredesejour
|
||||
40,chill_vendee,vendee_person
|
||||
41,chill_vendee,vendee_person_mineur
|
||||
42,chill_vendee,vendeeperson_entourage
|
||||
43,chill_vendee,vendeepersonmineur_adressederelais
|
||||
44,public,accompanying_periods_scopes,Services associés aux parcours
|
||||
45,public,activity,Échanges
|
||||
46,public,activity_activityreason,s
|
||||
47,public,activity_person,
|
||||
48,public,activity_storedobject,
|
||||
49,public,activity_thirdparty,
|
||||
50,public,activity_user,
|
||||
51,public,activityreason,Sujets d'échange
|
||||
52,public,activityreasoncategory,Catégories de sujets
|
||||
53,public,activitytpresence,Présence aux échanges
|
||||
54,public,activitytype,Types d'échanges
|
||||
55,public,activitytypecategory,Catégories de types d'échanges
|
||||
56,public,centers,"Centres (territoires, agences, etc.)"
|
||||
57,public,chill_activity_activity_chill_person_socialaction,
|
||||
58,public,chill_activity_activity_chill_person_socialissue
|
||||
59,public,chill_docgen_template,Gabarits de documents
|
||||
60,public,chill_main_address,Adresses
|
||||
61,public,chill_main_address_legacy,Anciennes adresses (dépréciés)
|
||||
62,public,chill_main_address_reference,Adresses de référence
|
||||
63,public,chill_main_civility,Civilités
|
||||
64,public,chill_main_cronjob_execution,Dernière exécution des tâche cron
|
||||
65,public,chill_main_geographical_unit,Unités géographiques
|
||||
66,public,chill_main_geographical_unit_layer,Couches d'unités géographiques
|
||||
67,public,chill_main_location,Localisations
|
||||
68,public,chill_main_location_type,Types de localisations
|
||||
69,public,chill_main_notification,Notifications
|
||||
70,public,chill_main_notification_addresses_unread
|
||||
71,public,chill_main_notification_addresses_user
|
||||
72,public,chill_main_notification_comment,
|
||||
73,public,chill_main_postal_code,Code postaux
|
||||
74,public,chill_main_saved_export,Exports enregistrés
|
||||
75,public,chill_main_user_job,Métiers
|
||||
76,public,chill_main_workflow_entity,Workflows
|
||||
77,public,chill_main_workflow_entity_comment
|
||||
78,public,chill_main_workflow_entity_step,Etapes du workflow
|
||||
79,public,chill_main_workflow_entity_step_cc_user,
|
||||
80,public,chill_main_workflow_entity_step_user
|
||||
81,public,chill_main_workflow_entity_step_user_by_accesskey,
|
||||
82,public,chill_main_workflow_entity_subscriber_to_final,
|
||||
83,public,chill_main_workflow_entity_subscriber_to_step
|
||||
84,public,chill_person_accompanying_period,Parcours d'accompagnement
|
||||
85,public,chill_person_accompanying_period_closingmotive,Motifs de cloture des parcours
|
||||
86,public,chill_person_accompanying_period_comment,Commentaires des parcours
|
||||
87,public,chill_person_accompanying_period_location_history,Historique de la localisatio ndes parcours
|
||||
88,public,chill_person_accompanying_period_origin,Origine des parcours
|
||||
89,public,chill_person_accompanying_period_participation,Appartenance des usagers au parcours
|
||||
90,public,chill_person_accompanying_period_resource,Personnes ressources d'un parcours
|
||||
91,public,chill_person_accompanying_period_social_issues,
|
||||
92,public,chill_person_accompanying_period_step_history
|
||||
93,public,chill_person_accompanying_period_user_history
|
||||
94,public,chill_person_accompanying_period_work,Actions d'accompagnements
|
||||
95,public,chill_person_accompanying_period_work_evaluation,Évaluations (dans les actions d'accompagnements)
|
||||
96,public,chill_person_accompanying_period_work_evaluation_document,Documents des évaluations
|
||||
97,public,chill_person_accompanying_period_work_goal,Objectifs d'une actions
|
||||
98,public,chill_person_accompanying_period_work_goal_result,Objectifs et résultats d'une action
|
||||
99,public,chill_person_accompanying_period_work_person,Usagers associés à une actions
|
||||
100,public,chill_person_accompanying_period_work_referrer,Référents d'une actions
|
||||
101,public,chill_person_accompanying_period_work_result,Résultats d'une action
|
||||
102,public,chill_person_accompanying_period_work_third_party,Tiers traitants d'une action
|
||||
103,public,chill_person_alt_name,"Noms supplémentaires d'un usager (nom marital, etc.)"
|
||||
104,public,chill_person_household,Ménages
|
||||
105,public,chill_person_household_composition,
|
||||
106,public,chill_person_household_composition_type,Types de composition de ménage
|
||||
107,public,chill_person_household_members,Membres du ménages
|
||||
108,public,chill_person_household_position,Positions dans le ménage
|
||||
109,public,chill_person_household_to_addresses,Association adresses - ménages
|
||||
110,public,chill_person_marital_status,Etats civils
|
||||
111,public,chill_person_not_duplicate,
|
||||
112,public,chill_person_person,Usagers
|
||||
113,public,chill_person_person_center_history,Historique des centres d'un usagers
|
||||
114,public,chill_person_persons_to_addresses,Déprécié
|
||||
115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager
|
||||
116,public,chill_person_relations,Types de relations de filiation
|
||||
117,public,chill_person_relationships,Relations de filiations
|
||||
118,public,chill_person_residential_address,Adresses de résidences
|
||||
119,public,chill_person_resource,Personnes ressources (pour les personnes)
|
||||
120,public,chill_person_resource_kind,Type de personnes ressources
|
||||
121,public,chill_person_social_action,Liste des actions d'accompagnement
|
||||
122,public,chill_person_social_action_goal,Objectifs associés à une action
|
||||
123,public,chill_person_social_action_result,Résultats associés à une action
|
||||
124,public,chill_person_social_issue,Problématiques sociales
|
||||
125,public,chill_person_social_work_evaluation,Evaluations disponibles
|
||||
126,public,chill_person_social_work_evaluation_action,Associations entre les évaluations et les actions
|
||||
127,public,chill_person_social_work_goal,Objectifs disponibles pour une actions
|
||||
128,public,chill_person_social_work_goal_result,Objectifs et résultats disponible pour une action
|
||||
129,public,chill_person_social_work_result,Résultats disponibles pour une action
|
||||
130,public,country,Pays
|
||||
131,public,custom_field_long_choice_options,
|
||||
132,public,customfield
|
||||
133,public,customfieldsdefaultgroup
|
||||
134,public,customfieldsgroup
|
||||
135,public,geography_columns,Table liée à postgis
|
||||
136,public,geometry_columns,Table liée à postgis
|
||||
137,public,group_centers,
|
||||
138,public,language,Langues
|
||||
139,public,messenger_messages,Table système
|
||||
140,public,migration_versions,Table système
|
||||
141,public,permission_groups
|
||||
142,public,permissionsgroup_rolescope
|
||||
143,public,persons_spoken_languages
|
||||
144,public,regroupment,Regroupement de centres
|
||||
145,public,regroupment_center,
|
||||
146,public,role_scopes,
|
||||
147,public,scopes,Services
|
||||
148,public,spatial_ref_sys,Table système (postgis)
|
||||
149,public,user_groupcenter,
|
||||
150,public,users,Utilisateurs
|
||||
151,public,view_chill_person_accompanying_period_info,
|
||||
152,public,view_chill_person_current_address
|
||||
153,public,view_chill_person_household_address
|
||||
154,public,view_chill_person_person_center_history_current
|
Can't render this file because it has a wrong number of fields in line 28.
|
@@ -9,7 +9,7 @@
|
||||
Development
|
||||
###########
|
||||
|
||||
As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the framework's documentation should answer most of your questions. We are explaining here some tips to work with Chill, and things we provide to encounter our needs.
|
||||
As Chill relies on the `symfony <http://symfony.com>`_ framework, reading the framework's documentation should answer most of your questions. We are explaining here some tips to work with Chill, and help with things we've encountered.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
@@ -36,6 +36,8 @@ As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the fram
|
||||
Assets <assets.rst>
|
||||
Cron Jobs <cronjob.rst>
|
||||
Info about entities <entity-info.rst>
|
||||
Info about database (in French) <database-principles.rst>
|
||||
Developer FAQ <FAQ.rst>
|
||||
|
||||
Layout and UI
|
||||
**************
|
||||
|
@@ -56,7 +56,7 @@ class ChillMainConfiguration implements ConfigurationInterface
|
||||
->end() // end of widgets
|
||||
->end() // end of root/children
|
||||
->end() // end of root
|
||||
;
|
||||
;
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
@@ -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,13 @@
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Set\ValueObject\LevelSetList;
|
||||
|
@@ -13,6 +13,4 @@ namespace Chill\ActivityBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillActivityBundle extends Bundle
|
||||
{
|
||||
}
|
||||
class ChillActivityBundle extends Bundle {}
|
||||
|
@@ -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,69 +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
|
||||
) {
|
||||
$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;
|
||||
}
|
||||
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,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes a Activity entity.
|
||||
@@ -289,14 +252,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->hasEntityChoice('activity_types') ? $filter->getEntityChoiceData('activity_types') : [],
|
||||
'jobs' => $filter->hasEntityChoice('jobs') ? $filter->getEntityChoiceData('jobs') : [],
|
||||
'before' => $filter->getDateRangeData('activity_date')['to'],
|
||||
'after' => $filter->getDateRangeData('activity_date')['from'],
|
||||
];
|
||||
|
||||
if ($person instanceof Person) {
|
||||
$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 +288,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 +311,46 @@ 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');
|
||||
|
||||
if (1 < count($types)) {
|
||||
$filterBuilder
|
||||
->addEntityChoice('activity_types', 'activity_filter.Types', \Chill\ActivityBundle\Entity\ActivityType::class, $types, [
|
||||
'choice_label' => function (\Chill\ActivityBundle\Entity\ActivityType $activityType) {
|
||||
$text = match ($activityType->hasCategory()) {
|
||||
true => $this->translatableStringHelper->localize($activityType->getCategory()->getName()) . ' > ',
|
||||
false => '',
|
||||
};
|
||||
|
||||
return $text . $this->translatableStringHelper->localize($activityType->getName());
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
if (1 < count($jobs)) {
|
||||
$filterBuilder
|
||||
->addEntityChoice('jobs', 'activity_filter.Jobs', UserJob::class, $jobs, [
|
||||
'choice_label' => fn (UserJob $u) => $this->translatableStringHelper->localize($u->getLabel())
|
||||
]);
|
||||
}
|
||||
|
||||
return $filterBuilder->build();
|
||||
}
|
||||
|
||||
public function newAction(Request $request): Response
|
||||
{
|
||||
$view = null;
|
||||
|
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\ActivityBundle\Export\Aggregator;
|
||||
|
||||
use Chill\ActivityBundle\Export\Declarations;
|
||||
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Closure;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use function in_array;
|
||||
|
||||
final readonly class ActivityLocationAggregator implements AggregatorInterface
|
||||
{
|
||||
public const KEY = 'activity_location_aggregator';
|
||||
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data)
|
||||
{
|
||||
if (!in_array('actloc', $qb->getAllAliases(), true)) {
|
||||
$qb->leftJoin('activity.location', 'actloc');
|
||||
}
|
||||
$qb->addSelect(sprintf('actloc.name AS %s', self::KEY));
|
||||
$qb->addGroupBy(self::KEY);
|
||||
}
|
||||
|
||||
public function applyOn(): string
|
||||
{
|
||||
return Declarations::ACTIVITY;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
// no form required for this aggregator
|
||||
}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): Closure
|
||||
{
|
||||
return function ($value): string {
|
||||
if ('_header' === $value) {
|
||||
return 'export.aggregator.activity.by_location.Activity Location';
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
}
|
||||
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return [self::KEY];
|
||||
}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return 'export.aggregator.activity.by_location.Title';
|
||||
}
|
||||
}
|
@@ -36,9 +36,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
|
||||
$this->repository = $em->getRepository(Activity::class);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -36,9 +36,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface
|
||||
$this->repository = $em->getRepository(Activity::class);
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -32,9 +32,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface
|
||||
$this->activityRepository = $activityRepository;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -50,9 +50,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
|
||||
$this->activityRepository = $activityRepository;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -104,9 +104,7 @@ class ListActivityHelper
|
||||
->addGroupBy('location.id');
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
|
||||
public function getAllowedFormattersTypes()
|
||||
{
|
||||
|
@@ -22,11 +22,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
class EmergencyFilter implements FilterInterface
|
||||
{
|
||||
private const CHOICES = [
|
||||
'activity is emergency' => true,
|
||||
'activity is not emergency' => false,
|
||||
'activity is emergency' => 'true',
|
||||
'activity is not emergency' => 'false',
|
||||
];
|
||||
|
||||
private const DEFAULT_CHOICE = false;
|
||||
private const DEFAULT_CHOICE = 'false';
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
|
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
|
||||
|
||||
use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private RollingDateConverterInterface $rollingDateConverter,
|
||||
) {}
|
||||
|
||||
public function getTitle()
|
||||
{
|
||||
return 'export.filter.activity.course_having_activity_between_date.Title';
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder
|
||||
->add('start_date', PickRollingDateType::class, [
|
||||
'label' => 'export.filter.activity.course_having_activity_between_date.Receiving an activity after'
|
||||
])
|
||||
->add('end_date', PickRollingDateType::class, [
|
||||
'label' => 'export.filter.activity.course_having_activity_between_date.Receiving an activity before'
|
||||
]);
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [
|
||||
'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
|
||||
'end_date' => new RollingDate(RollingDate::T_TODAY)
|
||||
];
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
{
|
||||
return [
|
||||
'export.filter.activity.course_having_activity_between_date.Only course having an activity between from and to',
|
||||
[
|
||||
'from' => $this->rollingDateConverter->convert($data['start_date']),
|
||||
'to' => $this->rollingDateConverter->convert($data['end_date']),
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data)
|
||||
{
|
||||
$alias = 'act_period_having_act_betw_date_alias';
|
||||
$from = 'act_period_having_act_betw_date_start';
|
||||
$to = 'act_period_having_act_betw_date_end';
|
||||
|
||||
$qb->andWhere(
|
||||
$qb->expr()->exists(
|
||||
'SELECT 1 FROM ' . Activity::class . " {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = acp"
|
||||
)
|
||||
);
|
||||
|
||||
$qb
|
||||
->setParameter($from, $this->rollingDateConverter->convert($data['start_date']))
|
||||
->setParameter($to, $this->rollingDateConverter->convert($data['end_date']));
|
||||
}
|
||||
|
||||
public function applyOn()
|
||||
{
|
||||
return \Chill\PersonBundle\Export\Declarations::ACP_TYPE;
|
||||
}
|
||||
}
|
@@ -18,67 +18,192 @@ 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
|
||||
) {
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->repository = $repository;
|
||||
$this->em = $em;
|
||||
$this->security = $security;
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelper,
|
||||
private CenterResolverManagerInterface $centerResolverManager,
|
||||
private ActivityRepository $repository,
|
||||
private EntityManagerInterface $em,
|
||||
private Security $security,
|
||||
private RequestStack $requestStack,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws NonUniqueResultException
|
||||
* @throws NoResultException
|
||||
*/
|
||||
public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int
|
||||
{
|
||||
$qb = $this->buildBaseQuery($filters);
|
||||
|
||||
$qb
|
||||
->select('COUNT(a)')
|
||||
->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
|
||||
public function countByPerson(Person $person, 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 = $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 +284,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 +399,6 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
|
||||
|
||||
// acls:
|
||||
$reachableCenters = $this->authorizationHelper->getReachableCenters(
|
||||
$this->tokenStorage->getToken()->getUser(),
|
||||
ActivityVoter::SEE
|
||||
);
|
||||
|
||||
@@ -251,7 +423,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;
|
||||
}
|
||||
|
@@ -43,8 +43,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
|
||||
private CenterResolverManagerInterface $centerResolverManager,
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
|
||||
private Security $security
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
|
||||
{
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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>
|
||||
|
@@ -36,8 +36,7 @@ final class AccompanyingPeriodActivityGenericDocProvider implements GenericDocFo
|
||||
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
|
||||
{
|
||||
|
@@ -29,8 +29,7 @@ final readonly class PersonActivityGenericDocProvider implements GenericDocForPe
|
||||
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
|
||||
{
|
||||
|
@@ -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')]];
|
||||
}
|
||||
}
|
@@ -122,5 +122,4 @@ class ActivityDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
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;
|
||||
}
|
||||
|
@@ -135,6 +135,10 @@ services:
|
||||
tags:
|
||||
- { name: chill.export_filter, alias: 'accompanyingcourse_has_no_activity_filter' }
|
||||
|
||||
Chill\ActivityBundle\Export\Filter\ACPFilters\PeriodHavingActivityBetweenDatesFilter:
|
||||
tags:
|
||||
- { name: chill.export_filter, alias: 'period_having_activity_betw_dates_filter' }
|
||||
|
||||
## Aggregators
|
||||
Chill\ActivityBundle\Export\Aggregator\PersonAggregators\ActivityReasonAggregator:
|
||||
tags:
|
||||
@@ -144,6 +148,10 @@ services:
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: activity_common_type_aggregator }
|
||||
|
||||
Chill\ActivityBundle\Export\Aggregator\ActivityLocationAggregator:
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: activity_common_location_aggregator }
|
||||
|
||||
chill.activity.export.user_aggregator:
|
||||
class: Chill\ActivityBundle\Export\Aggregator\ActivityUserAggregator
|
||||
tags:
|
||||
|
@@ -0,0 +1,5 @@
|
||||
export:
|
||||
filter:
|
||||
activity:
|
||||
course_having_activity_between_date:
|
||||
Only course having an activity between from and to: Seulement les parcours ayant reçu au moins un échange entre le {from, date, short} et le {to, date, short}
|
@@ -83,12 +83,20 @@ 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é
|
||||
|
||||
#timeline
|
||||
'%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"'
|
||||
|
||||
@@ -365,6 +373,12 @@ export:
|
||||
by_usersscope:
|
||||
Filter by users scope: Filtrer les échanges par services d'au moins un utilisateur participant
|
||||
'Filtered activity by users scope: only %scopes%': 'Filtré par service d''au moins un utilisateur participant: seulement %scopes%'
|
||||
course_having_activity_between_date:
|
||||
Title: Filtre les parcours ayant reçu un échange entre deux dates
|
||||
Receiving an activity after: Ayant reçu un échange après le
|
||||
Receiving an activity before: Ayant reçu un échange avant le
|
||||
|
||||
|
||||
aggregator:
|
||||
activity:
|
||||
by_sent_received:
|
||||
@@ -372,6 +386,9 @@ export:
|
||||
is sent: envoyé
|
||||
is received: reçu
|
||||
Group activity by sentreceived: Grouper les échanges par envoyé / reçu
|
||||
by_location:
|
||||
Activity Location: Localisation de l'échange
|
||||
Title: Grouper les échanges par localisation de l'échange
|
||||
|
||||
generic_doc:
|
||||
filter:
|
||||
|
@@ -13,6 +13,4 @@ namespace Chill\AsideActivityBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillAsideActivityBundle extends Bundle
|
||||
{
|
||||
}
|
||||
class ChillAsideActivityBundle extends Bundle {}
|
||||
|
@@ -33,6 +33,7 @@ final class AsideActivityController extends CRUDController
|
||||
$asideActivity = new AsideActivity();
|
||||
|
||||
$asideActivity->setAgent($this->getUser());
|
||||
$asideActivity->setLocation($this->getUser()->getCurrentLocation());
|
||||
|
||||
$duration = $request->query->get('duration', '300');
|
||||
$duration = DateTime::createFromFormat('U', $duration);
|
||||
|
@@ -14,6 +14,7 @@ namespace Chill\AsideActivityBundle\Entity;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
@@ -60,9 +61,10 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=100, nullable=true)
|
||||
* @ORM\ManyToOne(targetEntity=Location::class)
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private $location;
|
||||
private ?Location $location = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=true)
|
||||
@@ -115,7 +117,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getLocation(): ?string
|
||||
public function getLocation(): ?Location
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
@@ -175,7 +177,7 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setLocation(?string $location): self
|
||||
public function setLocation(?Location $location): self
|
||||
{
|
||||
$this->location = $location;
|
||||
|
||||
|
@@ -0,0 +1,98 @@
|
||||
<?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\AsideActivityBundle\Export\Aggregator;
|
||||
|
||||
use Chill\AsideActivityBundle\Export\Declarations;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Closure;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class ByLocationAggregator implements AggregatorInterface
|
||||
{
|
||||
public function __construct(private LocationRepository $locationRepository) {}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder): void
|
||||
{
|
||||
// no form
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
return function ($value): string {
|
||||
if ('_header' === $value) {
|
||||
return 'export.aggregator.Aside activity localisation';
|
||||
}
|
||||
if (null === $value || '' === $value || null === $l = $this->locationRepository->find($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $l->getName();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return ['by_aside_activity_location_aggregator'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'export.aggregator.Group by aside activity location';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function alterQuery(QueryBuilder $qb, $data): void
|
||||
{
|
||||
$qb->addSelect('IDENTITY(aside.location) AS by_aside_activity_location_aggregator')
|
||||
->addGroupBy('by_aside_activity_location_aggregator');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function applyOn(): string
|
||||
{
|
||||
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||
}
|
||||
}
|
@@ -31,9 +31,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -31,9 +31,7 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -23,6 +23,7 @@ use Chill\MainBundle\Export\Helper\DateTimeHelper;
|
||||
use Chill\MainBundle\Export\Helper\UserHelper;
|
||||
use Chill\MainBundle\Export\ListInterface;
|
||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use DateTimeInterface;
|
||||
@@ -58,6 +59,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||
CenterRepositoryInterface $centerRepository,
|
||||
AsideActivityCategoryRepository $asideActivityCategoryRepository,
|
||||
CategoryRender $categoryRender,
|
||||
private LocationRepository $locationRepository,
|
||||
TranslatableStringHelperInterface $translatableStringHelper
|
||||
) {
|
||||
$this->em = $em;
|
||||
@@ -70,9 +72,8 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
@@ -145,6 +146,19 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||
return $this->categoryRender->renderString($c, []);
|
||||
};
|
||||
|
||||
case 'location':
|
||||
return function ($value) {
|
||||
if ('_header' === $value) {
|
||||
return 'export.aside_activity.location';
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value || null === $l = $this->locationRepository->find($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $l->getName();
|
||||
};
|
||||
|
||||
case 'main_scope':
|
||||
return function ($value) {
|
||||
if ('_header' === $value) {
|
||||
@@ -191,6 +205,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||
'date',
|
||||
'duration',
|
||||
'note',
|
||||
'location',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -226,6 +241,7 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
|
||||
->addSelect('IDENTITY(aside.type) AS aside_activity_type')
|
||||
->addSelect('aside.date')
|
||||
->addSelect('aside.duration')
|
||||
->addSelect('IDENTITY(aside.location) AS location')
|
||||
->addSelect('aside.note');
|
||||
|
||||
return $qb;
|
||||
|
@@ -31,9 +31,7 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
|
@@ -0,0 +1,104 @@
|
||||
<?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\AsideActivityBundle\Export\Filter;
|
||||
|
||||
use Chill\AsideActivityBundle\Export\Declarations;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
|
||||
use Chill\MainBundle\Repository\LocationRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class ByLocationFilter implements FilterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'export.filter.Filter by aside activity location';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function buildForm(FormBuilderInterface $builder): void
|
||||
{
|
||||
$builder
|
||||
->add('locations', PickUserLocationType::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if ($user instanceof User) {
|
||||
return [
|
||||
'locations' => $user->getCurrentLocation(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'locations' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
$locations = $data['locations']->map(fn (Location $l): string => $l->getName());
|
||||
|
||||
return ['export.filter.Filtered by aside activity location: only %location%', [
|
||||
'%location%' => implode(', ', $locations),
|
||||
]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function alterQuery(QueryBuilder $qb, $data): void
|
||||
{
|
||||
$clause = $qb->expr()->in('aside.location', ':locations');
|
||||
|
||||
$qb->andWhere($clause);
|
||||
$qb->setParameter('locations', $data['locations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function applyOn(): string
|
||||
{
|
||||
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||
}
|
||||
}
|
@@ -13,13 +13,16 @@ namespace Chill\AsideActivityBundle\Form;
|
||||
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||
use Chill\AsideActivityBundle\Form\Type\PickAsideActivityCategoryType;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
||||
@@ -77,7 +80,9 @@ final class AsideActivityFormType extends AbstractType
|
||||
->add('note', ChillTextareaType::class, [
|
||||
'label' => 'Note',
|
||||
'required' => false,
|
||||
]);
|
||||
])
|
||||
->add('location', PickUserLocationType::class)
|
||||
;
|
||||
|
||||
foreach (['duration'] as $fieldName) {
|
||||
$builder->get($fieldName)
|
||||
|
@@ -39,6 +39,9 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{%- if entity.location.name is defined -%}
|
||||
<div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
<div class="item-col" style="justify-content: flex-end;">
|
||||
<div class="box">
|
||||
|
@@ -23,6 +23,13 @@
|
||||
<dt class="inline">{{ 'Created for'|trans }}</dt>
|
||||
<dd>{{ entity.agent }}</dd>
|
||||
|
||||
<dt class="inline">{{ 'Asideactivity location'|trans }}</dt>
|
||||
{%- if entity.location.name is defined -%}
|
||||
<dd>{{ entity.location.name }}</dd>
|
||||
{%- else -%}
|
||||
<dd><span class="chill-no-data-statement">{{ 'No data given'|trans }}</span></dd>
|
||||
{%- endif -%}
|
||||
|
||||
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
|
||||
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
|
@@ -46,19 +46,27 @@ services:
|
||||
tags:
|
||||
- { name: chill.export_filter, alias: 'aside_activity_user_filter' }
|
||||
|
||||
Chill\AsideActivityBundle\Export\Filter\ByLocationFilter:
|
||||
tags:
|
||||
- { name: chill.export_filter, alias: 'aside_activity_location_filter' }
|
||||
|
||||
## Aggregators
|
||||
|
||||
chill.aside_activity.export.type_aggregator:
|
||||
class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: activity_type_aggregator }
|
||||
- { name: chill.export_aggregator, alias: 'activity_type_aggregator' }
|
||||
|
||||
chill.aside_activity.export.user_job_aggregator:
|
||||
class: Chill\AsideActivityBundle\Export\Aggregator\ByUserJobAggregator
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: aside_activity_user_job_aggregator }
|
||||
- { name: chill.export_aggregator, alias: 'aside_activity_user_job_aggregator' }
|
||||
|
||||
chill.aside_activity.export.user_scope_aggregator:
|
||||
class: Chill\AsideActivityBundle\Export\Aggregator\ByUserScopeAggregator
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: aside_activity_user_scope_aggregator }
|
||||
- { name: chill.export_aggregator, alias: 'aside_activity_user_scope_aggregator' }
|
||||
|
||||
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
|
||||
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\AsideActivity;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20230816112809 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Update location attribute in asideactivity';
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP INDEX chill_asideactivity.IDX_A866DA0E64D218E');
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP CONSTRAINT FK_A866DA0E64D218E');
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP location_id');
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity ADD location VARCHAR(100) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD location_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity DROP location');
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD CONSTRAINT FK_A866DA0E64D218E FOREIGN KEY (location_id) REFERENCES chill_main_location (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_A866DA0E64D218E ON chill_asideactivity.asideactivity (location_id)');
|
||||
}
|
||||
}
|
@@ -26,6 +26,7 @@ Users: Utilisateurs
|
||||
Emergency: Urgent
|
||||
by: "Par "
|
||||
location: Lieu
|
||||
Asideactivity location: Localisation de l'activité
|
||||
|
||||
# Crud
|
||||
crud:
|
||||
@@ -176,11 +177,13 @@ 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
|
||||
location: Localisation
|
||||
|
||||
Exports of aside activities: Exports des activités annexes
|
||||
Count aside activities: Nombre d'activités annexes
|
||||
@@ -201,11 +204,16 @@ export:
|
||||
Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs
|
||||
'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateur: uniquement %scopes%"
|
||||
Filter by user scope: Filtrer les activités annexes par service d'utilisateur
|
||||
Filter by aside activity location: Filtrer les activités annexes par localisation
|
||||
'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
|
||||
aggregator:
|
||||
Group by aside activity type: Grouper les activités annexes par type d'activité
|
||||
Aside activity type: Type d'activité annexe
|
||||
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
|
||||
Aggregate by user scope: Grouper les activités annexes par service des utilisateurs
|
||||
Aside activity location: Localisation des activités annexe
|
||||
Group by aside activity location: Grouper les activités annexes par localisation
|
||||
Aside activity localisation: Localisation
|
||||
|
||||
# ROLES
|
||||
CHILL_ASIDE_ACTIVITY_STATS: Statistiques pour les activités annexes
|
||||
|
@@ -1,11 +1,12 @@
|
||||
{% macro table_elements(elements, family) %}
|
||||
{% macro table_elements(elements, type) %}
|
||||
|
||||
<table class="table table-bordered border-dark budget-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="{{ family }} el-type">{{ 'Budget element type'|trans }}</th>
|
||||
<th class="{{ family }}">{{ 'Amount'|trans }}</th>
|
||||
<th class="{{ family }}">{{ 'Validity period'|trans }}</th>
|
||||
<th class="{{ family }}"> </th>
|
||||
<th class="{{ type }} el-type">{{ 'Budget element type'|trans }}</th>
|
||||
<th class="{{ type }}">{{ 'Amount'|trans }}</th>
|
||||
<th class="{{ type }}">{{ 'Validity period'|trans }}</th>
|
||||
<th class="{{ type }}"> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -38,17 +39,17 @@
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_BUDGET_ELEMENT_SEE', f) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_budget_' ~ family ~ '_view', { 'id': f.id } ) }}" class="btn btn-sm btn-show"></a>
|
||||
<a href="{{ path('chill_budget_' ~ type ~ '_view', { 'id': f.id } ) }}" class="btn btn-sm btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_BUDGET_ELEMENT_UPDATE', f) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_budget_' ~ family ~'_edit', { 'id': f.id } ) }}" class="btn btn-sm btn-edit"></a>
|
||||
<a href="{{ path('chill_budget_' ~ type ~'_edit', { 'id': f.id } ) }}" class="btn btn-sm btn-edit"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_BUDGET_ELEMENT_DELETE', f) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_budget_' ~ family ~ '_delete', { 'id': f.id } ) }}" class="btn btn-sm btn-delete"></a>
|
||||
<a href="{{ path('chill_budget_' ~ type ~ '_delete', { 'id': f.id } ) }}" class="btn btn-sm btn-delete"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
@@ -69,7 +70,7 @@
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro table_results(actualCharges, actualResources) %}
|
||||
{% macro table_results(actualCharges, actualResources, results) %}
|
||||
|
||||
{% set totalCharges = 0 %}
|
||||
{% for c in actualCharges %}
|
||||
@@ -97,6 +98,20 @@
|
||||
{{ result|format_currency('EUR') }}
|
||||
</td>
|
||||
</tr>
|
||||
{% for result in results %}
|
||||
<tr>
|
||||
<td>{{ result.label }}</td>
|
||||
<td>
|
||||
{% if result.type == 'currency' %}
|
||||
{{ result.result|format_currency('EUR') }}
|
||||
{% elseif result.type == 'percentage' %}
|
||||
{{ result.result|round(2, 'ceil') ~ '%' }}
|
||||
{% else %}
|
||||
{{ result.result|round(2, 'common') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
@@ -25,7 +25,7 @@
|
||||
|
||||
<div class="mt-5">
|
||||
<h3 class="subtitle">{{ 'Budget calculator'|trans }}</h3>
|
||||
{{ table_results(charges, resources) }}
|
||||
{{ table_results(charges, resources, results) }}
|
||||
</div>
|
||||
|
||||
{% if is_granted('CHILL_BUDGET_ELEMENT_CREATE', person) %}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -29,11 +29,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
class CalendarRangeFilter implements FilterInterface
|
||||
{
|
||||
private const CHOICES = [
|
||||
'Not made within a calendar range' => true,
|
||||
'Made within a calendar range' => false,
|
||||
'Not made within a calendar range' => 'true',
|
||||
'Made within a calendar range' => 'false',
|
||||
];
|
||||
|
||||
private const DEFAULT_CHOICE = false;
|
||||
private const DEFAULT_CHOICE = 'false';
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
|
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use DateInterval;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Query\ResultSetMapping;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use function strtr;
|
||||
|
||||
/**
|
||||
* Contains classes and methods for fetching users with some calendar metadatas.
|
||||
*/
|
||||
class MSGraphUserRepository
|
||||
{
|
||||
private const MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH = <<<'SQL'
|
||||
select
|
||||
{select}
|
||||
from users u
|
||||
where
|
||||
NOT attributes ?? 'msgraph'
|
||||
OR NOT attributes->'msgraph' ?? 'subscription_events_expiration'
|
||||
OR (attributes->'msgraph' ?? 'subscription_events_expiration' AND (attributes->'msgraph'->>'subscription_events_expiration')::int < EXTRACT(EPOCH FROM (NOW() + :interval::interval)))
|
||||
LIMIT :limit OFFSET :offset
|
||||
;
|
||||
SQL;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function countByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval): int
|
||||
{
|
||||
$rsm = new ResultSetMapping();
|
||||
$rsm->addScalarResult('c', 'c');
|
||||
|
||||
$sql = strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, [
|
||||
'{select}' => 'COUNT(u) AS c',
|
||||
'LIMIT :limit OFFSET :offset' => '',
|
||||
]);
|
||||
|
||||
return $this->entityManager->createNativeQuery($sql, $rsm)->setParameters([
|
||||
'interval' => $interval,
|
||||
])->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|User[]
|
||||
*/
|
||||
public function findByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval, int $limit = 50, int $offset = 0): array
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||
$rsm->addRootEntityFromClassMetadata(User::class, 'u');
|
||||
|
||||
return $this->entityManager->createNativeQuery(
|
||||
strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, ['{select}' => $rsm->generateSelectClause()]),
|
||||
$rsm
|
||||
)->setParameters([
|
||||
'interval' => $interval,
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
])->getResult();
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
<?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\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
final readonly class MSUserAbsenceReader implements MSUserAbsenceReaderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HttpClientInterface $machineHttpClient,
|
||||
private MapCalendarToUser $mapCalendarToUser,
|
||||
private ClockInterface $clock,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft
|
||||
*/
|
||||
public function isUserAbsent(User $user): bool|null
|
||||
{
|
||||
$id = $this->mapCalendarToUser->getUserId($user);
|
||||
|
||||
if (null === $id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$automaticRepliesSettings = $this->machineHttpClient
|
||||
->request('GET', 'users/' . $id . '/mailboxSettings/automaticRepliesSetting')
|
||||
->toArray(true);
|
||||
} catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|TransportExceptionInterface $e) {
|
||||
throw new UserAbsenceSyncException("Error receiving response for mailboxSettings", 0, $e);
|
||||
} catch (ServerExceptionInterface $e) {
|
||||
throw new UserAbsenceSyncException("Server error receiving response for mailboxSettings", 0, $e);
|
||||
}
|
||||
|
||||
if (!array_key_exists("status", $automaticRepliesSettings)) {
|
||||
throw new \LogicException("no key \"status\" on automatic replies settings: " . json_encode($automaticRepliesSettings, JSON_THROW_ON_ERROR));
|
||||
}
|
||||
|
||||
return match ($automaticRepliesSettings['status']) {
|
||||
'disabled' => false,
|
||||
'alwaysEnabled' => true,
|
||||
'scheduled' =>
|
||||
RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledStartDateTime']['dateTime']) < $this->clock->now()
|
||||
&& RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledEndDateTime']['dateTime']) > $this->clock->now(),
|
||||
default => throw new UserAbsenceSyncException("this status is not documented by Microsoft")
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
<?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\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
interface MSUserAbsenceReaderInterface
|
||||
{
|
||||
/**
|
||||
* @throw UserAbsenceSyncException when the data cannot be reached or is not valid from microsoft
|
||||
*/
|
||||
public function isUserAbsent(User $user): bool|null;
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<?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\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
readonly class MSUserAbsenceSync
|
||||
{
|
||||
public function __construct(
|
||||
private MSUserAbsenceReaderInterface $absenceReader,
|
||||
private ClockInterface $clock,
|
||||
private LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function syncUserAbsence(User $user): void
|
||||
{
|
||||
$absence = $this->absenceReader->isUserAbsent($user);
|
||||
|
||||
if (null === $absence) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($absence === $user->isAbsent()) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->info("will change user absence", ['userId' => $user->getId()]);
|
||||
|
||||
if ($absence) {
|
||||
$this->logger->debug("make user absent", ['userId' => $user->getId()]);
|
||||
$user->setAbsenceStart($this->clock->now());
|
||||
} else {
|
||||
$this->logger->debug("make user present", ['userId' => $user->getId()]);
|
||||
$user->setAbsenceStart(null);
|
||||
}
|
||||
}
|
||||
}
|
@@ -48,23 +48,13 @@ class NullRemoteCalendarConnector implements RemoteCalendarConnectorInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void
|
||||
{
|
||||
}
|
||||
public function removeCalendar(string $remoteId, array $remoteAttributes, User $user, ?CalendarRange $associatedCalendarRange = null): void {}
|
||||
|
||||
public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void
|
||||
{
|
||||
}
|
||||
public function removeCalendarRange(string $remoteId, array $remoteAttributes, User $user): void {}
|
||||
|
||||
public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void
|
||||
{
|
||||
}
|
||||
public function syncCalendar(Calendar $calendar, string $action, ?CalendarRange $previousCalendarRange, ?User $previousMainUser, ?array $oldInvites, ?array $newInvites): void {}
|
||||
|
||||
public function syncCalendarRange(CalendarRange $calendarRange): void
|
||||
{
|
||||
}
|
||||
public function syncCalendarRange(CalendarRange $calendarRange): void {}
|
||||
|
||||
public function syncInvite(Invite $invite): void
|
||||
{
|
||||
}
|
||||
public function syncInvite(Invite $invite): void {}
|
||||
}
|
||||
|
@@ -23,6 +23,8 @@ use Chill\CalendarBundle\Command\MapAndSubscribeUserCalendarCommand;
|
||||
use Chill\CalendarBundle\Controller\RemoteCalendarConnectAzureController;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineHttpClient;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineTokenStorage;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReaderInterface;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraphRemoteCalendarConnector;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\NullRemoteCalendarConnector;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
||||
@@ -37,17 +39,13 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$config = $container->getParameter('chill_calendar');
|
||||
$connector = null;
|
||||
|
||||
if (!$config['remote_calendars_sync']['enabled']) {
|
||||
$connector = NullRemoteCalendarConnector::class;
|
||||
}
|
||||
|
||||
if ($config['remote_calendars_sync']['microsoft_graph']['enabled']) {
|
||||
if (true === $config['remote_calendars_sync']['microsoft_graph']['enabled']) {
|
||||
$connector = MSGraphRemoteCalendarConnector::class;
|
||||
|
||||
$container->setAlias(HttpClientInterface::class . ' $machineHttpClient', MachineHttpClient::class);
|
||||
} else {
|
||||
$connector = NullRemoteCalendarConnector::class;
|
||||
// remove services which cannot be loaded
|
||||
$container->removeDefinition(MapAndSubscribeUserCalendarCommand::class);
|
||||
$container->removeDefinition(AzureGrantAdminConsentAndAcquireToken::class);
|
||||
@@ -55,16 +53,14 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface
|
||||
$container->removeDefinition(MachineTokenStorage::class);
|
||||
$container->removeDefinition(MachineHttpClient::class);
|
||||
$container->removeDefinition(MSGraphRemoteCalendarConnector::class);
|
||||
$container->removeDefinition(MSUserAbsenceReaderInterface::class);
|
||||
$container->removeDefinition(MSUserAbsenceSync::class);
|
||||
}
|
||||
|
||||
if (!$container->hasAlias(Azure::class) && $container->hasDefinition('knpu.oauth2.client.azure')) {
|
||||
$container->setAlias(Azure::class, 'knpu.oauth2.provider.azure');
|
||||
}
|
||||
|
||||
if (null === $connector) {
|
||||
throw new RuntimeException('Could not configure remote calendar');
|
||||
}
|
||||
|
||||
foreach ([
|
||||
NullRemoteCalendarConnector::class,
|
||||
MSGraphRemoteCalendarConnector::class, ] as $serviceId) {
|
||||
|
@@ -23,6 +23,4 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
* @extends DocGeneratorContextWithPublicFormInterface<Calendar>
|
||||
* @extends DocGeneratorContextWithAdminFormInterface<Calendar>
|
||||
*/
|
||||
interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInterface, DocGeneratorContextWithAdminFormInterface
|
||||
{
|
||||
}
|
||||
interface CalendarContextInterface extends DocGeneratorContextWithPublicFormInterface, DocGeneratorContextWithAdminFormInterface {}
|
||||
|
@@ -39,8 +39,7 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $em
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws MappingException
|
||||
@@ -147,6 +146,9 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->addWhereClause(implode(" OR ", $or), $orParams, $orTypes);
|
||||
|
||||
return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
@@ -187,6 +189,4 @@ final readonly class AccompanyingPeriodCalendarGenericDocProvider implements Gen
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -39,8 +39,7 @@ final readonly class PersonCalendarGenericDocProvider implements GenericDocForPe
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $em
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
private function addWhereClausesToQuery(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
|
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReader;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class MSUserAbsenceReaderTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataTestUserAbsence
|
||||
*/
|
||||
public function testUserAbsenceReader(string $mockResponse, bool $expected, string $message): void
|
||||
{
|
||||
$user = new User();
|
||||
$client = new MockHttpClient([new MockResponse($mockResponse)]);
|
||||
$mapUser = $this->prophesize(MapCalendarToUser::class);
|
||||
$mapUser->getUserId($user)->willReturn('1234');
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
|
||||
|
||||
$absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
|
||||
|
||||
self::assertEquals($expected, $absenceReader->isUserAbsent($user), $message);
|
||||
}
|
||||
|
||||
public function testIsUserAbsentWithoutRemoteId(): void
|
||||
{
|
||||
$user = new User();
|
||||
$client = new MockHttpClient();
|
||||
|
||||
$mapUser = $this->prophesize(MapCalendarToUser::class);
|
||||
$mapUser->getUserId($user)->willReturn(null);
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
|
||||
|
||||
$absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
|
||||
|
||||
self::assertNull($absenceReader->isUserAbsent($user), "when no user found, absence should be null");
|
||||
}
|
||||
|
||||
public function provideDataTestUserAbsence(): iterable
|
||||
{
|
||||
// contains data that was retrieved from microsoft graph api on 2023-07-06
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "disabled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-06T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-07T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
false,
|
||||
"User is present"
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "scheduled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-06T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-21T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
true,
|
||||
'User is absent with absence scheduled, we are within this period'
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "scheduled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-08T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-21T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
false,
|
||||
'User is present: absence is scheduled for later'
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "scheduled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-05T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-06T11:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
false,
|
||||
'User is present: absence is past'
|
||||
];
|
||||
|
||||
yield [
|
||||
<<<'JSON'
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
|
||||
"status": "alwaysEnabled",
|
||||
"externalAudience": "none",
|
||||
"internalReplyMessage": "Je suis en congé.",
|
||||
"externalReplyMessage": "",
|
||||
"scheduledStartDateTime": {
|
||||
"dateTime": "2023-07-06T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
},
|
||||
"scheduledEndDateTime": {
|
||||
"dateTime": "2023-07-07T12:00:00.0000000",
|
||||
"timeZone": "UTC"
|
||||
}
|
||||
}
|
||||
JSON,
|
||||
true,
|
||||
"User is absent: absence is always enabled"
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\CalendarBundle\Tests\RemoteCalendar\Connector\MSGraph;
|
||||
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReader;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReaderInterface;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class MSUserAbsenceSyncTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataTestSyncUserAbsence
|
||||
*/
|
||||
public function testSyncUserAbsence(User $user, ?bool $absenceFromMicrosoft, bool $expectedAbsence, ?\DateTimeImmutable $expectedAbsenceStart, string $message): void
|
||||
{
|
||||
$userAbsenceReader = $this->prophesize(MSUserAbsenceReaderInterface::class);
|
||||
$userAbsenceReader->isUserAbsent($user)->willReturn($absenceFromMicrosoft);
|
||||
|
||||
$clock = new MockClock(new \DateTimeImmutable('2023-07-01T12:00:00'));
|
||||
|
||||
$syncer = new MSUserAbsenceSync($userAbsenceReader->reveal(), $clock, new NullLogger());
|
||||
|
||||
$syncer->syncUserAbsence($user);
|
||||
|
||||
self::assertEquals($expectedAbsence, $user->isAbsent(), $message);
|
||||
self::assertEquals($expectedAbsenceStart, $user->getAbsenceStart(), $message);
|
||||
}
|
||||
|
||||
public function provideDataTestSyncUserAbsence(): iterable
|
||||
{
|
||||
yield [new User(), false, false, null, "user present remains present"];
|
||||
yield [new User(), true, true, new \DateTimeImmutable('2023-07-01T12:00:00'), "user present becomes absent"];
|
||||
|
||||
$user = new User();
|
||||
$user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00"));
|
||||
yield [$user, true, true, $abs, "user absent remains absent"];
|
||||
|
||||
$user = new User();
|
||||
$user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00"));
|
||||
yield [$user, false, false, null, "user absent becomes present"];
|
||||
|
||||
yield [new User(), null, false, null, "user not syncable: presence do not change"];
|
||||
|
||||
$user = new User();
|
||||
$user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00"));
|
||||
yield [$user, null, true, $abs, "user not syncable: absence do not change"];
|
||||
}
|
||||
}
|
@@ -16,9 +16,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class CustomFieldsTitleType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
}
|
||||
public function buildForm(FormBuilderInterface $builder, array $options) {}
|
||||
|
||||
public function getBlockPrefix()
|
||||
{
|
||||
|
@@ -31,8 +31,7 @@ final readonly class GenericDocForAccompanyingPeriodController
|
||||
private PaginatorFactory $paginator,
|
||||
private Security $security,
|
||||
private EngineInterface $twig,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param AccompanyingPeriod $accompanyingPeriod
|
||||
@@ -94,5 +93,4 @@ final readonly class GenericDocForAccompanyingPeriodController
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -30,8 +30,7 @@ final readonly class GenericDocForPerson
|
||||
private PaginatorFactory $paginator,
|
||||
private Security $security,
|
||||
private EngineInterface $twig,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
@@ -91,5 +90,4 @@ final readonly class GenericDocForPerson
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -54,8 +54,7 @@ class FetchQuery implements FetchQueryInterface
|
||||
private array $selectIdentifierTypes = [],
|
||||
private array $selectDateParams = [],
|
||||
private array $selectDateTypes = [],
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function addJoinClause(string $sql, array $params = [], array $types = []): int
|
||||
{
|
||||
@@ -82,7 +81,6 @@ class FetchQuery implements FetchQueryInterface
|
||||
}
|
||||
|
||||
unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]);
|
||||
|
||||
}
|
||||
|
||||
public function removeJoinClause(int $index): void
|
||||
@@ -92,7 +90,6 @@ class FetchQuery implements FetchQueryInterface
|
||||
}
|
||||
|
||||
unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]);
|
||||
|
||||
}
|
||||
|
||||
public function getSelectKeyString(): string
|
||||
|
@@ -21,8 +21,7 @@ final readonly class GenericDocDTO
|
||||
public array $identifiers,
|
||||
public \DateTimeImmutable $docDate,
|
||||
public AccompanyingPeriod|Person $linked,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function getContext(): string
|
||||
{
|
||||
|
@@ -27,5 +27,4 @@ interface GenericDocForAccompanyingPeriodProviderInterface
|
||||
* Return true if the user is allowed to see some documents for this provider.
|
||||
*/
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool;
|
||||
|
||||
}
|
||||
|
@@ -32,8 +32,7 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
@@ -143,5 +142,4 @@ final readonly class AccompanyingCourseDocumentGenericDocProvider implements Gen
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -28,8 +28,7 @@ final readonly class PersonDocumentGenericDocProvider implements GenericDocForPe
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function buildFetchQueryForPerson(
|
||||
Person $person,
|
||||
|
@@ -24,8 +24,7 @@ final readonly class AccompanyingCourseDocumentGenericDocRenderer implements Gen
|
||||
public function __construct(
|
||||
private AccompanyingCourseDocumentRepository $accompanyingCourseDocumentRepository,
|
||||
private PersonDocumentRepository $personDocumentRepository,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool
|
||||
{
|
||||
|
@@ -25,8 +25,7 @@ final readonly class GenericDocExtensionRuntime implements RuntimeExtensionInter
|
||||
* @var list<GenericDocRendererInterface>
|
||||
*/
|
||||
private iterable $renderers,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws RuntimeError
|
||||
@@ -46,5 +45,4 @@ final readonly class GenericDocExtensionRuntime implements RuntimeExtensionInter
|
||||
|
||||
throw new \LogicException("no renderer found");
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,5 +20,4 @@ interface GenericDocRendererInterface
|
||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
|
||||
|
||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
|
||||
|
||||
}
|
||||
|
@@ -38,8 +38,7 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
|
||||
private CenterResolverManagerInterface $centerResolverManager,
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
) {}
|
||||
|
||||
public function buildQueryByPerson(Person $person): QueryBuilder
|
||||
{
|
||||
@@ -98,7 +97,6 @@ final readonly class PersonDocumentACLAwareRepository implements PersonDocumentA
|
||||
continue;
|
||||
}
|
||||
$orPersonId[] = $participation->getPerson()->getId();
|
||||
|
||||
}
|
||||
|
||||
if ([] === $orPersonId) {
|
||||
|
@@ -54,9 +54,6 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase
|
||||
);
|
||||
self::assertEquals(['foo', 'bar_baz', 'baz'], $params);
|
||||
self::assertEquals([Types::STRING, Types::STRING, Types::STRING], $types);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testToSqlWithoutWhere(): void
|
||||
@@ -85,5 +82,4 @@ class FetchQueryToSqlBuilderTest extends KernelTestCase
|
||||
self::assertEquals([], $params);
|
||||
self::assertEquals([], $types);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -142,7 +142,6 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null];
|
||||
yield [$period, null, null, 'test'];
|
||||
yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
|
||||
|
||||
}
|
||||
|
||||
public function provideDataBuildFetchQueryForPerson(): iterable
|
||||
@@ -154,5 +153,4 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
yield [null, null, 'test'];
|
||||
yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -46,8 +46,7 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
|
||||
|
||||
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
|
||||
{
|
||||
$course = $this->getRelatedEntity($entityWorkflow)
|
||||
->getCourse();
|
||||
$course = $this->getRelatedEntity($entityWorkflow)?->getCourse();
|
||||
$persons = [];
|
||||
|
||||
if (null !== $course) {
|
||||
|
@@ -13,6 +13,4 @@ namespace Chill\EventBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillEventBundle extends Bundle
|
||||
{
|
||||
}
|
||||
class ChillEventBundle extends Bundle {}
|
||||
|
@@ -16,6 +16,8 @@ use Chill\EventBundle\Entity\Event;
|
||||
use Chill\EventBundle\Entity\Participation;
|
||||
use Chill\EventBundle\Form\ParticipationType;
|
||||
use Chill\EventBundle\Security\Authorization\ParticipationVoter;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\ReadableCollection;
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
@@ -509,7 +511,7 @@ class ParticipationController extends AbstractController
|
||||
/**
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
protected function createEditFormMultiple(ArrayIterator $participations, Event $event)
|
||||
protected function createEditFormMultiple(Collection $participations, Event $event)
|
||||
{
|
||||
$form = $this->createForm(
|
||||
\Symfony\Component\Form\Extension\Core\Type\FormType::class,
|
||||
@@ -638,6 +640,7 @@ class ParticipationController extends AbstractController
|
||||
$ignoredParticipations = $newParticipations = [];
|
||||
|
||||
foreach ($participations as $participation) {
|
||||
/** @var Participation $participation */
|
||||
// check for authorization
|
||||
$this->denyAccessUnlessGranted(
|
||||
ParticipationVoter::CREATE,
|
||||
|
@@ -160,11 +160,11 @@ class Event implements HasCenterInterface, HasScopeInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayIterator|Collection|Traversable
|
||||
* @return Collection<Participation>
|
||||
*/
|
||||
public function getParticipations()
|
||||
{
|
||||
return $this->getParticipationsOrdered();
|
||||
return new ArrayCollection(iterator_to_array($this->getParticipationsOrdered()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -16,6 +16,4 @@ use Doctrine\ORM\EntityRepository;
|
||||
/**
|
||||
* Class EventRepository.
|
||||
*/
|
||||
class EventRepository extends EntityRepository
|
||||
{
|
||||
}
|
||||
class EventRepository extends EntityRepository {}
|
||||
|
@@ -152,7 +152,7 @@ class EventSearch extends AbstractSearch
|
||||
$orWhere = $qb->expr()->orX();
|
||||
|
||||
foreach ($reachableCenters as $center) {
|
||||
$n = $n+1;
|
||||
$n = $n + 1;
|
||||
$circles = $this->authorizationHelper->getReachableScopes(
|
||||
$this->security->getUser(),
|
||||
'CHILL_EVENT_SEE',
|
||||
|
@@ -13,6 +13,4 @@ namespace Chill\FamilyMembersBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillAMLIFamilyMembersBundle extends Bundle
|
||||
{
|
||||
}
|
||||
class ChillAMLIFamilyMembersBundle extends Bundle {}
|
||||
|
@@ -150,9 +150,7 @@ abstract class AbstractCRUDController extends AbstractController
|
||||
return new $class();
|
||||
}
|
||||
|
||||
protected function customizeQuery(string $action, Request $request, $query): void
|
||||
{
|
||||
}
|
||||
protected function customizeQuery(string $action, Request $request, $query): void {}
|
||||
|
||||
protected function getActionConfig(string $action)
|
||||
{
|
||||
|
@@ -242,13 +242,9 @@ class CRUDController extends AbstractController
|
||||
/**
|
||||
* Customize the form created by createFormFor.
|
||||
*/
|
||||
protected function customizeForm(string $action, FormInterface $form)
|
||||
{
|
||||
}
|
||||
protected function customizeForm(string $action, FormInterface $form) {}
|
||||
|
||||
protected function customizeQuery(string $action, Request $request, $query): void
|
||||
{
|
||||
}
|
||||
protected function customizeQuery(string $action, Request $request, $query): void {}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
@@ -927,9 +923,7 @@ class CRUDController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
/**
|
||||
* @param $action
|
||||
@@ -952,77 +946,55 @@ class CRUDController extends AbstractController
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
/**
|
||||
* method used by indexAction.
|
||||
*
|
||||
* @param mixed $query
|
||||
*/
|
||||
protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query)
|
||||
{
|
||||
}
|
||||
protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) {}
|
||||
|
||||
/**
|
||||
* method used by indexAction.
|
||||
*
|
||||
* @param mixed $entities
|
||||
*/
|
||||
protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities)
|
||||
{
|
||||
}
|
||||
protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities) {}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
protected function onPreDelete(string $action, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPreDelete(string $action, Request $request) {}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
protected function onPreIndex(string $action, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPreIndex(string $action, Request $request) {}
|
||||
|
||||
/**
|
||||
* method used by indexAction.
|
||||
*/
|
||||
protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator)
|
||||
{
|
||||
}
|
||||
protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) {}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
/**
|
||||
* @param $entity
|
||||
*/
|
||||
protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request)
|
||||
{
|
||||
}
|
||||
protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {}
|
||||
|
||||
/**
|
||||
* Add ordering fields in the query build by self::queryEntities.
|
||||
|
@@ -16,6 +16,4 @@ use Symfony\Component\Form\AbstractType;
|
||||
/**
|
||||
* Class CRUDDeleteEntityForm.
|
||||
*/
|
||||
class CRUDDeleteEntityForm extends AbstractType
|
||||
{
|
||||
}
|
||||
class CRUDDeleteEntityForm extends AbstractType {}
|
||||
|
@@ -213,14 +213,8 @@ class LoadPostalCodesCommand extends Command
|
||||
}
|
||||
}
|
||||
|
||||
class ExistingPostalCodeException extends Exception
|
||||
{
|
||||
}
|
||||
class ExistingPostalCodeException extends Exception {}
|
||||
|
||||
class CountryCodeNotFoundException extends Exception
|
||||
{
|
||||
}
|
||||
class CountryCodeNotFoundException extends Exception {}
|
||||
|
||||
class PostalCodeNotValidException extends Exception
|
||||
{
|
||||
}
|
||||
class PostalCodeNotValidException extends Exception {}
|
||||
|
@@ -11,176 +11,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Form\CenterType;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Class CenterController.
|
||||
*/
|
||||
class CenterController extends AbstractController
|
||||
class CenterController extends CRUDController
|
||||
{
|
||||
/**
|
||||
* Creates a new Center entity.
|
||||
*/
|
||||
public function createAction(Request $request)
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||
{
|
||||
$center = new Center();
|
||||
$form = $this->createCreateForm($center);
|
||||
$form->handleRequest($request);
|
||||
$query->addOrderBy('e.name', 'ASC');
|
||||
|
||||
if ($form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($center);
|
||||
$em->flush();
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_center'));
|
||||
}
|
||||
|
||||
return $this->render('@ChillMain/Center/new.html.twig', [
|
||||
'entity' => $center,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to edit an existing Center entity.
|
||||
*
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function editAction($id)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$center = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->find($id);
|
||||
|
||||
if (!$center) {
|
||||
throw $this->createNotFoundException('Unable to find Center entity.');
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($center);
|
||||
|
||||
return $this->render('@ChillMain/Center/edit.html.twig', [
|
||||
'entity' => $center,
|
||||
'edit_form' => $editForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all Center entities.
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$entities = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->findAll();
|
||||
|
||||
return $this->render('@ChillMain/Center/index.html.twig', [
|
||||
'entities' => $entities,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a form to create a new Center entity.
|
||||
*/
|
||||
public function newAction()
|
||||
{
|
||||
$center = new Center();
|
||||
$form = $this->createCreateForm($center);
|
||||
|
||||
return $this->render('@ChillMain/Center/new.html.twig', [
|
||||
'entity' => $center,
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and displays a Center entity.
|
||||
*
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function showAction($id)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$center = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->find($id);
|
||||
|
||||
if (!$center) {
|
||||
throw $this->createNotFoundException('Unable to find Center entity.');
|
||||
}
|
||||
|
||||
return $this->render('@ChillMain/Center/show.html.twig', [
|
||||
'entity' => $center,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an existing Center entity.
|
||||
*
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function updateAction(Request $request, $id)
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$center = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->find($id);
|
||||
|
||||
if (!$center) {
|
||||
throw $this->createNotFoundException('Unable to find Center entity.');
|
||||
}
|
||||
|
||||
$editForm = $this->createEditForm($center);
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isValid()) {
|
||||
$em->flush();
|
||||
|
||||
return $this->redirect($this->generateUrl('admin_center_edit', ['id' => $id]));
|
||||
}
|
||||
|
||||
return $this->render('@ChillMain/Center/edit.html.twig', [
|
||||
'entity' => $center,
|
||||
'edit_form' => $editForm->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to create a Center entity.
|
||||
*
|
||||
* @param Center $center The entity
|
||||
*
|
||||
* @return \Symfony\Component\Form\Form The form
|
||||
*/
|
||||
private function createCreateForm(Center $center)
|
||||
{
|
||||
$form = $this->createForm(CenterType::class, $center, [
|
||||
'action' => $this->generateUrl('admin_center_create'),
|
||||
'method' => 'POST',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Create']);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a form to edit a Center entity.
|
||||
*
|
||||
* @param Center $center The entity
|
||||
*
|
||||
* @return \Symfony\Component\Form\Form The form
|
||||
*/
|
||||
private function createEditForm(Center $center)
|
||||
{
|
||||
$form = $this->createForm(CenterType::class, $center, [
|
||||
'action' => $this->generateUrl('admin_center_update', ['id' => $center->getId()]),
|
||||
'method' => 'PUT',
|
||||
]);
|
||||
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Update']);
|
||||
|
||||
return $form;
|
||||
return parent::orderQuery($action, $query, $request, $paginator);
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,4 @@ namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
|
||||
class LocationTypeController extends CRUDController
|
||||
{
|
||||
}
|
||||
class LocationTypeController extends CRUDController {}
|
||||
|
@@ -44,7 +44,5 @@ class LoginController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
public function LoginCheckAction(Request $request)
|
||||
{
|
||||
}
|
||||
public function LoginCheckAction(Request $request) {}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user