mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-12 09:44:58 +00:00
Compare commits
351 Commits
optimize_a
...
issue127_A
Author | SHA1 | Date | |
---|---|---|---|
da75f5b17f | |||
5606b714cd | |||
f92c500657 | |||
1146bd666f | |||
|
5e5dcaefe7 | ||
|
a1b381a3ea | ||
|
5615734123 | ||
|
2eb30d2ae8 | ||
|
48ea67968e | ||
|
9fc397f048 | ||
|
a19a5803e8 | ||
|
bf155f6967 | ||
|
6a9e133256 | ||
|
fb4b586cf0 | ||
|
6b60c46ad3 | ||
|
774b5b69d9 | ||
2382e7f44b | |||
|
e2ab3bfc6e | ||
b2d6c82cbe | |||
9247cd3546 | |||
ec6828f128 | |||
7399d0aa9e | |||
19b469259d | |||
5c037ca18f | |||
|
c979de962e | ||
|
061197e52e | ||
e786c40679 | |||
e4f8ada236 | |||
21e419b338 | |||
7fabe0214e | |||
|
fa05a6856a | ||
|
1dbf29beea | ||
|
bddcdd7de2 | ||
092ea4d57f | |||
f16f45f3f3 | |||
5ce2fe3295 | |||
5111a0ff6a | |||
74df496e38 | |||
fd2d74c9f5 | |||
b615f0c79f | |||
97859e9c1f | |||
6642b87168 | |||
35d807cab0 | |||
e1677cd2b6 | |||
df0cb8596b | |||
250bd29ab3 | |||
|
8de78c7584 | ||
|
84ab4f8d1f | ||
261810374f | |||
8f541c8418 | |||
b69cee84b6 | |||
b7e0753dbf | |||
9c57d21e5a | |||
c03b925bd9 | |||
6fdbefa4c8 | |||
13b43ace39 | |||
12c3bfa578 | |||
|
52778a7051 | ||
|
a768563cd0 | ||
|
4011fc6e77 | ||
|
51938a99db | ||
|
c1f4e77293 | ||
|
bfb219746a | ||
|
4e72bdead5 | ||
|
e52880bb53 | ||
|
0439e6a821 | ||
72337a8fba | |||
645603a98f | |||
c6d7de3e58 | |||
8c5bf6786b | |||
7c5d0fce51 | |||
35154f5ae1 | |||
6911ace412 | |||
87e16ec24f | |||
765a450bb9 | |||
|
93880f7010 | ||
|
0231433a6c | ||
b2fe024534 | |||
39e0e01eb8 | |||
|
2aa588037a | ||
|
addb362478 | ||
|
819fc8af46 | ||
e7ad96aaf7 | |||
|
f4bd55262a | ||
eebff23282 | |||
2d33ae26d8 | |||
7a384f9657 | |||
|
c8bc6ac495 | ||
b0471c4851 | |||
6d248ab47b | |||
|
999658e792 | ||
|
1646740382 | ||
94e84fbdf0 | |||
20cebfc517 | |||
e9d20a56ff | |||
576ee49da0 | |||
8214fec217 | |||
e0cb5a88bd | |||
c63b1015e1 | |||
35a27de216 | |||
4dc29435dc | |||
db15a3d53c | |||
059a0b075c | |||
4017f8db48 | |||
|
046f65f5ff | ||
27db6ddf0c | |||
|
c90f9ee5bc | ||
|
0bc8986aa7 | ||
|
56019cad99 | ||
4da32dc5ca | |||
e63d645f8a | |||
97dbc4bc16 | |||
2bbc6bd158 | |||
aea5e9b1d7 | |||
4287ca37ae | |||
07ed6b0fdf | |||
8e0a22974b | |||
b4b661882a | |||
f4fb375fd1 | |||
691c5ffd21 | |||
a09c824726 | |||
59118c8eed | |||
c4c75199d6 | |||
333a4d94b2 | |||
3f869e9ca5 | |||
5cf94a5ed6 | |||
965ea528e3 | |||
6921817853 | |||
|
532efb1787 | ||
|
0fa3ff4c16 | ||
|
d2eb7b471e | ||
|
816855e6f8 | ||
|
f6806f7743 | ||
|
69ae252da6 | ||
|
b800b62e88 | ||
|
6a1cf4eb92 | ||
|
f44566037c | ||
c1a845e3e1 | |||
4096e0d4df | |||
00ad7a74d6 | |||
|
1b7cc2be7a | ||
|
526ec52c8d | ||
|
82a679c233 | ||
|
21f463ccb9 | ||
|
911e709a74 | ||
|
c46065d9c5 | ||
|
625c43c0b9 | ||
bae06fcc9c | |||
2f053d3b5f | |||
c4da6a436b | |||
4d55b0582c | |||
693a2889bc | |||
|
913eb5f229 | ||
d009d09215 | |||
|
8c96aae988 | ||
|
ece7ec032d | ||
|
154416cddf | ||
5869236af1 | |||
|
7dc4590580 | ||
|
e24fd8aff0 | ||
|
e9322e61d4 | ||
c38196006c | |||
a754bc0abe | |||
f09329daee | |||
b40ed48776 | |||
97cf6e52ac | |||
|
92843677f9 | ||
|
e06a98e70a | ||
|
821480c06f | ||
|
db67ed3194 | ||
|
8962c1c05f | ||
dded4fb804 | |||
02ca9add52 | |||
3f138dc152 | |||
|
d1e2257db6 | ||
0a058bad82 | |||
4f21c7f219 | |||
c8762d2bc2 | |||
94c91d5825 | |||
2227d5c66f | |||
47f7837ab2 | |||
f889d43f6c | |||
eecb11436e | |||
220dafeb99 | |||
8be11314c3 | |||
f89e92e88b | |||
acdb4fdd36 | |||
b73332c4a3 | |||
8fb5a1b5e8 | |||
5d494221d2 | |||
|
fcc8f67094 | ||
|
99b99de9be | ||
|
31fd9cee57 | ||
809eb9643e | |||
89b0b94d22 | |||
e6e5bfee4f | |||
9cbac89cae | |||
8f361567ff | |||
8c87eaa46f | |||
6565e3f8e3 | |||
6876c23adb | |||
d86307327c | |||
0e18a3ada9 | |||
4677fadf6f | |||
3e4cb41f7a | |||
ada9efc75d | |||
d0dd99db9a | |||
e6845326d7 | |||
61488e9876 | |||
a4af30e542 | |||
58a8949cac | |||
c179649c56 | |||
05b2b2f9b8 | |||
6430de94a7 | |||
62d3bf8742 | |||
0437e00dcd | |||
dc7bcb5233 | |||
b3bd4a5b49 | |||
4f940b44ad | |||
a75f4015c7 | |||
eaeb6c18b0 | |||
9a02665053 | |||
6fccc70e41 | |||
13b96637bb | |||
4d4662a634 | |||
b418d13190 | |||
48f3c440a2 | |||
342c462ed7 | |||
49b1b6f413 | |||
7c21818f00 | |||
2d0895b83d | |||
01ff88074b | |||
e570f5c28e | |||
b84d0100e0 | |||
88d073b9a9 | |||
|
210c199a35 | ||
|
e4a05a1812 | ||
82ae300bd9 | |||
85538f74b7 | |||
f1773997ce | |||
ae7774ac39 | |||
9eec15873e | |||
e9192c5011 | |||
f4369553e1 | |||
bc608832c2 | |||
313c17826e | |||
acbb33918f | |||
cb29e1e8be | |||
799ed0de5c | |||
76f84934d4 | |||
|
62c0410bf5 | ||
|
f7954d5159 | ||
992b320820 | |||
47897b5fe3 | |||
b5394a3de2 | |||
2db9a98bfe | |||
0c5e5ac672 | |||
ffb99e26fb | |||
a27b228a6f | |||
abedf83dbd | |||
80a05f76ba | |||
|
30fcdfd1f6 | ||
0dbff5a6a7 | |||
d7ae279101 | |||
08764aa0b4 | |||
c54edd8407 | |||
e465170c7b | |||
72e9346b6b | |||
0633fd812f | |||
4d71a1c630 | |||
e286acf9fe | |||
7812442c9a | |||
9a708ca618 | |||
58ddbfb67b | |||
2820ad83de | |||
ca3ced0308 | |||
7ea2c36c5e | |||
a8edef13a3 | |||
05b9476a71 | |||
cc7e38194f | |||
78b651bdc4 | |||
487103375d | |||
bfe5e0f515 | |||
7ed61398ea | |||
1eba6a35aa | |||
158fbfd241 | |||
02268792ba | |||
afe076041f | |||
800ab896bc | |||
|
acbd6e761a | ||
8f889248fb | |||
e12e5798ea | |||
52c1a01980 | |||
84189faae6 | |||
3a07fa4faf | |||
c87eda32cb | |||
|
2d4b6d1dd1 | ||
1ffff1f72c | |||
8fca704f74 | |||
d524897838 | |||
fb05d892a2 | |||
48e59bac79 | |||
1ded5b7c01 | |||
|
665b0f8533 | ||
|
748fe28f8d | ||
|
fd73fac1a8 | ||
aa852ec924 | |||
9d6958a835 | |||
|
3770318e6a | ||
|
24320cb702 | ||
7dd42c3fa2 | |||
b510d6a7f5 | |||
72d67624a2 | |||
|
0c4ea81550 | ||
|
7d8f380050 | ||
af4cf55046 | |||
369a29af48 | |||
461b96ea37 | |||
fc237db98a | |||
43daab1f7b | |||
85e8fe59a4 | |||
262fefa92f | |||
1403ee2ba5 | |||
548247188f | |||
6a34046e93 | |||
88b8ff86d1 | |||
cc258ba164 | |||
5d69e48787 | |||
cb4059e5c3 | |||
8411c909ff | |||
41fc41b1da | |||
cb5b45cbe8 | |||
4c47a35457 | |||
34dd35f2e2 | |||
6e3ce06fcf | |||
537518b66f | |||
4ad9714e8b | |||
5a936cd20b | |||
1fb14834b7 | |||
53fc5b8399 | |||
8318458805 | |||
7515415888 | |||
27a52ce166 | |||
17b6f287dc | |||
01ae50aca7 | |||
a156bd0863 | |||
b1dbd8b011 | |||
b28b4e5fba | |||
ead96f3836 | |||
27077c9d96 | |||
8a38712525 |
@@ -15,7 +15,9 @@ before_script:
|
||||
- curl -sS https://getcomposer.org/installer | php
|
||||
- php -d memory_limit=2G composer.phar install
|
||||
- php tests/app/bin/console doctrine:migrations:migrate -n
|
||||
- php -d memory_limit=2G tests/app/bin/console doctrine:fixtures:load -n
|
||||
- php -d memory_limit=2G tests/app/bin/console cache:clear --env=dev
|
||||
- php -d memory_limit=3G tests/app/bin/console doctrine:fixtures:load -n
|
||||
- php -d memory_limit=2G tests/app/bin/console cache:clear --env=test
|
||||
- echo "before_script finished"
|
||||
|
||||
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
|
||||
|
24
.gitlab/merge_request_templates/Default merge request.md
Normal file
24
.gitlab/merge_request_templates/Default merge request.md
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
# Description of changes
|
||||
|
||||
<!--
|
||||
describe here the change of your MR. It can be either a text, or a bullet list
|
||||
for changes
|
||||
-->
|
||||
|
||||
|
||||
# Issues related
|
||||
|
||||
<!--
|
||||
list the issues related to this MR.
|
||||
|
||||
It may be client issues, or dev issues
|
||||
-->
|
||||
|
||||
* ...
|
||||
* ...
|
||||
|
||||
# Tests
|
||||
|
||||
<!-- Describe tests if any, or why no tests -->
|
||||
|
118
CHANGELOG.md
118
CHANGELOG.md
@@ -10,18 +10,95 @@ and this project adheres to
|
||||
|
||||
## Unreleased
|
||||
|
||||
* [Aside activity] Fixes for aside activity
|
||||
|
||||
* categories with child
|
||||
* fast creation buttons
|
||||
* add ordering for types
|
||||
<!-- write down unreleased development here -->
|
||||
|
||||
|
||||
|
||||
## Test releases
|
||||
|
||||
### Test release 2021-11-08
|
||||
|
||||
* [person]: Add civility to the person
|
||||
* [person]: Various improvements on the edit person form
|
||||
* [person]: Set available_languages and available_countries as parameters for use in the edit person form
|
||||
* [activity] Bugfix: documents can now be added to an activity.
|
||||
* [tasks] improve tasks with filter order
|
||||
* [tasks] refactor singleControllerTasks: limit the number of conditions from the context
|
||||
* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60).
|
||||
* [renderbox] If gender of person is not defined, no icon is displayed instead of neuter-icon (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/129).
|
||||
* [confidential information] module added to blur confidential information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/248).
|
||||
* refactor `AuthorizationHelper` and `UserACLAwareRepository` to fix constructor, and separate logic for parent role helper into `ParentRoleHelper`
|
||||
* [main]: filter location and locationType in backend: exclude NULL names, only active and availableToUsers
|
||||
* [activity]: perform client-side validation & show/hide fields in the "new location" modal
|
||||
* [person]: normalize person with CenterResolverDispatcher and handle case where center is null or multiple in PersonRenderBox
|
||||
* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory)
|
||||
* [docstore] add authorization check inside controller and menu
|
||||
* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu
|
||||
* [person] show current address in search results
|
||||
* [person] show alt names in search results
|
||||
* [admin]: links to activity admin section added again.
|
||||
* [household]: endDate field deleted from household edit form.
|
||||
* [household]: View accompanying periods of current and old household members.
|
||||
* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning
|
||||
* [admin]: links to activity admin section added again.
|
||||
* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top.
|
||||
* [socialWorkAction]: display of social issue and parent issues + banner context added.
|
||||
* [DBAL dependencies] Upgrade to DBAL 3.1
|
||||
* [person]: double parentheses removed around age in banner + whitespace
|
||||
|
||||
|
||||
### Test release 2021-10-27
|
||||
|
||||
* [person]: delete double actions buttons on search person page
|
||||
* [person]: accompanying course work: remove creation date display the list of work + handle case when end date is null
|
||||
* [main]: Add new pages with a menu for managing location and location type in the admin
|
||||
* [main]: Add some fixtures for location type
|
||||
* [calendar]: Pass the location when transforming a calendar item (rdv) into an activity
|
||||
* [calendar]: Add a user menu for "my calendar"
|
||||
|
||||
### Test release 2021-10-18
|
||||
|
||||
* [3party]: french translation of contact and company
|
||||
* [3party]: show parent in list
|
||||
* [3party]: change color for badge "child"
|
||||
* [3party]: fix address creation
|
||||
* [household members editor] finalisation of editor
|
||||
* [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70)
|
||||
* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location.
|
||||
|
||||
### Test release 2021-10-11
|
||||
|
||||
* Address: zoom on postal code geometry + fix origin of manually entered postal code
|
||||
|
||||
* in the Address vue component, order the postal code and street address by alphabetic and numeric order
|
||||
|
||||
* add 3 new fields to PostalCode and adapt postal code command and fixtures
|
||||
|
||||
* [Aside activity] Fixes for aside activity
|
||||
|
||||
* categories with child
|
||||
* fast creation buttons
|
||||
* add ordering for types
|
||||
|
||||
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
|
||||
* Improve badges behaviour with small screens;
|
||||
|
||||
* [ThirdParty]:
|
||||
|
||||
* third party list
|
||||
* create a kind contact/institution when create a new thirdparty, and set contact embedded as kind=child;
|
||||
* filter thirdparties in list
|
||||
|
||||
* [FilterOrder]: add development kit for generating filter and ordering in list
|
||||
* [Capitalization of names] person names are capitalized on creation, on prePersist event
|
||||
* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ;
|
||||
* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ;
|
||||
|
||||
### test release 2021-10-04
|
||||
|
||||
* [Household editor][UI] Update how household suggestion and addresses are picked;
|
||||
|
||||
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80
|
||||
* [AddAddress] Handle address suggestion;
|
||||
* [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType
|
||||
when no centers are reachable by the default ACL.
|
||||
@@ -30,14 +107,41 @@ and this project adheres to
|
||||
|
||||
* a badge "requestor" is shown into search results;
|
||||
* periods where the person is only requestor (without participating) are also shown;
|
||||
|
||||
|
||||
Issues:
|
||||
|
||||
|
||||
* https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/13
|
||||
* https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/199
|
||||
* [Person form] "accept sms" not required:
|
||||
|
||||
https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37
|
||||
https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221
|
||||
|
||||
* [Household editor] suggest only temporarily addresses;
|
||||
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82
|
||||
* On-The-Fly modale works for showing, editing and creating person and thirdparty ;
|
||||
* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ;
|
||||
* [AddAddress] Handle address suggestion;
|
||||
* [AddAddress][Entity address]: add a link between address and address reference;
|
||||
* [Household editor] suggest household by comparing the temporary addresses from courses;
|
||||
|
||||
See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81
|
||||
* On-The-Fly modale works for showing, editing and creating person and thirdparty
|
||||
|
||||
|
||||
## Test released
|
||||
|
||||
<!--
|
||||
|
||||
Coming soon...
|
||||
|
||||
DO NOT ADD unreleased items here. Add them under "Unreleased" title
|
||||
|
||||
### Test release yyyy-mm-dd
|
||||
|
||||
-->
|
||||
|
||||
## Stable releases
|
||||
|
||||
No stable releases for v2+
|
||||
|
||||
|
383
CONVENTIONS.md
Normal file
383
CONVENTIONS.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# Conventions Chill
|
||||
|
||||
en cours de rédaction
|
||||
|
||||
|
||||
## Assets: nommage des entrypoints
|
||||
|
||||
Trois types d'entrypoint:
|
||||
|
||||
* application vue (souvent spécifique à une page) -> préfixé par `vue_`;
|
||||
* code js/css qui est réutilisé à plusieurs endroits:
|
||||
* ckeditor
|
||||
* async_upload (utilisé pour un formulaire)
|
||||
* bootstrap
|
||||
* chill.js
|
||||
* ...
|
||||
|
||||
=> on préfixe `mod_`
|
||||
* code css ou js pour une seule page
|
||||
* ré-utilise parfois des "foncitionnalités": ShowHide, ...
|
||||
=> on préfixe `page_`
|
||||
|
||||
|
||||
Arborescence:
|
||||
|
||||
```
|
||||
# Sous Resources/public
|
||||
|
||||
- chill/ => theme (chill)
|
||||
- chillmain.scss -> push dans l'entrypoint chill
|
||||
- lib/ => ne vont jamais dans un entrypoint, mais sont ré-utilisés par d'autres
|
||||
- ShowHide
|
||||
- Collection
|
||||
- Select2
|
||||
- module/ => termine dans des entrypoints ré-utilisables (mod_)
|
||||
- bootstrap
|
||||
- custom.scss
|
||||
- custom/
|
||||
- variables.scss
|
||||
- ..
|
||||
- forkawesome
|
||||
- AsyncUpload
|
||||
- vue/ => uniquement application vue (vue_)
|
||||
- _components
|
||||
- app
|
||||
- page/ => uniquement pour une seule page (page_)
|
||||
- login
|
||||
- person
|
||||
- personvendee
|
||||
- household_edit_metadata
|
||||
- index.js
|
||||
```
|
||||
|
||||
## Organisation des feuilles de styles
|
||||
|
||||
Comment s'échaffaudent les styles dans Chill ?
|
||||
|
||||
|
||||
1. l'entrypoint **mod_bootstrap** (module bootstrap) est le premier niveau. Toutes les parties(modules) de bootstrap sont convoquées dans le fichier ```bootstrap.js``` situé dans ```ChillMainBundle/Resources/public/module/bootstrap```.
|
||||
* Au début, ce fichier importe le fichier ```variables.scss``` qui détermine la plupart des réglages bootstrap tels qu'on les a personnalisés. Ce fichier surcharge l'original, et de nombreuses variables y sont adaptées pour Chill.
|
||||
* On veillera à ce qu'on puisse toujours comparer ce fichier à l'original de bootstrap. En cas de mise à jour de bootstrap, il faudra générer un diff, et adapter ce diff sur le fichier variable de la nouvelle version.
|
||||
* A la fin on importe le fichier ```custom.scss```, qui comprends des adaptations de bootstrap pour le préparer à notre thème Chill.
|
||||
* ce ```custom.scss``` peut être splitté en plus petits fichiers avec des ```@import 'custom/...'```
|
||||
* L'idée est que cette première couche bootstrap règle un partie importante des styles de l'application, en particulier ce qui touche aux position du layout, aux points de bascules responsive, aux marges et écarts appliqués par défauts aux éléments qu'on manipule.
|
||||
|
||||
2. l'entrypoint **chill** est le second niveau. Il contient le thème Chill qui est reconnaissable à l'application.
|
||||
* Chaque bundle a un dossier ```Resources/public/chill``` dans lequel on peut trouver une feuille sass principale, qui est éventuellement splittée avec des ```@imports```. Toutes ces feuilles sont compilées dans un unique entrypoint Chill, c'est le thème de l'application. Celui-ci surcharge bootstrap.
|
||||
* La feuille chillmain.scss devrait contenir les cascades de styles les plus générales, celles qui sont appliquées à de nombreux endroits de l'application.
|
||||
* La feuille chillperson.scss va aussi retrouver des styles propres aux différents contextes des personnes: person, household et accompanyingcourse.
|
||||
* Certains bundles plus secondaires ne contiennent que des styles spécifiques à leur fonctionnement.
|
||||
|
||||
3. les entrypoints **vue_** sont utilisés pour des composants vue. Les fichiers vue peuvent contenir un bloc de styles scss. Ce sont des styles qui ne concernent que le composant et son héritage, le tag ```scoped``` précise justement sa portée (voir la doc).
|
||||
|
||||
4. les entrypoints **page_** sont utilisés pour ajouter des assets spécifiques à certaines pages, le plus souvent des scripts et des styles.
|
||||
|
||||
|
||||
## Taguer du code html et construire la cascade de styles
|
||||
|
||||
L'exemple suivant montre comment taguer sans excès un élément de code. On remarque que:
|
||||
* il n'est pas nécessaire de taguer toutes les classes intérieures,
|
||||
* il ne faut pas répéter la classe parent dans toutes les classes enfants. La cascade sass va permettre de saisir le html avec souplesse sans alourdir la structure des balises.
|
||||
* souvent la première classe sera déclinée par plusieurs classes qui commencent de la même manière: ```bloc-dark``` ajoute juste la version sombre de ```bloc```, on ne met pas ```bloc dark```, car on ne souhaite pas que la classe ```dark``` de ```bloc``` interagisse avec la même classe ```dark``` de ```table```. On aura donc un élément ```bloc bloc-dark``` et un élément ```table table-dark```.
|
||||
|
||||
```html
|
||||
<div class="bloc bloc-dark mon-bloc">
|
||||
<h3>mon titre</h3>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-edit"></a>
|
||||
</li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
```
|
||||
|
||||
Finalement, il importe ici de définir ce qu'est un bloc, ce qu'est une zone d'actions et ce qu'est un bouton. Ces 3 éléments existent de manière autonome, ce sont les seuls qu'on tagge.
|
||||
|
||||
Par exemple pour mettre un style au titre on précise juste h3 dans la cascade bloc.
|
||||
|
||||
```sass
|
||||
div.bloc {
|
||||
// un bloc générique, utilisé à plusieurs endroits
|
||||
&.bloc-dark {
|
||||
// la version sombre du bloc
|
||||
}
|
||||
h3 {}
|
||||
ul {
|
||||
// une liste standard dans bloc
|
||||
li {
|
||||
// des items de liste standard dans bloc
|
||||
}
|
||||
}
|
||||
}
|
||||
div.mon-bloc {
|
||||
// des exceptions spécifiques à mon-bloc,
|
||||
// qui sont des adaptations de bloc
|
||||
}
|
||||
|
||||
ul.record_actions {
|
||||
// va uniformiser tous les record_actions de l'application
|
||||
li {
|
||||
//...
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
// les boutons de bootstrap
|
||||
.btn-edit {
|
||||
// chill étends les boutons bootstrap pour ses propres besoins
|
||||
}
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Render box
|
||||
|
||||
|
||||
## URL
|
||||
|
||||
### Nommage des routes
|
||||
|
||||
:::warning
|
||||
Ces règles n'ont pas toujours été utilisées par le passé. Elles sont souhaitées pour le futur.
|
||||
:::
|
||||
|
||||
Les routes sont nommées de cette manière:
|
||||
|
||||
`chill_bundle_entite_action`
|
||||
|
||||
1. d'abord chill_ (pour tous les modules chill)
|
||||
2. ensuite une string qui est identique, par bundle
|
||||
3. si le point est un point d'api (json), alors ajouter la string `api`
|
||||
4. ensuite une string qui indique sur quelle entité porte la route, voire également les sous-entités
|
||||
5. ensuite une action (`list`, `view`, `edit`, `new`, ...)
|
||||
|
||||
Le fait d'indiquer `api` en 3 permet de distinguer les routes d'api qui sont générées par la configuration (qui sont toutes préfixées par `chill_api`, de celles générées manuellement. (Exemple: `chill_api_household__index`, et `chill_person_api_household_members_move`)
|
||||
|
||||
Si les points 4 et 5 sont inexistants, alors ils sont remplacés par d'autres éléments de manière à garantir l'unicité de la route, et sa bonne compréhension.
|
||||
|
||||
### URL
|
||||
|
||||
Les URL respectent également une convention:
|
||||
|
||||
#### Pour les pages html
|
||||
|
||||
:::warning
|
||||
Ces règles n'ont pas toujours été utilisées par le passé. Elles sont souhaitées pour le futur.
|
||||
:::
|
||||
|
||||
Syntaxe:
|
||||
|
||||
```
|
||||
/{_locale}/bundle/entity/{id}/action
|
||||
/{_locale}/bundle/entity/sub-entity/{id}/action
|
||||
```
|
||||
|
||||
Les éléments suivants devraient se trouver dans la liste:
|
||||
|
||||
1. la locale;
|
||||
2. un identifiant du bundle
|
||||
3. l'entité auquel il se rapporte
|
||||
4. les éventuelles sous-entités auxquelles l'url se rapport
|
||||
5. l'action
|
||||
|
||||
Ces éléments peuvent être entrecoupés de l'identifiant d'une entité. Dans ce cas, cet identifiant se place juste après l'entité auquel il se rapporte.
|
||||
|
||||
Exemple:
|
||||
|
||||
```
|
||||
# liste des échanges pour une personne
|
||||
/fr/activity/person/25/activity/list
|
||||
|
||||
# nouvelle activité
|
||||
/fr/activity/activity/new?person_id=25
|
||||
|
||||
```
|
||||
|
||||
#### Pour les API
|
||||
|
||||
:::info
|
||||
Les routes générées automatiquement sont préfixées par chill_api
|
||||
:::
|
||||
|
||||
Syntaxe:
|
||||
|
||||
```
|
||||
/api/1.0/bundle/entity/{id}/action
|
||||
/api/1.0/bundle/entity/sub-entity/{id}/action
|
||||
```
|
||||
|
||||
Les éléments suivants devraient se trouver dans la liste:
|
||||
|
||||
1. la string `/api/` et puis la version (1.0)
|
||||
2. un identifiant du bundle
|
||||
3. l'entité auquel il se rapporte
|
||||
4. les éventuelles sous-entités auxquelles l'url se rapport
|
||||
5. l'action
|
||||
|
||||
Ces éléments peuvent être entrecoupés de l'identifiant d'une entité. Dans ce cas, cet identifiant se place juste après l'entité auquel il se rapporte.
|
||||
|
||||
## Règles UI chill
|
||||
|
||||
### Titre des pages
|
||||
|
||||
#### Chaque page contient un titre
|
||||
|
||||
Chaque page contient un titre dans la balise head. Ce titre est normalement identique à celui de l'entête de la page.
|
||||
|
||||
Astuce: il est possible d'utiliser la fonction `block` de twig pour cela:
|
||||
|
||||
```htmlmixed=
|
||||
{% block title "Titre de la page" %}
|
||||
|
||||
{% block content %}
|
||||
<h1>
|
||||
{{ block('title')}}
|
||||
</h1>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
|
||||
### Utilisation des `entity_render`
|
||||
|
||||
#### En twig
|
||||
|
||||
Les templates twig doivent toujours utiliser la fonction chill_entity_render_box pour effectuer le rendu des éléments suivants:
|
||||
|
||||
* User
|
||||
* Person
|
||||
* SocialIssue
|
||||
* SocialAction
|
||||
* Address
|
||||
* ThirdParty
|
||||
* ...
|
||||
|
||||
Exemple:
|
||||
|
||||
```
|
||||
address|chill_entity_render_box
|
||||
```
|
||||
|
||||
Justification:
|
||||
|
||||
* des éléments sont parfois personnalisés par installation (par exemple, le nom de chaque utilisateur sera suivi par le nom du service)
|
||||
* pour rationaliser et rendre semblable les affichages
|
||||
* pour simplifier le code twig
|
||||
|
||||
A prevoir:
|
||||
|
||||
* toujours trois positions:
|
||||
* inline
|
||||
* block
|
||||
* item (dans un tableau, une ligne)
|
||||
|
||||
> block et item sont en fait la même option passée au render_box: render: bloc. Il y a aussi ‘raw’ pour le inline, et ‘label’ pour une titraille configurable avec des options.
|
||||
|
||||
> quand on passe l’option render: bloc, on peut placer le render_box dans une boucle for plus large qui fonctionne avec la classe flex-table ou la classe flex-bloc, ce qui donnera un affichage en rangée (table) ou en blocs. [name=Mathieu]
|
||||
|
||||
|
||||
|
||||
#### En vue
|
||||
|
||||
Il existe systématiquement une "box" équivalente en vue.
|
||||
|
||||
#### Lien vers des sections
|
||||
|
||||
A chaque fois qu'on indique le nom d'une personne, un parcours, un ménage, il y a toujours:
|
||||
|
||||
* un lien pour accéder à son dossier (pour autant que l'utilisateur ait les droits d'accès);
|
||||
* à moins qu'il ne soit indiqué dans une phrase, l'icône de son dossier avant ou après (donc un bonhomme pour la personne, une maison pour le ménage, un fa-random pour les parcours);
|
||||
|
||||
Ces éléments sont toujours proposé par des `render_box` par défaut. Des options permettent de les désactiver dans des cas particuliers
|
||||
|
||||
> à discuter, quelques réflexion:
|
||||
> quelle est la logique qui domine pour les boutons ? on a symbolisé les 4 actions du crud par des couleurs: bleu(show) orange(edit) vert(create) et rouge(delete).
|
||||
> Est-ce que c'est ça qui prime, et comment ça s'articule avec la logique des pictos ?
|
||||
> Par exemple, il pourrait être logique d'utiliser l'oeil bleu pour voir l'objet, qu'il s'agisse d'une personne ou d'un parcours, ce serait plutôt le contexte, et l'infobulle (title) qui préciserait le contexte.
|
||||
> Je pense que les pictos de boutons doivent faire référence à l'action, mais pas à l'objet. Autrement dit je n'utiliserais jamais l'icone du ménage ou du parcours dans les boutons.
|
||||
> Pour représenter les ménages et les parcours, je pense qu'il faudrait trouver autre chose que forkawesome. Si c'est des pictos, trouver un motif différents et de tailles différente. Réfléchir à un couplage picto-couleur-forme différent, qui exprime le contexte et qui se distingue bien des boutons.
|
||||
> Idem pour les badges, il faut une palette de badge qui couvre tous les besoins: socialIssue, socialActions, socialReason, members, etc. [name=Mathieu]
|
||||
|
||||
### Formulaires
|
||||
|
||||
#### Vocabulaire:
|
||||
|
||||
Utiliser toujours:
|
||||
|
||||
* `Créer` dans un `bt bt-create` pour les **liens** vers le formulairep pour créer une entité (pour parvenir au formulaire);
|
||||
* `Enregistrer` dans un `bt bt-save` pour les boutons "Enregistrer" (dans un formulaire édition **ou** création);
|
||||
* `Enregistrer et nouveau`
|
||||
* `Enregistrer et voir`
|
||||
* `Modifier` dans un `bt bt-edit` pour les **liens** vers le formulaire d'édition
|
||||
* `Dupliquer` (préciser là où on peut le voir)
|
||||
* `Annuler` pour quitter une page d'édition avec un lien vers la liste, ou le `returnPath`
|
||||
|
||||
#### Retour après un enregistrement
|
||||
|
||||
Après avoir cliqué sur "Créer" ou "Sauver", la page devrait revenir:
|
||||
|
||||
* vers le returnPath, s'il existe;
|
||||
* sinon, vers la page "vue".
|
||||
|
||||
|
||||
### Bandeaux contenant les boutons d'actions
|
||||
|
||||
Les boutons sont toujours dans un bandeau "sticky-form" dans le bas du formulaire ou de la page de liste.
|
||||
|
||||
Si pertinent:
|
||||
|
||||
* Le bandeau contient un bouton "Annuler" qui retourne à la page précédente. Il est obligatoire pour les formulaires, optionnel pour les listes ou les pages "résumés"
|
||||
* Ce bouton "annuler" est toujours à gauche
|
||||
|
||||
```
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_entity_return_path('route_name' { 'route': 'option' } )}}">{{ return_path_label }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<!-- action 1 -->
|
||||
</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
### Messages flash
|
||||
|
||||
#### A la création d'une entité
|
||||
|
||||
A chaque fois qu'un élément est créé par un formulaire, un message flash doit apparaitre. Il indique:
|
||||
|
||||
> "L'élément a été créé"
|
||||
|
||||
Le nom de l'élément peut être remplacé par quelque chose de plus pertinent:
|
||||
|
||||
> * L'activité a été créée
|
||||
> * Le rendez-vous a été créé
|
||||
> * ...
|
||||
|
||||
|
||||
#### A l'enregistrement d'une entité
|
||||
|
||||
A chaque fois qu'un élément est enregistré, un message flash doit apparaitre:
|
||||
|
||||
> * Les données ont été modifiées
|
||||
>
|
||||
|
||||
#### Erreur sur un formulaire (erreur de validation)
|
||||
|
||||
En tête d'un formulaire, un message flash doit indiquer que des validations n'ont pas réussi:
|
||||
|
||||
> Ce formulaire contient des erreurs
|
||||
|
||||
Les erreurs doivent apparaitre attachée au champ qui les concerne. Toutefois, il est acceptable d'afficher les erreurs à la racine du formulaire s'il était complexe, techniquement, d'attacher les erreurs.
|
||||
|
||||
### Liens de retour
|
||||
|
||||
A chaque fois qu'un lien est indiqué, vérifier si on ne doit pas utiliser la fonction `chill_return_path`, `chill_forward_return_path` ou `chill_return_path_or`.
|
||||
|
||||
* depuis la page liste, vers l'ouverture d'un élément, ou le bouton création => utiliser `chill_path_add_return_path`
|
||||
* dans ces pages d'éditions,
|
||||
* utiliser `chill_return_path_or` dans le bouton "Cancel";
|
||||
* pour les boutons "enregistrer et voir" et "Enregistrer et fermer" => ?
|
||||
|
@@ -54,6 +54,9 @@
|
||||
"doctrine/doctrine-fixtures-bundle": "^3.3",
|
||||
"fakerphp/faker": "^1.13",
|
||||
"nelmio/alice": "^3.8",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.0",
|
||||
"phpunit/phpunit": "^7.0",
|
||||
"symfony/debug-bundle": "^5.1",
|
||||
"symfony/dotenv": "^5.1",
|
||||
|
1542
phpstan-baseline.neon
Normal file
1542
phpstan-baseline.neon
Normal file
File diff suppressed because it is too large
Load Diff
21
phpstan.neon.dist
Normal file
21
phpstan.neon.dist
Normal file
@@ -0,0 +1,21 @@
|
||||
parameters:
|
||||
level: 1
|
||||
paths:
|
||||
- src/
|
||||
excludePaths:
|
||||
- src/Bundle/*/Tests/*
|
||||
- src/Bundle/*/Test/*
|
||||
- src/Bundle/*/config/*
|
||||
- src/Bundle/*/migrations/*
|
||||
- src/Bundle/*/translations/*
|
||||
- src/Bundle/*/Resources/*
|
||||
- src/Bundle/*/src/Tests/*
|
||||
- src/Bundle/*/src/Test/*
|
||||
- src/Bundle/*/src/config/*
|
||||
- src/Bundle/*/src/migrations/*
|
||||
- src/Bundle/*/src/translations/*
|
||||
- src/Bundle/*/src/Resources/*
|
||||
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
@@ -27,3 +27,4 @@ Version 1.5.5
|
||||
=============
|
||||
|
||||
- [activity] replace dropdown for selecting reasons and use chillEntity for reason rendering
|
||||
- fix bug: error when trying to edit activity of which the type has been deactivated
|
||||
|
@@ -174,6 +174,10 @@ class ActivityController extends AbstractController
|
||||
$activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class)
|
||||
->find($activityType_id);
|
||||
|
||||
if (isset($activityType) && !$activityType->isActive()) {
|
||||
throw new \InvalidArgumentException('Activity type must be active');
|
||||
}
|
||||
|
||||
$activityData = null;
|
||||
if ($request->query->has('activityData')) {
|
||||
$activityData = $request->query->get('activityData');
|
||||
@@ -238,6 +242,11 @@ class ActivityController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('location', $activityData)) {
|
||||
$location = $em->getRepository(\Chill\MainBundle\Entity\Location::class)->find($activityData['location']);
|
||||
$entity->setLocation($location);
|
||||
}
|
||||
|
||||
if (array_key_exists('comment', $activityData)) {
|
||||
$comment = new CommentEmbeddable();
|
||||
$comment->setComment($activityData['comment']);
|
||||
|
@@ -141,7 +141,6 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface, C
|
||||
$ref = 'activity_'.$person->getFullnameCanonical();
|
||||
|
||||
for($i = 0; $i < $activityNbr; $i ++) {
|
||||
print "Creating an activity type for : ".$person." (ref: ".$ref.") \n";
|
||||
$activity = $this->newRandomActivity($person);
|
||||
$manager->persist($activity);
|
||||
}
|
||||
|
@@ -87,7 +87,9 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
|
||||
ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS),
|
||||
ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS),
|
||||
ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS),
|
||||
ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE)
|
||||
ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE),
|
||||
ActivityVoter::FULL => [ActivityVoter::CREATE, ActivityVoter::DELETE,
|
||||
ActivityVoter::UPDATE],
|
||||
)
|
||||
));
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ namespace Chill\ActivityBundle\Entity;
|
||||
use Chill\DocStoreBundle\Entity\Document;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
||||
@@ -157,7 +158,7 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
|
||||
private ?Collection $thirdParties = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject")
|
||||
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
|
||||
*/
|
||||
private Collection $documents;
|
||||
|
||||
@@ -177,6 +178,13 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
|
||||
*/
|
||||
private string $sentReceived = '';
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private ?Location $location = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->reasons = new ArrayCollection();
|
||||
@@ -555,4 +563,22 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Location|null
|
||||
*/
|
||||
public function getLocation(): ?Location
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Location|null $location
|
||||
* @return Activity
|
||||
*/
|
||||
public function setLocation(?Location $location): Activity
|
||||
{
|
||||
$this->location = $location;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -44,7 +44,7 @@ class ActivityReason
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
@@ -108,7 +108,7 @@ class ActivityReason
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set category of the reason. If you set to the reason an inactive
|
||||
* category, the reason will become inactive
|
||||
@@ -121,7 +121,7 @@ class ActivityReason
|
||||
if($this->category !== $category && ! $category->getActive()) {
|
||||
$this->setActive(False);
|
||||
}
|
||||
|
||||
|
||||
$this->category = $category;
|
||||
|
||||
return $this;
|
||||
|
@@ -2,17 +2,17 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@@ -43,7 +43,7 @@ class ActivityReasonCategory
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
@@ -52,7 +52,7 @@ class ActivityReasonCategory
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active = true;
|
||||
|
||||
|
||||
/**
|
||||
* Array of ActivityReason
|
||||
* @var ArrayCollection
|
||||
@@ -61,7 +61,7 @@ class ActivityReasonCategory
|
||||
* mappedBy="category")
|
||||
*/
|
||||
private $reasons;
|
||||
|
||||
|
||||
/**
|
||||
* ActivityReasonCategory constructor.
|
||||
*/
|
||||
@@ -69,7 +69,7 @@ class ActivityReasonCategory
|
||||
{
|
||||
$this->reasons = new ArrayCollection();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -139,9 +139,9 @@ class ActivityReasonCategory
|
||||
$reason->setActive($active);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->active = $active;
|
||||
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@@ -44,7 +44,7 @@ class ActivityType
|
||||
private ?int $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private array $name = [];
|
||||
|
||||
@@ -248,6 +248,16 @@ class ActivityType
|
||||
*/
|
||||
private string $socialActionsLabel = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="smallint", nullable=false, options={"default"=1})
|
||||
*/
|
||||
private int $locationVisible = self::FIELD_INVISIBLE;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", nullable=false, options={"default"=""})
|
||||
*/
|
||||
private string $locationLabel = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="float", options={"default"="0.0"})
|
||||
*/
|
||||
@@ -820,4 +830,29 @@ class ActivityType
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocationVisible(): ?int
|
||||
{
|
||||
return $this->locationVisible;
|
||||
}
|
||||
|
||||
public function setLocationVisible(int $locationVisible): self
|
||||
{
|
||||
$this->locationVisible = $locationVisible;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLocationLabel(): ?string
|
||||
{
|
||||
return $this->locationLabel;
|
||||
}
|
||||
|
||||
public function setLocationLabel(string $locationLabel): self
|
||||
{
|
||||
$this->locationLabel = $locationLabel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ class ActivityTypeCategory
|
||||
private ?int $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private array $name = [];
|
||||
|
||||
|
@@ -29,27 +29,27 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Doctrine\ORM\Query\Expr\Join;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class ActivityTypeAggregator implements AggregatorInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var EntityRepository
|
||||
*/
|
||||
protected $typeRepository;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatableStringHelper
|
||||
*/
|
||||
protected $stringHelper;
|
||||
|
||||
|
||||
const KEY = 'activity_type_aggregator';
|
||||
|
||||
|
||||
public function __construct(
|
||||
EntityRepository $typeRepository,
|
||||
TranslatableStringHelper $stringHelper
|
||||
@@ -57,19 +57,19 @@ class ActivityTypeAggregator implements AggregatorInterface
|
||||
$this->typeRepository = $typeRepository;
|
||||
$this->stringHelper = $stringHelper;
|
||||
}
|
||||
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data)
|
||||
{
|
||||
// add select element
|
||||
// add select element
|
||||
$qb->addSelect(sprintf('IDENTITY(activity.type) AS %s', self::KEY));
|
||||
|
||||
|
||||
// add the "group by" part
|
||||
$groupBy = $qb->addGroupBy(self::KEY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a join between Activity and another alias
|
||||
*
|
||||
*
|
||||
* @param Join[] $joins
|
||||
* @param string $alias the alias to search for
|
||||
* @return boolean
|
||||
@@ -81,7 +81,7 @@ class ActivityTypeAggregator implements AggregatorInterface
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -99,18 +99,18 @@ class ActivityTypeAggregator implements AggregatorInterface
|
||||
{
|
||||
return "Aggregate by activity type";
|
||||
}
|
||||
|
||||
|
||||
public function addRole()
|
||||
{
|
||||
return new Role(ActivityStatsVoter::STATS);
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
public function getLabels($key, array $values, $data): \Closure
|
||||
{
|
||||
// for performance reason, we load data from db only once
|
||||
$this->typeRepository->findBy(array('id' => $values));
|
||||
|
||||
return function($value) use ($data) {
|
||||
|
||||
return function($value): string {
|
||||
if ($value === '_header') {
|
||||
return 'Activity type';
|
||||
}
|
||||
@@ -120,12 +120,11 @@ class ActivityTypeAggregator implements AggregatorInterface
|
||||
|
||||
return $this->stringHelper->localize($t->getName());
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
public function getQueryKeys($data)
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return array(self::KEY);
|
||||
return [self::KEY];
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ use Chill\ActivityBundle\Entity\Activity;
|
||||
use Chill\ActivityBundle\Entity\ActivityPresence;
|
||||
use Chill\ActivityBundle\Entity\ActivityReason;
|
||||
use Chill\DocStoreBundle\Form\StoredObjectType;
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\CommentType;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
@@ -93,15 +94,13 @@ class ActivityType extends AbstractType
|
||||
/** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */
|
||||
$activityType = $options['activityType'];
|
||||
|
||||
if (!$activityType->isActive()) {
|
||||
throw new \InvalidArgumentException('Activity type must be active');
|
||||
}
|
||||
|
||||
// TODO revoir la gestion des center au niveau du form des activité.
|
||||
if ($options['center']) {
|
||||
$builder->add('scope', ScopePickerType::class, [
|
||||
'center' => $options['center'],
|
||||
'role' => $options['role']
|
||||
'role' => $options['role'],
|
||||
// TODO make required again once scope and rights are fixed
|
||||
'required' => false
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -224,7 +223,7 @@ class ActivityType extends AbstractType
|
||||
|
||||
if ($activityType->isVisible('comment')) {
|
||||
$builder->add('comment', CommentType::class, [
|
||||
'label' => empty($activityType->getLabel('comment'))
|
||||
'label' => empty($activityType->getLabel('comment'))
|
||||
? 'activity.comment' : $activityType->getLabel('comment'),
|
||||
'required' => $activityType->isRequired('comment'),
|
||||
]);
|
||||
@@ -304,6 +303,23 @@ class ActivityType extends AbstractType
|
||||
;
|
||||
}
|
||||
|
||||
if ($activityType->isVisible('location')) {
|
||||
$builder->add('location', HiddenType::class)
|
||||
->get('location')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
function (?Location $location): string {
|
||||
if (null === $location) {
|
||||
return '';
|
||||
}
|
||||
return $location->getId();
|
||||
},
|
||||
function (?string $id): ?Location {
|
||||
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
||||
}
|
||||
))
|
||||
;
|
||||
}
|
||||
|
||||
if ($activityType->isVisible('emergency')) {
|
||||
$builder->add('emergency', CheckboxType::class, [
|
||||
'label' => $activityType->getLabel('emergency'),
|
||||
|
@@ -71,7 +71,7 @@ class TranslatableActivityType extends AbstractType
|
||||
|
||||
if ($options['active_only'] === true) {
|
||||
$qb->where($qb->expr()->eq('at.active', ':active'));
|
||||
$qb->setParameter('active', true, \Doctrine\DBAL\Types\Type::BOOLEAN);
|
||||
$qb->setParameter('active', true, \Doctrine\DBAL\Types\Types::BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,29 +2,27 @@
|
||||
|
||||
namespace Chill\ActivityBundle\Menu;
|
||||
|
||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
protected TokenStorageInterface $tokenStorage;
|
||||
|
||||
protected AuthorizationHelper $authorizationHelper;
|
||||
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
protected Security $security;
|
||||
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
Security $security,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->security = $security;
|
||||
$this->translator = $translator;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
}
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
@@ -35,7 +33,8 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
$period = $parameters['accompanyingCourse'];
|
||||
|
||||
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) {
|
||||
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
|
||||
&& $this->security->isGranted(ActivityVoter::SEE, $period)) {
|
||||
$menu->addChild($this->translator->trans('Activity'), [
|
||||
'route' => 'chill_activity_activity_list',
|
||||
'routeParameters' => [
|
||||
|
48
src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php
Normal file
48
src/Bundle/ChillActivityBundle/Menu/AdminMenuBuilder.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\ActivityBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class AdminMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
private Security $security;
|
||||
|
||||
public function __construct(Security $security)
|
||||
{
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return ['admin_index', 'admin_section', 'admin_activity'];
|
||||
}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($menuId, ['admin_index', 'admin_section'])) {
|
||||
$menu->addChild('Activities', [
|
||||
'route' => 'chill_admin_activity_index'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 2000,
|
||||
'explain' => "Activity configuration"
|
||||
]);
|
||||
} else {
|
||||
$menu
|
||||
->addChild('Activities', [
|
||||
'route' => 'chill_admin_activity_index'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => '60'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,9 @@
|
||||
// Access to Bootstrap variables and mixins
|
||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
||||
|
||||
// activity creation first step: select type page
|
||||
//// ACTIVITY CREATION
|
||||
// first step: select type page
|
||||
|
||||
div.new-activity-select-type {
|
||||
div.activity-row {
|
||||
display: flex;
|
||||
@@ -21,91 +23,36 @@ div.new-activity-select-type {
|
||||
}
|
||||
}
|
||||
|
||||
// exceptions for flex-table in list-records
|
||||
div.activity-list {
|
||||
div.flex-table {
|
||||
div.item-bloc {
|
||||
div.item-row.main {
|
||||
div.item-col {
|
||||
&:first-child {
|
||||
flex-basis: 15%;
|
||||
}
|
||||
ul.list-content {
|
||||
li.social-issues, li.social-actions {
|
||||
.badge-primary {
|
||||
font-variant: small-caps;
|
||||
font-weight: bold;
|
||||
font-size: 88%;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
li.social-issues .badge-primary {
|
||||
background-color: $orange;
|
||||
}
|
||||
li.social-actions .badge-primary {
|
||||
background-color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div.item-row.comment {
|
||||
margin-left: 15%;
|
||||
blockquote.chill-user-quote {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
div.item-row.details {
|
||||
margin-left: 15%;
|
||||
//// ACTIVITY LIST PAGE
|
||||
// precise badge-title specific details
|
||||
|
||||
// override flex-bloc to adapt in list
|
||||
// TODO refund this
|
||||
div.accompanyingCourse.flex-bloc.concerned-groups {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
div.item-bloc {
|
||||
box-shadow: unset;
|
||||
padding: 0;
|
||||
flex-basis: 25%;
|
||||
div.item-row {
|
||||
flex-direction: column;
|
||||
div.item-col {
|
||||
&:first-child {
|
||||
width: unset;
|
||||
}
|
||||
&:last-child {
|
||||
border-top: 0;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
ul.list-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h2.badge-title {
|
||||
div.duration {
|
||||
font-size: smaller;
|
||||
padding-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
ul.list-content {
|
||||
font-size: 70%;
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
li {
|
||||
margin-bottom: 0.2em;
|
||||
// exception: change bg color for action badges above badge-title
|
||||
.bg-light {
|
||||
background-color: $chill-light-gray !important;
|
||||
}
|
||||
|
||||
}
|
||||
ul.list-content {
|
||||
list-style-type: none;
|
||||
padding-left: 1em;
|
||||
margin: 0 0;
|
||||
li {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
div.duration {
|
||||
font-size: smaller;
|
||||
padding-left: 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div.main {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
// exceptions for flex-bloc in concerned-groups
|
||||
//// ACTIVITY SHOW AND FORM PAGES
|
||||
// Exceptions for flex-bloc in concerned-groups
|
||||
|
||||
div.flex-bloc.concerned-groups {
|
||||
margin-top: 1em;
|
||||
div.item-bloc {
|
||||
@@ -130,7 +77,6 @@ div.flex-bloc.concerned-groups {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// CHILL ENTITY RENDER BOX
|
||||
.chill-entity {
|
||||
|
||||
|
@@ -1,17 +1,20 @@
|
||||
<template>
|
||||
<concerned-groups></concerned-groups>
|
||||
<social-issues-acc></social-issues-acc>
|
||||
<location></location>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ConcernedGroups from './components/ConcernedGroups.vue';
|
||||
import SocialIssuesAcc from './components/SocialIssuesAcc.vue';
|
||||
import Location from './components/Location.vue';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
SocialIssuesAcc
|
||||
SocialIssuesAcc,
|
||||
Location
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -3,16 +3,61 @@ import { getSocialIssues } from 'ChillPersonAssets/vuejs/AccompanyingCourse/api.
|
||||
/*
|
||||
* Load socialActions by socialIssue (id)
|
||||
*/
|
||||
const getSocialActionByIssue = (id) => {
|
||||
const getSocialActionByIssue = (id) => {
|
||||
const url = `/api/1.0/person/social/social-action/by-social-issue/${id}.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Load Locations
|
||||
*/
|
||||
const getLocations = () => {
|
||||
const url = `/api/1.0/main/location.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Load Location Types
|
||||
*/
|
||||
const getLocationTypes = () => {
|
||||
const url = `/api/1.0/main/location-type.json`;
|
||||
return fetch(url)
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Post a Location
|
||||
*/
|
||||
const postLocation = (body) => {
|
||||
const url = `/api/1.0/main/location.json`;
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) { return response.json(); }
|
||||
throw Error('Error with request resource response');
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
getSocialIssues,
|
||||
getSocialActionByIssue
|
||||
getSocialActionByIssue,
|
||||
getLocations,
|
||||
getLocationTypes,
|
||||
postLocation
|
||||
};
|
||||
|
@@ -73,9 +73,12 @@ export default {
|
||||
addPersons: {
|
||||
key: 'activity',
|
||||
options: {
|
||||
type: ['person', 'thirdparty', 'user'], // TODO add 'user'
|
||||
type: ['person', 'thirdparty', 'user'],
|
||||
priority: null,
|
||||
uniq: false,
|
||||
button: {
|
||||
size: 'btn-sm'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<teleport to="#location">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-sm-4">
|
||||
{{ $t('activity.location') }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
|
||||
<VueMultiselect
|
||||
name="selectLocation"
|
||||
id="selectLocation"
|
||||
label="name"
|
||||
track-by="id"
|
||||
open-direction="top"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('activity.choose_location')"
|
||||
:custom-label="customLabel"
|
||||
:options="locations"
|
||||
v-model="location">
|
||||
</VueMultiselect>
|
||||
|
||||
<new-location v-bind:locations="locations"></new-location>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import NewLocation from './Location/NewLocation.vue';
|
||||
import { getLocations } from '../api.js';
|
||||
|
||||
export default {
|
||||
name: "Location",
|
||||
components: {
|
||||
NewLocation,
|
||||
VueMultiselect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
locations: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activity']),
|
||||
location: {
|
||||
get() {
|
||||
return this.activity.location;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('updateLocation', value);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getLocationsList();
|
||||
},
|
||||
methods: {
|
||||
getLocationsList() {
|
||||
getLocations().then(response => new Promise(resolve => {
|
||||
console.log('getLocations', response);
|
||||
this.locations = response.results;
|
||||
resolve();
|
||||
}))
|
||||
},
|
||||
customLabel(value) {
|
||||
return `${value.locationType.title.fr} ${value.name ? value.name : ''}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-sm btn-create" @click="openModal">
|
||||
{{ $t('activity.create_new_location') }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<teleport to="body">
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">{{ $t('activity.create_new_location') }}</h3>
|
||||
</template>
|
||||
<template v-slot:body>
|
||||
<form>
|
||||
<div class="form-floating mb-3">
|
||||
<p v-if="errors.length">
|
||||
<b>{{ $t('activity.errors') }}</b>
|
||||
<ul>
|
||||
<li v-for="error in errors" :key="error">{{ error }}</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<select class="form-select form-select-lg" id="type" required v-model="selectType">
|
||||
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
|
||||
<option v-for="t in locationTypes" :value="t" :key="t.id">
|
||||
{{ t.title.fr }}
|
||||
</option>
|
||||
</select>
|
||||
<label>{{ $t('activity.location_fields.type') }}</label>
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" id="name" v-model="inputName" placeholder />
|
||||
<label for="name">{{ $t('activity.location_fields.name') }}</label>
|
||||
</div>
|
||||
|
||||
<add-address
|
||||
:context="addAddress.context"
|
||||
:options="addAddress.options"
|
||||
:addressChangedCallback="submitNewAddress"
|
||||
v-if="showAddAddress"
|
||||
ref="addAddress">
|
||||
</add-address>
|
||||
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input class="form-control form-control-lg" id="phonenumber1" v-model="inputPhonenumber1" placeholder />
|
||||
<label for="phonenumber1">{{ $t('activity.location_fields.phonenumber1') }}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="hasPhonenumber1">
|
||||
<input class="form-control form-control-lg" id="phonenumber2" v-model="inputPhonenumber2" placeholder />
|
||||
<label for="phonenumber2">{{ $t('activity.location_fields.phonenumber2') }}</label>
|
||||
</div>
|
||||
<div class="form-floating mb-3" v-if="showContactData">
|
||||
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
|
||||
<label for="email">{{ $t('activity.location_fields.email') }}</label>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-save"
|
||||
@click.prevent="saveNewLocation"
|
||||
>
|
||||
{{ $t('action.save') }}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
|
||||
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
|
||||
import { mapState } from "vuex";
|
||||
import { getLocationTypes, postLocation } from "../../api";
|
||||
|
||||
export default {
|
||||
name: "NewLocation",
|
||||
components: {
|
||||
Modal,
|
||||
AddAddress,
|
||||
},
|
||||
props: ['locations'],
|
||||
data() {
|
||||
return {
|
||||
errors: [],
|
||||
selected: {
|
||||
type: null,
|
||||
name: null,
|
||||
addressId: null,
|
||||
phonenumber1: null,
|
||||
phonenumber2: null,
|
||||
email: null,
|
||||
},
|
||||
locationTypes: [],
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl"
|
||||
},
|
||||
addAddress: {
|
||||
options: {
|
||||
button: {
|
||||
text: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
||||
size: 'btn-sm'
|
||||
},
|
||||
title: { create: 'activity.create_address', edit: 'activity.edit_address' },
|
||||
},
|
||||
context: {
|
||||
target: { //name, id
|
||||
},
|
||||
edit: false,
|
||||
addressId: null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['activity']),
|
||||
selectType: {
|
||||
get() {
|
||||
return this.selected.type;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.type = value;
|
||||
}
|
||||
},
|
||||
inputName: {
|
||||
get() {
|
||||
return this.selected.name;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.name = value;
|
||||
}
|
||||
},
|
||||
inputEmail: {
|
||||
get() {
|
||||
return this.selected.email;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.email = value;
|
||||
}
|
||||
},
|
||||
inputPhonenumber1: {
|
||||
get() {
|
||||
return this.selected.phonenumber1;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber1 = value;
|
||||
}
|
||||
},
|
||||
inputPhonenumber2: {
|
||||
get() {
|
||||
return this.selected.phonenumber2;
|
||||
},
|
||||
set(value) {
|
||||
this.selected.phonenumber2 = value;
|
||||
}
|
||||
},
|
||||
hasPhonenumber1() {
|
||||
return this.selected.phonenumber1 !== null && this.selected.phonenumber1 !== "";
|
||||
},
|
||||
showAddAddress() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.addressRequired !== 'never') {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
showContactData() {
|
||||
let cond = false;
|
||||
if (this.selected.type) {
|
||||
if (this.selected.type.contactData !== 'never') {
|
||||
cond = true;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getLocationTypesList();
|
||||
},
|
||||
methods: {
|
||||
checkForm() {
|
||||
let cond = true;
|
||||
this.errors = [];
|
||||
if (!this.selected.type) {
|
||||
this.errors.push('Type de localisation requis');
|
||||
cond = false;
|
||||
} else {
|
||||
if (this.selected.type.addressRequired === 'required' && !this.selected.addressId) {
|
||||
this.errors.push('Adresse requise');
|
||||
cond = false;
|
||||
}
|
||||
if (this.selected.type.contactData === 'required' && !this.selected.phonenumber1) {
|
||||
this.errors.push('Numéro de téléphone requis');
|
||||
cond = false;
|
||||
}
|
||||
if (this.selected.type.contactData === 'required' && !this.selected.email) {
|
||||
this.errors.push('Adresse email requise');
|
||||
cond = false;
|
||||
}
|
||||
}
|
||||
return cond;
|
||||
},
|
||||
getLocationTypesList() {
|
||||
getLocationTypes().then(response => new Promise(resolve => {
|
||||
console.log('getLocationTypes', response);
|
||||
this.locationTypes = response.results.filter(t => t.availableForUsers === true);
|
||||
resolve();
|
||||
}))
|
||||
},
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
saveNewLocation() {
|
||||
if (this.checkForm()) {
|
||||
console.log('saveNewLocation', this.selected);
|
||||
let body = {
|
||||
type: 'location',
|
||||
name: this.selected.name,
|
||||
locationType: {
|
||||
id: this.selected.type.id,
|
||||
type: 'location-type'
|
||||
},
|
||||
phonenumber1: this.selected.phonenumber1,
|
||||
phonenumber2: this.selected.phonenumber2,
|
||||
email: this.selected.email,
|
||||
};
|
||||
if (this.selected.addressId) {
|
||||
body = Object.assign(body, {
|
||||
address: {
|
||||
id: this.selected.addressId
|
||||
}
|
||||
});
|
||||
}
|
||||
postLocation(body)
|
||||
.then(
|
||||
location => new Promise(resolve => {
|
||||
console.log('postLocation', location);
|
||||
this.locations.push(location);
|
||||
this.$store.dispatch('updateLocation', location);
|
||||
resolve();
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
).catch(
|
||||
err => {
|
||||
this.errors.push(err.message);
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
submitNewAddress(payload) {
|
||||
console.log('submitNewAddress', payload);
|
||||
this.selected.addressId = payload.addressId;
|
||||
this.addAddress.context.addressId = payload.addressId;
|
||||
this.addAddress.context.edit = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<teleport to="#social-issues-acc">
|
||||
|
||||
|
||||
<div class="mb-3 row">
|
||||
<div class="col-4">
|
||||
<label class="col-form-label">{{ $t('activity.social_issues') }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
|
||||
|
||||
<check-social-issue
|
||||
v-for="issue in socialIssuesList"
|
||||
v-bind:key="issue.id"
|
||||
@@ -14,7 +14,7 @@
|
||||
v-bind:selection="socialIssuesSelected"
|
||||
@updateSelected="updateIssuesSelected">
|
||||
</check-social-issue>
|
||||
|
||||
|
||||
<div class="my-3">
|
||||
<VueMultiselect
|
||||
name="otherIssues"
|
||||
@@ -28,16 +28,15 @@
|
||||
v-bind:taggable="false"
|
||||
v-bind:multiple="false"
|
||||
v-bind:searchable="true"
|
||||
v-bind:allow-empty="true"
|
||||
v-bind:allow-empty="true"
|
||||
v-bind:show-labels="false"
|
||||
v-bind:loading="issueIsLoading"
|
||||
v-bind:placeholder="$t('activity.choose_other_social_issue')"
|
||||
v-bind:options="socialIssuesOther"
|
||||
v-model="value"
|
||||
@select="addIssueInList">
|
||||
</VueMultiselect>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,11 +45,11 @@
|
||||
<label class="col-form-label">{{ $t('activity.social_actions') }}</label>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
|
||||
|
||||
<div v-if="actionIsLoading === true">
|
||||
<i class="chill-green fa fa-circle-o-notch fa-spin fa-lg"></i>
|
||||
</div>
|
||||
|
||||
|
||||
<check-social-action
|
||||
v-if="socialIssuesSelected.length || socialActionsSelected.length"
|
||||
v-for="action in socialActionsList"
|
||||
@@ -59,19 +58,18 @@
|
||||
v-bind:selection="socialActionsSelected"
|
||||
@updateSelected="updateActionsSelected">
|
||||
</check-social-action>
|
||||
|
||||
|
||||
<span v-else class="inline-choice chill-no-data-statement mt-3">
|
||||
{{ $t('activity.select_first_a_social_issue') }}
|
||||
</span>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { readonly } from 'vue';
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import CheckSocialIssue from './SocialIssuesAcc/CheckSocialIssue.vue';
|
||||
import CheckSocialAction from './SocialIssuesAcc/CheckSocialAction.vue';
|
||||
@@ -113,7 +111,7 @@ export default {
|
||||
this.issueIsLoading = true;
|
||||
getSocialIssues().then(response => new Promise((resolve, reject) => {
|
||||
this.$store.commit('updateIssuesOther', response.results);
|
||||
|
||||
|
||||
/* Add in list the issues already associated (if not yet listed)
|
||||
*/
|
||||
this.socialIssuesSelected.forEach(issue => {
|
||||
@@ -121,34 +119,34 @@ export default {
|
||||
this.$store.commit('addIssueInList', issue);
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
||||
/* Remove from multiselect the issues that are not yet in checkbox list
|
||||
*/
|
||||
this.socialIssuesList.forEach(issue => {
|
||||
this.$store.commit('removeIssueInOther', issue);
|
||||
}, this);
|
||||
|
||||
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit('filterList', 'issues');
|
||||
|
||||
|
||||
/* Add in list the actions already associated (if not yet listed)
|
||||
*/
|
||||
this.socialActionsSelected.forEach(action => {
|
||||
this.$store.commit('addActionInList', action);
|
||||
}, this);
|
||||
|
||||
|
||||
/* Filter issues
|
||||
*/
|
||||
this.$store.commit('filterList', 'actions');
|
||||
|
||||
|
||||
this.issueIsLoading = false;
|
||||
resolve();
|
||||
}));
|
||||
}));
|
||||
},
|
||||
methods: {
|
||||
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
|
||||
/* When choosing an issue in multiselect, add it in checkboxes (as selected),
|
||||
remove it from multiselect, and add socialActions concerned
|
||||
*/
|
||||
addIssueInList(value) {
|
||||
@@ -171,30 +169,30 @@ export default {
|
||||
//console.log('updateActionsSelected', actions);
|
||||
this.$store.dispatch('updateActionsSelected', actions);
|
||||
},
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
/* Add socialActions concerned: after reset, loop on each issue selected
|
||||
to get social actions concerned
|
||||
*/
|
||||
updateActionsList() {
|
||||
//console.log('updateActionsList');
|
||||
this.resetActionsList();
|
||||
this.socialIssuesSelected.forEach(item => {
|
||||
|
||||
|
||||
this.actionIsLoading = true;
|
||||
getSocialActionByIssue(item.id)
|
||||
.then(actions => new Promise((resolve, reject) => {
|
||||
|
||||
|
||||
actions.results.forEach(action => {
|
||||
this.$store.commit('addActionInList', action);
|
||||
}, this);
|
||||
|
||||
|
||||
this.$store.commit('filterList', 'actions');
|
||||
|
||||
|
||||
this.actionIsLoading = false;
|
||||
resolve();
|
||||
}));
|
||||
}, this);
|
||||
},
|
||||
/* Reset socialActions List: flush list and restore selected actions
|
||||
/* Reset socialActions List: flush list and restore selected actions
|
||||
*/
|
||||
resetActionsList() {
|
||||
this.$store.commit('resetActionsList');
|
||||
@@ -207,7 +205,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
span.multiselect__single {
|
||||
display: none !important;
|
||||
}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||
|
||||
const appMessages = {
|
||||
const activityMessages = {
|
||||
fr: {
|
||||
activity: {
|
||||
//
|
||||
errors: "Le formulaire contient des erreurs",
|
||||
social_issues: "Problématiques sociales",
|
||||
choose_other_social_issue: "Ajouter une autre problématique sociale...",
|
||||
social_actions: "Actions d'accompagnement",
|
||||
select_first_a_social_issue: "Sélectionnez d'abord une problématique sociale",
|
||||
|
||||
|
||||
//
|
||||
add_persons: "Ajouter des personnes concernées",
|
||||
bloc_persons: "Usagers",
|
||||
@@ -16,12 +17,28 @@ const appMessages = {
|
||||
bloc_persons_not_associated: "Tiers non-pro.",
|
||||
bloc_thirdparty: "Tiers professionnels",
|
||||
bloc_users: "T(M)S",
|
||||
|
||||
//
|
||||
location: "Localisation",
|
||||
choose_location: "Choisissez une localisation",
|
||||
choose_location_type: "Choisissez un type de localisation",
|
||||
create_new_location: "Créer une nouvelle localisation",
|
||||
location_fields: {
|
||||
name: "Nom",
|
||||
type: "Type",
|
||||
phonenumber1: "Téléphone",
|
||||
phonenumber2: "Autre téléphone",
|
||||
email: "Adresse courriel",
|
||||
},
|
||||
create_address: 'Créer une adresse',
|
||||
edit_address: "Modifier l'adresse"
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(appMessages.fr, personMessages.fr);
|
||||
Object.assign(activityMessages.fr, personMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
activityMessages
|
||||
};
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { createApp } from 'vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||
import { appMessages } from './i18n'
|
||||
import { activityMessages } from './i18n'
|
||||
import store from './store'
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
const i18n = _createI18n(appMessages);
|
||||
const i18n = _createI18n(activityMessages);
|
||||
|
||||
const app = createApp({
|
||||
template: `<app></app>`,
|
||||
|
@@ -114,6 +114,10 @@ const store = createStore({
|
||||
state.activity.users = state.activity.users.filter(user => user !== payload);
|
||||
break;
|
||||
};
|
||||
},
|
||||
updateLocation(state, value) {
|
||||
console.log('### mutation: updateLocation', value);
|
||||
state.activity.location = value;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -173,6 +177,12 @@ const store = createStore({
|
||||
break;
|
||||
};
|
||||
commit('removePersonInvolved', payload);
|
||||
},
|
||||
updateLocation({ commit }, value) {
|
||||
console.log('### action: updateLocation', value);
|
||||
let hiddenLocation = document.getElementById("chill_activitybundle_activity_location");
|
||||
hiddenLocation.value = value.id;
|
||||
commit('updateLocation', value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -0,0 +1,88 @@
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label">
|
||||
|
||||
{% if activity.date %}
|
||||
<h3>{{ activity.date|format_date('short') }}</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="duration">
|
||||
{% if activity.durationTime and t.durationTimeVisible %}
|
||||
<p>
|
||||
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
|
||||
{{ activity.durationTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if activity.travelTime and t.travelTimeVisible %}
|
||||
<p>
|
||||
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
|
||||
{{ activity.travelTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</span>
|
||||
<span class="title_action">
|
||||
|
||||
{{ activity.type.name | localize_translatable_string }}
|
||||
|
||||
<ul class="small_in_title">
|
||||
<li>
|
||||
<abbr title="{{ 'location'|trans }}">{{ 'location'|trans ~ ': ' }}</abbr>
|
||||
{# TODO {% if activity.location %}{{ activity.location }}{% endif %} #}
|
||||
Domicile de l'usager
|
||||
</li>
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<abbr title="{{ 'Referrer'|trans }}">{{ 'Referrer'|trans ~ ': ' }}</abbr>
|
||||
{{ activity.user.usernameCanonical }}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<ul class="list-content">
|
||||
{%- if t.reasonsVisible -%}
|
||||
{%- if activity.reasons is not empty -%}
|
||||
<li class="reasons">
|
||||
{% for r in activity.reasons %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialIssuesVisible %}
|
||||
{%- if activity.socialIssues is not empty -%}
|
||||
<li class="social-issues">
|
||||
{% for r in activity.socialIssues %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
{%- if t.socialActionsVisible -%}
|
||||
{%- if activity.socialActions is not empty -%}
|
||||
<li class="social-actions">
|
||||
{% for r in activity.socialActions %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{#
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
|
||||
href="{{ chill_path_add_return_path(
|
||||
"chill_person_accompanying_course_index",
|
||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||
) }}"><i class="fa fa-random"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
#}
|
@@ -5,17 +5,17 @@
|
||||
|
||||
{% if context == 'person' %}
|
||||
{% set blocs = [
|
||||
{ 'title': 'Others persons'|trans,
|
||||
{ 'title': 'Others persons'|trans,
|
||||
'items': entity.persons,
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},
|
||||
{ 'title': 'Third parties'|trans,
|
||||
{ 'title': 'Third parties'|trans,
|
||||
'items': entity.thirdParties,
|
||||
'path' : 'chill_3party_3party_show',
|
||||
'key' : 'thirdparty_id'
|
||||
},
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
'items': entity.users,
|
||||
'path' : 'admin_user_show',
|
||||
'key' : 'id'
|
||||
@@ -23,22 +23,22 @@
|
||||
] %}
|
||||
{% else %}
|
||||
{% set blocs = [
|
||||
{ 'title': 'Persons in accompanying course'|trans,
|
||||
{ 'title': 'Persons in accompanying course'|trans,
|
||||
'items': entity.personsAssociated,
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},
|
||||
{ 'title': 'Third persons'|trans,
|
||||
{ 'title': 'Third persons'|trans,
|
||||
'items': entity.personsNotAssociated,
|
||||
'path' : 'chill_person_view',
|
||||
'key' : 'person_id'
|
||||
},
|
||||
{ 'title': 'Third parties'|trans,
|
||||
{ 'title': 'Third parties'|trans,
|
||||
'items': entity.thirdParties,
|
||||
'path' : 'chill_3party_3party_show',
|
||||
'key' : 'thirdparty_id'
|
||||
},
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
{ 'title': 'Users concerned'|trans,
|
||||
'items': entity.users,
|
||||
'path' : 'admin_user_show',
|
||||
'key' : 'id'
|
||||
@@ -101,3 +101,31 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if (with_display == 'wrap-list') %}
|
||||
<div class="concerned-groups wrap-list">
|
||||
{% for bloc in blocs %}
|
||||
<div class="wl-row">
|
||||
{% if bloc.items|length > 0 %}
|
||||
<div class="wl-col title">
|
||||
<h4>{{ bloc.title }}</h4>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for item in bloc.items %}
|
||||
|
||||
<span class="wl-item {% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
|
||||
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
|
||||
{{ item|chill_entity_render_box({
|
||||
'render': 'raw',
|
||||
'addAltNames': false
|
||||
}) }}
|
||||
</a>
|
||||
</span>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@@ -54,7 +54,10 @@
|
||||
{{ form_row(edit_form.date) }}
|
||||
{% endif %}
|
||||
|
||||
{# TODO .. location #}
|
||||
{%- if edit_form.location is defined -%}
|
||||
{{ form_row(edit_form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if edit_form.durationTime is defined -%}
|
||||
{{ form_row(edit_form.durationTime) }}
|
||||
|
@@ -6,10 +6,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="activity-edit">
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_activity') }}
|
||||
{% endblock %}
|
||||
|
@@ -22,10 +22,10 @@
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="activity-edit">
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:edit.html.twig' %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ edit_form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="activity-list context-{{ context }}">
|
||||
<div class="context-{{ context }}">
|
||||
|
||||
{% if activities|length == 0 %}
|
||||
<p class="chill-no-data-statement">
|
||||
@@ -7,176 +7,56 @@
|
||||
</p>
|
||||
|
||||
{% else %}
|
||||
<div class="flex-table">
|
||||
<div class="flex-table activity-list">
|
||||
{% for activity in activities %}
|
||||
{% set t = activity.type %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row main">
|
||||
<div class="item-col">
|
||||
|
||||
{% if activity.date %}
|
||||
<h3>{{ activity.date|format_date('long') }}</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="duration">
|
||||
{% if activity.durationTime and t.durationTimeVisible %}
|
||||
<p>
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ activity.durationTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if activity.travelTime and t.travelTimeVisible %}
|
||||
<p>
|
||||
<i class="fa fa-fw fa-car"></i>
|
||||
{{ activity.travelTime|date('H:i') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
|
||||
<div class="mt-3">
|
||||
<a class="btn btn-sm btn-outline-primary"
|
||||
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
|
||||
href="{{ chill_path_add_return_path(
|
||||
"chill_person_accompanying_course_index",
|
||||
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
|
||||
) }}"><i class="fa fa-random"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
|
||||
<div class="float-button top">
|
||||
<div class="box">
|
||||
|
||||
<div class="action">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-show "></a>
|
||||
</li>
|
||||
{% if no_action is not defined or no_action == false %}
|
||||
{# TODO
|
||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
|
||||
#}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-update "></a>
|
||||
</li>
|
||||
{# TODO
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACTIVITY_DELETE', activity) %}
|
||||
#}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||
class="btn btn-sm btn-delete "></a>
|
||||
</li>
|
||||
{# TODO
|
||||
{% endif %}
|
||||
#}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul class="list-content">
|
||||
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<abbr class="referrer" title="{{ 'Referrer'|trans }}">ref:</abbr>
|
||||
<b>{{ activity.user.usernameCanonical }}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<b>{{ activity.type.name | localize_translatable_string }}</b>
|
||||
|
||||
{% if activity.attendee is not null and t.attendeeVisible %}
|
||||
{% if activity.attendee %}
|
||||
{{ '→ ' ~ 'present'|trans|capitalize }}
|
||||
{% else %}
|
||||
{{ '→ ' ~ 'not present'|trans|capitalize }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<b>{{ 'location'|trans ~ ': ' }}</b>
|
||||
Domicile de l'usager
|
||||
|
||||
{# TODO {% if activity.location %}{{ activity.location }}{% endif %}
|
||||
#}
|
||||
|
||||
</li>
|
||||
|
||||
{%- if t.reasonsVisible -%}
|
||||
<li>
|
||||
{%- if activity.reasons is not empty -%}
|
||||
{% for r in activity.reasons %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{%- if t.socialIssuesVisible %}
|
||||
<li class="social-issues">
|
||||
{%- if activity.socialIssues is not empty -%}
|
||||
{% for r in activity.socialIssues %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{%- if t.socialActionsVisible -%}
|
||||
<li class="social-actions">
|
||||
{%- if activity.socialActions is not empty -%}
|
||||
{% for r in activity.socialActions %}
|
||||
{{ r|chill_entity_render_box }}
|
||||
{% endfor %}
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="item-row">
|
||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
||||
</div>
|
||||
|
||||
|
||||
{% if activity.comment.comment is not empty
|
||||
or activity.persons|length > 0
|
||||
or activity.thirdParties|length > 0
|
||||
or activity.users|length > 0
|
||||
%}
|
||||
<div class="item-row comment separator">
|
||||
{% if activity.comment.comment is not empty %}
|
||||
{{ activity.comment|chill_entity_render_box({
|
||||
'disable_markdown': false,
|
||||
'limit_lines': 3,
|
||||
'metadata': false,
|
||||
}) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="item-row details">
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||
'context': context,
|
||||
'with_display': 'bloc',
|
||||
'entity': activity,
|
||||
'badge_person': true
|
||||
} %}
|
||||
</div>
|
||||
<div class="main">
|
||||
{% if activity.comment.comment is not empty %}
|
||||
{{ activity.comment|chill_entity_render_box({
|
||||
'disable_markdown': false,
|
||||
'limit_lines': 3,
|
||||
'metadata': false,
|
||||
}) }}
|
||||
{% endif %}
|
||||
|
||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||
'context': context,
|
||||
'with_display': 'row',
|
||||
'entity': activity,
|
||||
'badge_person': true
|
||||
} %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator">
|
||||
<div class="updatedBy"></div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-show "></a>
|
||||
</li>
|
||||
{% if no_action is not defined or no_action == false %}
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
|
||||
class="btn btn-sm btn-update "></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
|
||||
class="btn btn-sm btn-delete "></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@@ -0,0 +1,10 @@
|
||||
<div class="activity-list">
|
||||
{% for activity in activities | slice(0,5) %}
|
||||
{% set t = activity.type %}
|
||||
|
||||
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"></a>
|
||||
|
||||
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
@@ -55,7 +55,10 @@
|
||||
{{ form_row(form.date) }}
|
||||
{% endif %}
|
||||
|
||||
{# TODO .. location #}
|
||||
{%- if form.location is defined -%}
|
||||
{{ form_row(form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.durationTime is defined -%}
|
||||
{{ form_row(form.durationTime) }}
|
||||
|
@@ -6,10 +6,10 @@
|
||||
|
||||
{% block personcontent %}
|
||||
<div class="activity-new">
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %}
|
||||
|
||||
|
||||
<div id="activity"></div> {# <=== vue component #}
|
||||
{% include 'ChillActivityBundle:Activity:new.html.twig' with {'context': 'person'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
|
@@ -62,6 +62,21 @@
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
<dd>{{ entity.date|format_date('long') }}</dd>
|
||||
|
||||
{% if t.locationVisible %}
|
||||
<dt class="inline">{{ 'Activity location'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.location is not null %}
|
||||
<p>
|
||||
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ entity.location.name }}
|
||||
</p>
|
||||
{{ entity.location.address|chill_entity_render_box }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if t.durationTimeVisible %}
|
||||
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
|
||||
<dd>{% if entity.durationTime is not null %}
|
||||
@@ -73,7 +88,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if t.travelTimeVisible %}
|
||||
<dt class="inline">{{ 'Travel Time'|trans }}</dt>
|
||||
<dt class="inline">{{ 'Travel time'|trans }}</dt>
|
||||
<dd>{% if entity.travelTime is not null %}
|
||||
{{ entity.travelTime|date('H:i') }}
|
||||
{% else %}
|
||||
|
@@ -4,7 +4,7 @@ namespace Chill\Migrations\Activity;
|
||||
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
/**
|
||||
* Add an "active" column on activitytype table
|
||||
|
@@ -22,7 +22,6 @@ final class Version20210401090853 extends AbstractMigration
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SEQUENCE activitytypecategory_id_seq INCREMENT BY 1 MINVALUE 1 START 1000');
|
||||
$this->addSql('CREATE TABLE activitytypecategory (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('COMMENT ON COLUMN activitytypecategory.name IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('INSERT INTO activitytypecategory VALUES(1, \'{"fr": "Défaut", "en": "Default"}\', true)');
|
||||
|
||||
}
|
||||
|
@@ -51,7 +51,6 @@ final class Version20210408122329 extends AbstractMigration
|
||||
$this->addSql('ALTER TABLE activitytype ADD socialDataLabel VARCHAR(255) DEFAULT \'\' NOT NULL');
|
||||
$this->addSql('ALTER TABLE activitytype ALTER name SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE activitytype ALTER active DROP DEFAULT');
|
||||
$this->addSql('COMMENT ON COLUMN activitytype.name IS \'(DC2Type:json_array)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
|
@@ -6,7 +6,6 @@ Duration time: Durée
|
||||
Duration Time: Durée
|
||||
durationTime: durée
|
||||
Travel time: Durée de déplacement
|
||||
Reasons: Sujets
|
||||
Attendee: Présence de la personne
|
||||
attendee: présence de la personne
|
||||
list_reasons: liste des sujets
|
||||
@@ -23,6 +22,7 @@ Update: Mettre à jour
|
||||
Update activity: Modifier l'activité
|
||||
Scope: Cercle
|
||||
Activity data: Données de l'activité
|
||||
Activity location: Localisation de l'activité
|
||||
No reason associated: Aucun sujet
|
||||
No social issues associated: Aucune problématique sociale
|
||||
No social actions associated: Aucune action d'accompagnement
|
||||
@@ -99,10 +99,13 @@ CHILL_ACTIVITY_LIST: Liste des activités
|
||||
Activities: Activités
|
||||
Activity configuration: Configuration des activités
|
||||
Activity configuration menu: Configuration des activités
|
||||
Activity Types: Types d'activité
|
||||
Activity types: Types d'activité
|
||||
Activity type configuration: Configuration des categories d'activités
|
||||
Activity Reasons: Sujets d'une activité
|
||||
Activity Reasons Category: Catégories de sujet d'activités
|
||||
Activity Types Categories: Catégories des types d'activité
|
||||
Activity Presences: Presences des activités
|
||||
|
||||
|
||||
# Crud
|
||||
crud:
|
||||
@@ -210,3 +213,5 @@ Group by reasons: Sujet d'activité
|
||||
Aggregate by activity user: Aggréger par utilisateur lié à l'activité
|
||||
Aggregate by activity type: Aggréger par type d'activité
|
||||
Aggregate by activity reason: Aggréger par sujet de l'activité
|
||||
|
||||
Last activities: Les dernières activités
|
||||
|
@@ -7,6 +7,7 @@ namespace Chill\AsideActivityBundle\Controller;
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||
use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository;
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
@@ -22,9 +23,9 @@ final class AsideActivityController extends CRUDController
|
||||
$this->categoryRepository = $categoryRepository;
|
||||
}
|
||||
|
||||
protected function buildQueryEntities(string $action, Request $request)
|
||||
protected function buildQueryEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$qb = parent::buildQueryEntities($action, $request);
|
||||
$qb = parent::buildQueryEntities($action, $request, $filterOrder);
|
||||
|
||||
if ('index' === $action) {
|
||||
$qb->where($qb->expr()->eq('e.agent', ':user'));
|
||||
@@ -53,7 +54,7 @@ final class AsideActivityController extends CRUDController
|
||||
$asideActivity = new AsideActivity();
|
||||
|
||||
$duration = $request->query->get('duration', '300');
|
||||
$duration = \DateTime::createFromFormat('U', $duration);
|
||||
$duration = \DateTime::createFromFormat('U', $duration);
|
||||
$asideActivity->setDuration($duration);
|
||||
|
||||
$categoryId = $request->query->get('type', 7);
|
||||
|
@@ -19,8 +19,6 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
public function load(array $configs, ContainerBuilder $container): void
|
||||
{
|
||||
@@ -111,4 +109,4 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@ final class AsideActivityFormType extends AbstractType
|
||||
$builder
|
||||
->add('agent', EntityType::class,
|
||||
[
|
||||
'label' => 'Agent',
|
||||
'label' => 'For agent',
|
||||
'required' => true,
|
||||
'class' => User::class,
|
||||
'data' => $this->storage->getToken()->getUser(),
|
||||
|
@@ -32,7 +32,7 @@ final class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuild
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 900,
|
||||
'explain' => "Configure aside activities categories"
|
||||
'explain' => "Aside activity type configuration"
|
||||
]);
|
||||
} else {
|
||||
$menu
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
|
||||
|
||||
{% block admin_content %}
|
||||
<h1>{{ 'Aside Activity Type list'|trans }}</h1>
|
||||
<h1>{{ 'Aside Activity Type List'|trans }}</h1>
|
||||
|
||||
<table class="records_list table table-bordered border-dark">
|
||||
<thead>
|
||||
|
@@ -30,7 +30,9 @@ final class CategoryRender implements ChillEntityRenderInterface
|
||||
{
|
||||
$options = array_merge(self::DEFAULT_ARGS, $options);
|
||||
|
||||
$titles[] = $this->translatableStringHelper->localize($asideActivityCategory->getTitle());
|
||||
$titles = [
|
||||
$this->translatableStringHelper->localize($asideActivityCategory->getTitle()),
|
||||
];
|
||||
|
||||
while ($asideActivityCategory->hasParent()) {
|
||||
$asideActivityCategory = $asideActivityCategory->getParent();
|
||||
|
@@ -3,6 +3,7 @@ Show the aside activity: Voir l'activité annexe
|
||||
Edit the aside activity: Modifier l'activité annexe
|
||||
Remove aside activity: Supprimer l'activité annexe
|
||||
Aside activity: Activité annexe
|
||||
Aside Activity Type List: Liste des catégories d'activités annexes
|
||||
Duration time: Durée
|
||||
durationTime: durée
|
||||
user_username: nom de l'utilisateur
|
||||
@@ -45,7 +46,7 @@ Back to the list: Retour à la liste
|
||||
Choose the duration: Choisir la durée
|
||||
Choose a category: Choisir une catégorie
|
||||
Is active: Actif
|
||||
Agent: Utilisateur
|
||||
For agent: Pour l'utilisateur
|
||||
date: Date
|
||||
Duration: Durée
|
||||
Note: Note
|
||||
@@ -156,6 +157,11 @@ The activity has been successfully removed.: L'activité a été supprimée.
|
||||
|
||||
#Menu
|
||||
Create an aside activity: "Créer une activité annexe"
|
||||
Aside activity categories: Catégories des activités annexes
|
||||
Aside activity configuration menu: "Menu de configuration des activités annexes"
|
||||
Aside activity configuration: "Configuration des activités annexes"
|
||||
Phonecall: "Appel téléphonique"
|
||||
|
||||
# admin
|
||||
Aside activities: Activités annexes
|
||||
Aside activity types: Types d'activités annexes
|
||||
Aside activity type configuration: Configuration des categories d'activités annexes
|
||||
|
@@ -97,9 +97,9 @@ class CalendarController extends AbstractController
|
||||
'calendarItems' => $calendarItems,
|
||||
'user' => $user
|
||||
]);
|
||||
}
|
||||
|
||||
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||
|
||||
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||
$total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod);
|
||||
$paginator = $this->paginator->create($total);
|
||||
$calendarItems = $this->calendarRepository->findBy(
|
||||
@@ -117,6 +117,8 @@ class CalendarController extends AbstractController
|
||||
'paginator' => $paginator
|
||||
]);
|
||||
}
|
||||
|
||||
throw new \Exception('Unable to list actions.');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,6 +236,7 @@ class CalendarController extends AbstractController
|
||||
'professionalsId' => $professionalsId,
|
||||
'date' => $entity->getStartDate()->format('Y-m-d'),
|
||||
'durationTime' => $durationTimeInMinutes,
|
||||
'location' => $entity->getLocation()->getId(),
|
||||
'comment' => $entity->getComment()->getComment(),
|
||||
];
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\CalendarBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@@ -136,6 +137,13 @@ class Calendar
|
||||
*/
|
||||
private ?bool $sendSMS;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
|
||||
* @groups({"read"})
|
||||
*/
|
||||
private ?Location $location = null;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comment = new CommentEmbeddable();
|
||||
@@ -354,7 +362,7 @@ class Calendar
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPersonsAssociated(): array
|
||||
public function getPersonsAssociated(): array
|
||||
{
|
||||
if (null !== $this->accompanyingPeriod) {
|
||||
$personsAssociated = [];
|
||||
@@ -363,11 +371,11 @@ class Calendar
|
||||
$personsAssociated[] = $participation->getPerson();
|
||||
}
|
||||
}
|
||||
return $personsAssociated;
|
||||
return $personsAssociated;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public function getPersonsNotAssociated(): array
|
||||
{
|
||||
if (null !== $this->accompanyingPeriod) {
|
||||
@@ -407,4 +415,22 @@ class Calendar
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Location|null
|
||||
*/
|
||||
public function getLocation(): ?Location
|
||||
{
|
||||
return $this->location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Location|null $location
|
||||
* @return Calendar
|
||||
*/
|
||||
public function setLocation(?Location $location): Calendar
|
||||
{
|
||||
$this->location = $location;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ class CancelReason
|
||||
private $canceledBy;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name = [];
|
||||
|
||||
|
@@ -25,7 +25,7 @@ class Invite
|
||||
private User $user;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private array $status = [];
|
||||
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\CalendarBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Location;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@@ -155,7 +156,7 @@ class CalendarType extends AbstractType
|
||||
}
|
||||
))
|
||||
;
|
||||
|
||||
|
||||
$builder->add('calendarRange', HiddenType::class);
|
||||
$builder->get('calendarRange')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
@@ -178,6 +179,20 @@ class CalendarType extends AbstractType
|
||||
))
|
||||
;
|
||||
|
||||
$builder->add('location', HiddenType::class)
|
||||
->get('location')
|
||||
->addModelTransformer(new CallbackTransformer(
|
||||
function (?Location $location): string {
|
||||
if (null === $location) {
|
||||
return '';
|
||||
}
|
||||
return $location->getId();
|
||||
},
|
||||
function (?string $id): ?Location {
|
||||
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
|
||||
}
|
||||
))
|
||||
;
|
||||
|
||||
// $builder->add('invites', HiddenType::class);
|
||||
// $builder->get('invites')
|
||||
|
92
src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php
Normal file
92
src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace Chill\CalendarBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Chill\TaskBundle\Templating\UI\CountNotificationTask;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Champs-Libres
|
||||
*/
|
||||
class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* @var CountNotificationTask
|
||||
*/
|
||||
public $counter;
|
||||
|
||||
/*
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
public $tokenStorage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
public $translator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationCheckerInterface
|
||||
*/
|
||||
public $authorizationChecker;
|
||||
|
||||
public function __construct(
|
||||
CountNotificationTask $counter,
|
||||
TokenStorageInterface $tokenStorage,
|
||||
TranslatorInterface $translator,
|
||||
AuthorizationCheckerInterface $authorizationChecker
|
||||
) {
|
||||
$this->counter = $counter;
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->translator = $translator;
|
||||
$this->authorizationChecker = $authorizationChecker;
|
||||
}
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
|
||||
if ($this->authorizationChecker->isGranted('ROLE_USER')){
|
||||
$menu->addChild("My calendar list", [
|
||||
'route' => 'chill_calendar_calendar_list'
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 9,
|
||||
'icon' => 'tasks'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return [ 'user' ];
|
||||
}
|
||||
|
||||
}
|
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<concerned-groups></concerned-groups>
|
||||
<location></location>
|
||||
<teleport to="#calendarControls">
|
||||
<calendar-user-selector
|
||||
<calendar-user-selector
|
||||
v-bind:users="users"
|
||||
v-bind:calendarEvents="calendarEvents"
|
||||
v-bind:calendarEvents="calendarEvents"
|
||||
v-bind:updateEventsSource="updateEventsSource"
|
||||
v-bind:showMyCalendar="showMyCalendar"
|
||||
v-bind:toggleMyCalendar="toggleMyCalendar"
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
<script>
|
||||
import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue';
|
||||
import Location from 'ChillActivityAssets/vuejs/Activity/components/Location.vue';
|
||||
import CalendarUserSelector from '../_components/CalendarUserSelector/CalendarUserSelector.vue';
|
||||
import '@fullcalendar/core/vdom'; // solves problem with Vite
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
@@ -35,6 +37,7 @@ export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ConcernedGroups,
|
||||
Location,
|
||||
CalendarUserSelector,
|
||||
FullCalendar
|
||||
},
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n'
|
||||
import { calendarUserSelectorMessages } from '../_components/CalendarUserSelector/js/i18n';
|
||||
import { activityMessages } from 'ChillActivityAssets/vuejs/Activity/i18n';
|
||||
|
||||
const appMessages = {
|
||||
fr: {
|
||||
@@ -17,6 +18,7 @@ const appMessages = {
|
||||
|
||||
Object.assign(appMessages.fr, personMessages.fr);
|
||||
Object.assign(appMessages.fr, calendarUserSelectorMessages.fr);
|
||||
Object.assign(appMessages.fr, activityMessages.fr);
|
||||
|
||||
export {
|
||||
appMessages
|
||||
|
@@ -66,6 +66,11 @@ const store = createStore({
|
||||
setEvents(state, payload) {
|
||||
console.log(payload)
|
||||
state.currentEvent = {start: payload.start, end: payload.end}
|
||||
},
|
||||
// Location
|
||||
updateLocation(state, value) {
|
||||
console.log('### mutation: updateLocation', value);
|
||||
state.activity.location = value;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@@ -130,6 +135,14 @@ const store = createStore({
|
||||
commit('setEvents', payload);
|
||||
},
|
||||
|
||||
// Location
|
||||
updateLocation({ commit }, value) {
|
||||
console.log('### action: updateLocation', value);
|
||||
let hiddenLocation = document.getElementById("chill_calendarbundle_calendar_location");
|
||||
hiddenLocation.value = value.id;
|
||||
commit('updateLocation', value);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -38,7 +38,10 @@
|
||||
{{ form_row(form.calendarRange) }}
|
||||
{% endif %}
|
||||
|
||||
.. location
|
||||
{%- if form.location is defined -%}
|
||||
{{ form_row(form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.comment is defined -%}
|
||||
{{ form_row(form.comment) }}
|
||||
@@ -56,7 +59,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a
|
||||
<a
|
||||
class="btn btn-cancel"
|
||||
{%- if context == 'user' -%}
|
||||
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'user_id': user.id } )}}"
|
||||
|
@@ -6,14 +6,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="calendar-edit">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -21,7 +17,7 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
@@ -40,4 +36,4 @@
|
||||
|
||||
{% block block_post_menu %}
|
||||
<div id="calendarControls"></div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
@@ -4,14 +4,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="calendar-edit">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:edit.html.twig' with {'context': 'user'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -19,7 +15,7 @@
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingModifiedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
|
@@ -34,7 +34,10 @@
|
||||
{{ form_row(form.endDate) }}
|
||||
{% endif %}
|
||||
|
||||
.. location
|
||||
{%- if form.location is defined -%}
|
||||
{{ form_row(form.location) }}
|
||||
<div id="location"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.comment is defined -%}
|
||||
{{ form_row(form.comment) }}
|
||||
@@ -48,9 +51,9 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a
|
||||
<a
|
||||
class="btn btn-cancel"
|
||||
{%- if context == 'person' -%}
|
||||
{%- if context == 'person' -%}
|
||||
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'person_id': person.id } )}}"
|
||||
{%- else -%}
|
||||
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'accompanying_period_id': accompanyingCourse.id } )}}"
|
||||
|
@@ -6,25 +6,21 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="calendar-new">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar"></div> {# <=== vue component #}
|
||||
{% include 'ChillCalendarBundle:Calendar:new.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
window.entity = {{ entity_json|json_encode|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_calendar') }}
|
||||
{% endblock %}
|
||||
|
@@ -36,6 +36,20 @@
|
||||
<dt class="inline">{{ 'status'|trans }}</dt>
|
||||
<dd>{{ entity.status }}</dd>
|
||||
|
||||
|
||||
<dt class="inline">{{ 'calendar location'|trans }}</dt>
|
||||
<dd>
|
||||
{% if entity.location is not null %}
|
||||
<p>
|
||||
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
|
||||
{{ entity.location.name }}
|
||||
</p>
|
||||
{{ entity.location.address|chill_entity_render_box }}
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
|
||||
{% if not entity.comment.isEmpty %}
|
||||
<dt class="inline">{{ 'calendar comment'|trans }}</dt>
|
||||
<dd>{{ entity.comment|chill_entity_render_box }}</dd>
|
||||
|
@@ -6,10 +6,8 @@
|
||||
|
||||
{% block content -%}
|
||||
<div class="calendar-show">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'accompanyingCourse'} %}
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@@ -4,10 +4,8 @@
|
||||
|
||||
{% block content -%}
|
||||
<div class="calendar-show">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10 col-xxl">
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'ChillCalendarBundle:Calendar:show.html.twig' with {'context': 'user'} %}
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@@ -50,10 +50,7 @@ final class Version20210715141731 extends AbstractMigration
|
||||
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startDate IS \'(DC2Type:date_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.endDate IS \'(DC2Type:date_immutable)\'');
|
||||
$this->addSql('CREATE TABLE chill_calendar.cancel_reason (id INT NOT NULL, active BOOLEAN NOT NULL, canceledBy JSON NOT NULL, name JSON NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.name IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.canceledBy IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('CREATE TABLE chill_calendar.invite (id INT NOT NULL, user_id INT DEFAULT NULL, status JSON NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('COMMENT ON COLUMN chill_calendar.invite.status IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('CREATE INDEX IDX_F517FFA7A76ED395 ON chill_calendar.invite (user_id)');
|
||||
$this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACD7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
@@ -74,7 +71,7 @@ final class Version20210715141731 extends AbstractMigration
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_calendar.calendar_to_persons DROP CONSTRAINT FK_AEE94715A40A2C8');
|
||||
$this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals DROP CONSTRAINT FK_FADF2C77A40A2C8');
|
||||
$this->addSql('ALTER TABLE chill_calendar.calendar_to_thirdparties DROP CONSTRAINT FK_2BAB7EFDA40A2C8');
|
||||
|
@@ -34,6 +34,5 @@ final class Version20210723074557 extends AbstractMigration
|
||||
$this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals ADD CONSTRAINT fk_fadf2c77217bbb47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy TYPE JSON');
|
||||
$this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy DROP DEFAULT');
|
||||
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.canceledby IS \'(DC2Type:json_array)\'');
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ start date: début du rendez-vous
|
||||
end date: fin du rendez-vous
|
||||
cancel reason: motif d'annulation
|
||||
status: Statut du rendez-vous
|
||||
calendar location: Localistion du rendez-vous
|
||||
calendar comment: Remarque sur le rendez-vous
|
||||
sendSMS: Envoi d'un SMS
|
||||
Send s m s: Envoi d'un SMS ?
|
||||
|
@@ -28,27 +28,31 @@ class ChillCustomFieldsExtension extends Extension implements PrependExtensionIn
|
||||
$loader->load('services/fixtures.yaml');
|
||||
$loader->load('services/controller.yaml');
|
||||
$loader->load('services/command.yaml');
|
||||
|
||||
|
||||
//add at least a blank array at 'customizable_entities' options
|
||||
//$customizable_entities = (isset($config['customizables_entities'])
|
||||
// && $config['customizables_entities'] !== FALSE)
|
||||
//$customizable_entities = (isset($config['customizables_entities'])
|
||||
// && $config['customizables_entities'] !== FALSE)
|
||||
// ? $config['customizables_entities'] : array();
|
||||
|
||||
$container->setParameter('chill_custom_fields.customizables_entities',
|
||||
|
||||
$container->setParameter('chill_custom_fields.customizables_entities',
|
||||
$config['customizables_entities']);
|
||||
$container->setParameter('chill_custom_fields.show_empty_values',
|
||||
$container->setParameter('chill_custom_fields.show_empty_values',
|
||||
$config['show_empty_values_in_views']);
|
||||
}
|
||||
|
||||
|
||||
/* (non-PHPdoc)
|
||||
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
|
||||
*/
|
||||
public function prepend(ContainerBuilder $container)
|
||||
public function prepend(ContainerBuilder $container)
|
||||
{
|
||||
// add form layout to twig resources
|
||||
$twigConfig['form_themes'][] = 'ChillCustomFieldsBundle:Form:fields.html.twig';
|
||||
$twigConfig = [
|
||||
'form_themes' => [
|
||||
'ChillCustomFieldsBundle:Form:fields.html.twig',
|
||||
],
|
||||
];
|
||||
$container->prependExtensionConfig('twig', $twigConfig);
|
||||
|
||||
|
||||
//add routes for custom bundle
|
||||
$container->prependExtensionConfig('chill_main', array(
|
||||
'routing' => array(
|
||||
|
@@ -40,7 +40,7 @@ class CustomField
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
@@ -61,18 +61,18 @@ class CustomField
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active = true;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $options = array();
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
@@ -82,14 +82,14 @@ class CustomField
|
||||
* @ORM\Column(type="float")
|
||||
*/
|
||||
private $ordering;
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $required = FALSE;
|
||||
|
||||
|
||||
const ONE_TO_ONE = 1;
|
||||
const ONE_TO_MANY = 2;
|
||||
|
||||
@@ -102,7 +102,7 @@ class CustomField
|
||||
*/
|
||||
private $customFieldGroup;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
@@ -112,7 +112,7 @@ class CustomField
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -120,15 +120,15 @@ class CustomField
|
||||
{
|
||||
return $this->slug;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set type
|
||||
*
|
||||
@@ -152,7 +152,7 @@ class CustomField
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set active
|
||||
*
|
||||
@@ -229,9 +229,9 @@ class CustomField
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
|
||||
|
||||
} else {
|
||||
return $this->name;
|
||||
};
|
||||
@@ -272,7 +272,7 @@ class CustomField
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $slug
|
||||
* @return $this
|
||||
@@ -282,20 +282,20 @@ class CustomField
|
||||
$this->slug = $slug;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* alias for isRequired
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function getRequired()
|
||||
{
|
||||
return $this->isRequired();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* return true if the field required
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isRequired()
|
||||
@@ -308,5 +308,5 @@ class CustomField
|
||||
$this->required = $required;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -39,21 +39,21 @@ class Option
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=15)
|
||||
*/
|
||||
private $key;
|
||||
|
||||
|
||||
/**
|
||||
* A json representation of text (multilingual)
|
||||
*
|
||||
* @var array
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $text;
|
||||
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
* @ORM\OneToMany(
|
||||
@@ -61,7 +61,7 @@ class Option
|
||||
* mappedBy="parent")
|
||||
*/
|
||||
private $children;
|
||||
|
||||
|
||||
/**
|
||||
* @var Option
|
||||
* @ORM\ManyToOne(
|
||||
@@ -70,19 +70,19 @@ class Option
|
||||
* @ORM\JoinColumn(nullable=true)
|
||||
*/
|
||||
private $parent;
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", length=50, name="internal_key")
|
||||
*/
|
||||
private $internalKey = '';
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active = true;
|
||||
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
@@ -90,7 +90,7 @@ class Option
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -98,7 +98,7 @@ class Option
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -106,7 +106,7 @@ class Option
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
@@ -114,7 +114,7 @@ class Option
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Option
|
||||
*/
|
||||
@@ -122,7 +122,7 @@ class Option
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return $this
|
||||
@@ -132,7 +132,7 @@ class Option
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $text
|
||||
* @return $this
|
||||
@@ -142,7 +142,7 @@ class Option
|
||||
$this->text = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Option|null $parent
|
||||
* @return $this
|
||||
@@ -153,16 +153,16 @@ class Option
|
||||
$this->key = $parent->getKey();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasParent()
|
||||
{
|
||||
return $this->parent === NULL ? false : true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -170,7 +170,7 @@ class Option
|
||||
{
|
||||
return $this->internalKey;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@@ -178,7 +178,7 @@ class Option
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
@@ -186,7 +186,7 @@ class Option
|
||||
{
|
||||
return $this->isActive();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $internal_key
|
||||
* @return $this
|
||||
@@ -196,7 +196,7 @@ class Option
|
||||
$this->internalKey = $internal_key;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $active
|
||||
* @return $this
|
||||
@@ -206,5 +206,5 @@ class Option
|
||||
$this->active = $active;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class CustomFieldsGroup
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
@@ -76,15 +76,15 @@ class CustomFieldsGroup
|
||||
* @var array|null
|
||||
*/
|
||||
private $activeCustomFields = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $options = array();
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CustomFieldsGroup constructor.
|
||||
*/
|
||||
@@ -115,7 +115,7 @@ class CustomFieldsGroup
|
||||
{
|
||||
$this->customFields->removeElement($customField);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
@@ -142,7 +142,7 @@ class CustomFieldsGroup
|
||||
|
||||
return $this->activeCustomFields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
@@ -184,9 +184,9 @@ class CustomFieldsGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return '';
|
||||
|
||||
|
||||
} else {
|
||||
return $this->name;
|
||||
}
|
||||
@@ -214,10 +214,10 @@ class CustomFieldsGroup
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get options array
|
||||
*
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions()
|
||||
@@ -227,7 +227,7 @@ class CustomFieldsGroup
|
||||
|
||||
/**
|
||||
* set options array
|
||||
*
|
||||
*
|
||||
* @param array $options
|
||||
* @return CustomFieldsGroup
|
||||
*/
|
||||
|
@@ -1,16 +1,16 @@
|
||||
{#
|
||||
* Copyright (C) 2014, Champs Libres Cooperative SCRLFS, <http://www.champs-libres.coop>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#}
|
||||
@@ -33,7 +33,7 @@
|
||||
{# CFChoice : render the different elements in a choice list #}
|
||||
{% block cf_choices_row %}
|
||||
<h3>{{ 'Choices'|trans }}</h3>
|
||||
|
||||
|
||||
<div id="{{ form.vars.id }}" data-prototype="{{- form_row(form.vars.prototype.children.name)
|
||||
~ form_row(form.vars.prototype.children.active)
|
||||
~ form_row(form.vars.prototype.children.slug) -}}">
|
||||
@@ -47,8 +47,8 @@
|
||||
{% endfor %}
|
||||
</tbody></table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{# we use javascrit to add an additional element. All functions are personnalized with the id ( = form.vars.id) #}
|
||||
<script type="text/javascript">
|
||||
function addElementInDiv(div_id) {
|
||||
@@ -109,3 +109,13 @@
|
||||
|
||||
{# The choice_with_other_widget widget is defined in the main bundle #}
|
||||
|
||||
{% block pick_address_row %}
|
||||
{{ form_label(form) }}
|
||||
{{ form_errors(form) }}
|
||||
{{ form_widget(form) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block pick_address_widget %}
|
||||
{{ form_widget(form) }}
|
||||
<div data-input-address-container="{{ form.vars.uniqid }}"></div>
|
||||
{% endblock %}
|
||||
|
@@ -145,5 +145,7 @@ class DocGeneratorTemplateController extends AbstractController
|
||||
} catch (TransferException $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
throw new \Exception('Unable to generate document.');
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ class DocGeneratorTemplate
|
||||
private int $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private array $name = [];
|
||||
|
@@ -22,7 +22,6 @@ final class Version20210812214310 extends AbstractMigration
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT');
|
||||
$this->addSql('ALTER TABLE chill_docgen_template ADD context VARCHAR(255)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_docgen_template.name IS \'(DC2Type:json_array)\'');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
|
@@ -4,6 +4,7 @@ namespace Chill\DocStoreBundle\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||
use Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType;
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||
@@ -16,32 +17,27 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* Class DocumentAccompanyingCourseController
|
||||
*
|
||||
* @package Chill\DocStoreBundle\Controller
|
||||
* @Route("/{_locale}/parcours/{course}/document")
|
||||
*
|
||||
* TODO faire un controller abstrait ?
|
||||
*/
|
||||
class DocumentAccompanyingCourseController extends AbstractController
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
|
||||
/**
|
||||
* @var EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
|
||||
/**
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
|
||||
|
||||
/**
|
||||
* DocumentAccompanyingCourseController constructor.
|
||||
|
||||
@@ -50,15 +46,15 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
* @param AuthorizationHelper $authorizationHelper
|
||||
*/
|
||||
public function __construct(
|
||||
TranslatorInterface $translator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
TranslatorInterface $translator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
AuthorizationHelper $authorizationHelper
|
||||
) {
|
||||
$this->translator = $translator;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @Route("/", name="accompanying_course_document_index", methods="GET")
|
||||
*/
|
||||
@@ -70,7 +66,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
throw $this->createNotFoundException('Accompanying period not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $course);
|
||||
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course);
|
||||
|
||||
$documents = $em
|
||||
->getRepository("ChillDocStoreBundle:AccompanyingCourseDocument")
|
||||
@@ -78,7 +74,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
['course' => $course],
|
||||
['date' => 'DESC']
|
||||
);
|
||||
|
||||
|
||||
return $this->render(
|
||||
'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig',
|
||||
[
|
||||
@@ -96,13 +92,13 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
throw $this->createNotFoundException('Accompanying period not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $course);
|
||||
|
||||
$document = new AccompanyingCourseDocument();
|
||||
$document->setUser($this->getUser());
|
||||
$document->setCourse($course);
|
||||
$document->setDate(new \DateTime('Now'));
|
||||
|
||||
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::CREATE, $document);
|
||||
|
||||
$form = $this->createForm(AccompanyingCourseDocumentType::class, $document);
|
||||
$form->handleRequest($request);
|
||||
|
||||
@@ -114,7 +110,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$em->persist($document);
|
||||
$em->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->translator->trans("The document is successfully registered"));
|
||||
|
||||
return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
|
||||
@@ -134,9 +130,8 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
*/
|
||||
public function show(AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course);
|
||||
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', $document);
|
||||
|
||||
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE_DETAILS, $document);
|
||||
|
||||
return $this->render(
|
||||
'ChillDocStoreBundle:AccompanyingCourseDocument:show.html.twig',
|
||||
['document' => $document, 'accompanyingCourse' => $course]);
|
||||
@@ -147,8 +142,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
*/
|
||||
public function edit(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course);
|
||||
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', $document);
|
||||
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::UPDATE, $document);
|
||||
|
||||
$document->setUser($this->getUser());
|
||||
$document->setDate(new \DateTime('Now'));
|
||||
@@ -159,17 +153,17 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->translator->trans("The document is successfully updated"));
|
||||
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'accompanying_course_document_edit',
|
||||
['id' => $document->getId(), 'course' => $course->getId()]);
|
||||
|
||||
|
||||
} elseif ($form->isSubmitted() and !$form->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans("This form contains errors"));
|
||||
}
|
||||
|
||||
|
||||
return $this->render(
|
||||
'ChillDocStoreBundle:AccompanyingCourseDocument:edit.html.twig',
|
||||
[
|
||||
@@ -184,8 +178,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
*/
|
||||
public function delete(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', $course);
|
||||
$this->denyAccessUnlessGranted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', $document);
|
||||
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::DELETE, $document);
|
||||
|
||||
if ($this->isCsrfTokenValid('delete'.$document->getId(), $request->request->get('_token'))) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Chill\DocStoreBundle\DependencyInjection;
|
||||
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
@@ -39,7 +40,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
||||
$this->prependAuthorization($container);
|
||||
$this->prependTwig($container);
|
||||
}
|
||||
|
||||
|
||||
protected function prependRoute(ContainerBuilder $container)
|
||||
{
|
||||
//declare routes for task bundle
|
||||
@@ -52,7 +53,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
protected function prependAuthorization(ContainerBuilder $container)
|
||||
{
|
||||
$container->prependExtensionConfig('security', array(
|
||||
@@ -61,10 +62,14 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
||||
PersonDocumentVoter::CREATE => [PersonDocumentVoter::SEE_DETAILS],
|
||||
PersonDocumentVoter::DELETE => [PersonDocumentVoter::SEE_DETAILS],
|
||||
PersonDocumentVoter::SEE_DETAILS => [PersonDocumentVoter::SEE],
|
||||
AccompanyingCourseDocumentVoter::UPDATE => [AccompanyingCourseDocumentVoter::SEE_DETAILS],
|
||||
AccompanyingCourseDocumentVoter::CREATE => [AccompanyingCourseDocumentVoter::SEE_DETAILS],
|
||||
AccompanyingCourseDocumentVoter::DELETE => [AccompanyingCourseDocumentVoter::SEE_DETAILS],
|
||||
AccompanyingCourseDocumentVoter::SEE_DETAILS => [AccompanyingCourseDocumentVoter::SEE],
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
protected function prependTwig(ContainerBuilder $container)
|
||||
{
|
||||
$twigConfig = array(
|
||||
|
@@ -34,10 +34,10 @@ class DocumentCategory
|
||||
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
|
||||
public function __construct($bundleId, $idInsideBundle)
|
||||
{
|
||||
$this->bundleId = $bundleId;
|
||||
|
@@ -39,14 +39,14 @@ class StoredObject implements AsyncFileInterface, Document
|
||||
private $filename;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array", name="key")
|
||||
* @ORM\Column(type="json", name="key")
|
||||
*/
|
||||
private array $keyInfos = [];
|
||||
|
||||
/**
|
||||
*
|
||||
* @var int[]
|
||||
* @ORM\Column(type="json_array", name="iv")
|
||||
* @ORM\Column(type="json", name="iv")
|
||||
*/
|
||||
private array $iv = [];
|
||||
|
||||
@@ -69,7 +69,7 @@ class StoredObject implements AsyncFileInterface, Document
|
||||
private string $type = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json_array", name="datas")
|
||||
* @ORM\Column(type="json", name="datas")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private array $datas = [];
|
||||
|
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\DocStoreBundle\Repository;
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Entity\AccompanyingCourseDocument;
|
||||
namespace Chill\DocStoreBundle\EntityRepository;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
|
@@ -3,50 +3,27 @@
|
||||
*/
|
||||
namespace Chill\DocStoreBundle\Menu;
|
||||
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
||||
*/
|
||||
class MenuBuilder implements LocalMenuBuilderInterface
|
||||
final class MenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @var TokenStorageInterface
|
||||
*/
|
||||
protected $tokenStorage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
|
||||
/**
|
||||
*
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
private Security $security;
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
public function __construct(
|
||||
TokenStorageInterface $tokenStorage,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
Security $security,
|
||||
TranslatorInterface $translator
|
||||
){
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
) {
|
||||
$this->security = $security;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
switch($menuId) {
|
||||
@@ -65,11 +42,8 @@ class MenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
/* @var $person \Chill\PersonBundle\Entity\Person */
|
||||
$person = $parameters['person'];
|
||||
$user = $this->tokenStorage->getToken()->getUser();
|
||||
|
||||
if ($this->authorizationHelper->userHasAccess($user,
|
||||
$person->getCenter(), PersonDocumentVoter::SEE)) {
|
||||
|
||||
|
||||
if ($this->security->isGranted(PersonDocumentVoter::SEE, $person)) {
|
||||
$menu->addChild($this->translator->trans('Documents'), [
|
||||
'route' => 'person_document_index',
|
||||
'routeParameters' => [
|
||||
@@ -80,24 +54,22 @@ class MenuBuilder implements LocalMenuBuilderInterface
|
||||
'order'=> 350
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function buildMenuAccompanyingCourse(MenuItem $menu, array $parameters){
|
||||
$course = $parameters['accompanyingCourse'];
|
||||
// $user = $this->tokenStorage->getToken()->getUser();
|
||||
|
||||
//TODO : add condition to check user rights?
|
||||
|
||||
$menu->addChild($this->translator->trans('Documents'), [
|
||||
if ($this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $course)) {
|
||||
$menu->addChild($this->translator->trans('Documents'), [
|
||||
'route' => 'accompanying_course_document_index',
|
||||
'routeParameters' => [
|
||||
'course' => $course->getId()
|
||||
]
|
||||
])
|
||||
->setExtras([
|
||||
'order'=> 400
|
||||
]);
|
||||
->setExtras([
|
||||
'order' => 400
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
|
@@ -3,13 +3,20 @@
|
||||
namespace Chill\DocStoreBundle\Security\Authorization;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -22,30 +29,22 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
|
||||
const UPDATE = 'CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE';
|
||||
const DELETE = 'CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE';
|
||||
|
||||
/**
|
||||
* @var AuthorizationHelper
|
||||
*/
|
||||
protected $authorizationHelper;
|
||||
|
||||
/**
|
||||
* @var AccessDecisionManagerInterface
|
||||
*/
|
||||
protected $accessDecisionManager;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
protected LoggerInterface $logger;
|
||||
protected VoterHelperInterface $voterHelper;
|
||||
protected Security $security;
|
||||
|
||||
public function __construct(
|
||||
AccessDecisionManagerInterface $accessDecisionManager,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
LoggerInterface $logger
|
||||
)
|
||||
{
|
||||
$this->accessDecisionManager = $accessDecisionManager;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
LoggerInterface $logger,
|
||||
Security $security,
|
||||
VoterHelperFactoryInterface $voterHelperFactory
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->security = $security;
|
||||
$this->voterHelper = $voterHelperFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(AccompanyingCourseDocument::class, $this->getRoles())
|
||||
->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE])
|
||||
->build();
|
||||
}
|
||||
|
||||
public function getRoles()
|
||||
@@ -61,26 +60,30 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
|
||||
|
||||
protected function supports($attribute, $subject)
|
||||
{
|
||||
|
||||
if (\in_array($attribute, $this->getRoles())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->voterHelper->supports($attribute, $subject);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
$this->logger->debug(sprintf("Voting from %s class", self::class));
|
||||
|
||||
if (!$token->getUser() instanceof User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($subject instanceof AccompanyingCourseDocument
|
||||
&& !$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getCourse())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
public function getRolesWithHierarchy()
|
||||
{
|
||||
return ['accompanyingCourseDocument' => $this->getRoles() ];
|
||||
|
@@ -19,8 +19,11 @@
|
||||
|
||||
namespace Chill\DocStoreBundle\Security\Authorization;
|
||||
|
||||
use App\Security\Authorization\VoterHelperFactory;
|
||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
@@ -31,6 +34,7 @@ use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,25 +47,22 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
|
||||
const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE';
|
||||
const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE';
|
||||
|
||||
protected AuthorizationHelper $authorizationHelper;
|
||||
|
||||
protected AccessDecisionManagerInterface $accessDecisionManager;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
protected CenterResolverDispatcher $centerResolverDispatcher;
|
||||
protected Security $security;
|
||||
protected VoterHelperInterface $voterHelper;
|
||||
|
||||
public function __construct(
|
||||
AccessDecisionManagerInterface $accessDecisionManager,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
LoggerInterface $logger//,
|
||||
//CenterResolverDispatcher $centerResolverDispatcher
|
||||
)
|
||||
{
|
||||
$this->accessDecisionManager = $accessDecisionManager;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
LoggerInterface $logger,
|
||||
Security $security,
|
||||
VoterHelperFactoryInterface $voterHelperFactory
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
//$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||
$this->security = $security;
|
||||
$this->voterHelper = $voterHelperFactory
|
||||
->generate(self::class)
|
||||
->addCheckFor(PersonDocument::class, $this->getRoles())
|
||||
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
||||
->build();
|
||||
}
|
||||
|
||||
public function getRoles()
|
||||
@@ -77,16 +78,7 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
|
||||
|
||||
protected function supports($attribute, $subject)
|
||||
{
|
||||
if (\in_array($attribute, $this->getRoles()) && $subject instanceof PersonDocument) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($subject instanceof Person
|
||||
&& \in_array($attribute, [self::CREATE, self::SEE])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return $this->voterHelper->supports($attribute, $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,42 +96,12 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
|
||||
return false;
|
||||
}
|
||||
|
||||
$center = $this->centerResolverDispatcher->resolveCenter($subject);
|
||||
|
||||
if ($subject instanceof PersonDocument) {
|
||||
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);
|
||||
|
||||
} elseif ($subject instanceof Person) {
|
||||
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);
|
||||
|
||||
} else {
|
||||
|
||||
// subject is null. We check that at least one center is reachable
|
||||
$centers = $this->authorizationHelper
|
||||
->getReachableCenters($token->getUser(), new Role($attribute));
|
||||
|
||||
return count($centers) > 0;
|
||||
}
|
||||
|
||||
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
|
||||
if ($subject instanceof PersonDocument
|
||||
&& !$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->authorizationHelper->userHasAccess(
|
||||
$token->getUser(),
|
||||
$subject,
|
||||
$attribute
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
protected function isGranted($attribute, $report, $user = null)
|
||||
{
|
||||
if (! $user instanceof User){
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->helper->userHasAccess($user, $report, $attribute);
|
||||
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
|
||||
}
|
||||
|
||||
public function getRolesWithoutScope()
|
||||
|
@@ -1,8 +1,4 @@
|
||||
services:
|
||||
Chill\DocStoreBundle\Menu\MenuBuilder:
|
||||
arguments:
|
||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
||||
$translator: '@Symfony\Component\Translation\TranslatorInterface'
|
||||
tags:
|
||||
- { name: 'chill.menu_builder' }
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@@ -17,7 +17,6 @@ final class Version20180605102533 extends AbstractMigration
|
||||
$this->addSql('CREATE SCHEMA chill_doc');
|
||||
$this->addSql('CREATE SEQUENCE chill_doc.person_document_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_doc.document_category (bundle_id VARCHAR(255) NOT NULL, id_inside_bundle INT NOT NULL, document_class VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(bundle_id, id_inside_bundle))');
|
||||
$this->addSql('COMMENT ON COLUMN chill_doc.document_category.name IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('CREATE TABLE chill_doc.person_document (id INT NOT NULL, category_bundle_id VARCHAR(255) DEFAULT NULL, category_id_inside_bundle INT DEFAULT NULL, scope_id INT DEFAULT NULL, user_id INT DEFAULT NULL, person_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, content TEXT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_41DA53C369A0BE36EF62EFC ON chill_doc.person_document (category_bundle_id, category_id_inside_bundle)');
|
||||
$this->addSql('CREATE INDEX IDX_41DA53C682B5931 ON chill_doc.person_document (scope_id)');
|
||||
@@ -34,6 +33,6 @@ final class Version20180605102533 extends AbstractMigration
|
||||
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
|
||||
|
||||
$this->addSql('DROP SCHEMA chill_doc CASCADE');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -16,9 +16,6 @@ final class Version20180606133338 extends AbstractMigration
|
||||
|
||||
$this->addSql('CREATE SEQUENCE chill_doc.stored_object_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||
$this->addSql('CREATE TABLE chill_doc.stored_object (id INT NOT NULL, filename TEXT NOT NULL, key JSON NOT NULL, iv JSON NOT NULL, creation_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, type TEXT NOT NULL, datas JSON NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.key IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.iv IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.datas IS \'(DC2Type:json_array)\'');
|
||||
$this->addSql('ALTER TABLE chill_doc.person_document ADD object_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE chill_doc.person_document DROP content');
|
||||
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C232D562B FOREIGN KEY (object_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
|
@@ -39,12 +39,12 @@ use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
*/
|
||||
class ParticipationController extends AbstractController
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
|
||||
/**
|
||||
* ParticipationController constructor.
|
||||
*
|
||||
@@ -54,10 +54,10 @@ class ParticipationController extends AbstractController
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show a form to add a participation
|
||||
*
|
||||
*
|
||||
* This function parse the person_id / persons_ids query argument
|
||||
* and decide if it should process a single or multiple participation. Depending
|
||||
* on this, the appropriate layout and form.
|
||||
@@ -67,46 +67,46 @@ class ParticipationController extends AbstractController
|
||||
*/
|
||||
public function newAction(Request $request)
|
||||
{
|
||||
|
||||
|
||||
// test the request is correct
|
||||
try {
|
||||
$this->testRequest($request);
|
||||
} catch (\RuntimeException $ex) {
|
||||
$this->logger->warning($ex->getMessage());
|
||||
|
||||
|
||||
return (new Response())
|
||||
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||
->setContent($ex->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// forward to other action
|
||||
$single = $request->query->has('person_id');
|
||||
$multiple = $request->query->has('persons_ids');
|
||||
|
||||
|
||||
if ($single === true) {
|
||||
return $this->newSingle($request);
|
||||
}
|
||||
|
||||
|
||||
if ($multiple === true) {
|
||||
|
||||
|
||||
return $this->newMultiple($request);
|
||||
}
|
||||
|
||||
|
||||
// at this point, we miss the required fields. Throw an error
|
||||
return (new Response())
|
||||
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||
->setContent("You must provide either 'person_id' or "
|
||||
. "'persons_ids' argument in query");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Test that the query parameters are valid :
|
||||
*
|
||||
*
|
||||
* - an `event_id` is existing ;
|
||||
* - `person_id` and `persons_ids` are **not** both present ;
|
||||
* - `persons_id` is correct (contains only numbers and a ','.
|
||||
*
|
||||
*
|
||||
* @param Request $request
|
||||
* @throws \RuntimeException if an error is detected
|
||||
*/
|
||||
@@ -114,64 +114,64 @@ class ParticipationController extends AbstractController
|
||||
{
|
||||
$single = $request->query->has('person_id');
|
||||
$multiple = $request->query->has('persons_ids');
|
||||
|
||||
|
||||
if ($single === true AND $multiple === true) {
|
||||
// we are not allowed to have both person_id and persons_ids
|
||||
throw new \RuntimeException("You are not allow to provide both 'person_id' and "
|
||||
. "'persons_ids' simulaneously");
|
||||
}
|
||||
|
||||
|
||||
if ($multiple === true) {
|
||||
$persons_ids = $request->query->get('persons_ids');
|
||||
|
||||
|
||||
if (!preg_match('/^([0-9]{1,},{0,1}){1,}[0-9]{0,}$/', $persons_ids)) {
|
||||
throw new \RuntimeException("The persons_ids value should "
|
||||
. "contains int separated by ','");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check for event_id - this could be removed later
|
||||
if ($request->query->has('event_id') === FALSE) {
|
||||
throw new \RuntimeException("You must provide an event_id");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show a form with single participation.
|
||||
*
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
protected function newSingle(Request $request)
|
||||
{
|
||||
|
||||
|
||||
$returnPath = $request->query->get('return_path') ?
|
||||
$request->query->get('return_path') : null;
|
||||
|
||||
|
||||
$participation = $this->handleRequest($request, new Participation(), false);
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
$participation, 'The user is not allowed to create this participation');
|
||||
|
||||
|
||||
$form = $this->createCreateForm($participation, $returnPath);
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:new.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'participation' => $participation,
|
||||
'ignored_participations' => array() // this is required, see self::newMultiple
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show a form with multiple participation.
|
||||
*
|
||||
*
|
||||
* If a person is already participating on the event (if a participation with
|
||||
* the same person is associated with the event), the participation is ignored.
|
||||
*
|
||||
*
|
||||
* If all but one participation is ignored, the page show the same response
|
||||
* than the newSingle function.
|
||||
*
|
||||
* than the newSingle function.
|
||||
*
|
||||
* If all participations must be ignored, an error is shown and the method redirects
|
||||
* to the event 'show' view with an appropriate flash message.
|
||||
*
|
||||
@@ -181,24 +181,24 @@ class ParticipationController extends AbstractController
|
||||
protected function newMultiple(Request $request)
|
||||
{
|
||||
$participations = $this->handleRequest($request, new Participation(), true);
|
||||
|
||||
|
||||
$ignoredParticipations = $newParticipations = [];
|
||||
|
||||
foreach ($participations as $i => $participation) {
|
||||
// check for authorization
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
$participation, 'The user is not allowed to create this participation');
|
||||
|
||||
|
||||
// create a collection of person's id participating to the event
|
||||
/* @var $peopleParticipating \Doctrine\Common\Collections\ArrayCollection */
|
||||
$peopleParticipating = isset($peopleParticipating) ? $peopleParticipating :
|
||||
$participation->getEvent()->getParticipations()->map(
|
||||
function(Participation $p) { return $p->getPerson()->getId(); }
|
||||
);
|
||||
// check that the user is not already in the event
|
||||
// check that the user is not already in the event
|
||||
if ($peopleParticipating->contains($participation->getPerson()->getId())) {
|
||||
$ignoredParticipations[] = $participation
|
||||
->getEvent()->getParticipations()->filter(
|
||||
function (Participation $p) use ($participation) {
|
||||
function (Participation $p) use ($participation) {
|
||||
return $p->getPerson()->getId() === $participation->getPerson()->getId();
|
||||
}
|
||||
)->first();
|
||||
@@ -206,15 +206,15 @@ class ParticipationController extends AbstractController
|
||||
$newParticipations[] = $participation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// this is where the function redirect depending on valid participation
|
||||
|
||||
if (!isset($newParticipations)) {
|
||||
|
||||
if ([] === $newParticipations) {
|
||||
// if we do not have nay participants, redirect to event view
|
||||
$this->addFlash('error', $this->get('translator')->trans(
|
||||
'None of the requested people may participate '
|
||||
. 'the event: they are maybe already participating.'));
|
||||
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', array(
|
||||
'event_id' => $request->query->getInt('event_id', 0)
|
||||
));
|
||||
@@ -222,24 +222,29 @@ class ParticipationController extends AbstractController
|
||||
// if we have multiple participations, show a form with multiple participations
|
||||
$form = $this->createCreateFormMultiple($newParticipations);
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:new-multiple.html.twig', array(
|
||||
return $this->render(
|
||||
'ChillEventBundle:Participation:new-multiple.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
'participations' => $newParticipations,
|
||||
'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array()
|
||||
));
|
||||
} else {
|
||||
// if we have only one participation, show the same form than for single participation
|
||||
$form = $this->createCreateForm($participation);
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:new.html.twig', array(
|
||||
'ignored_participations' => $ignoredParticipations
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// if we have only one participation, show the same form than for single participation
|
||||
$form = $this->createCreateForm($participation);
|
||||
|
||||
return $this->render(
|
||||
'ChillEventBundle:Participation:new.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
'participation' => $participation,
|
||||
'ignored_participations' => isset($ignoredParticipations) ? $ignoredParticipations : array()
|
||||
));
|
||||
|
||||
}
|
||||
'ignored_participations' => $ignoredParticipations,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
@@ -251,32 +256,32 @@ class ParticipationController extends AbstractController
|
||||
$this->testRequest($request);
|
||||
} catch (\RuntimeException $ex) {
|
||||
$this->logger->warning($ex->getMessage());
|
||||
|
||||
|
||||
return (new Response())
|
||||
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||
->setContent($ex->getMessage());
|
||||
}
|
||||
|
||||
|
||||
// forward to other action
|
||||
$single = $request->query->has('person_id');
|
||||
$multiple = $request->query->has('persons_ids');
|
||||
|
||||
|
||||
if ($single === true) {
|
||||
return $this->createSingle($request);
|
||||
}
|
||||
|
||||
|
||||
if ($multiple === true) {
|
||||
|
||||
|
||||
return $this->createMultiple($request);
|
||||
}
|
||||
|
||||
|
||||
// at this point, we miss the required fields. Throw an error
|
||||
return (new Response())
|
||||
->setStatusCode(Response::HTTP_BAD_REQUEST)
|
||||
->setContent("You must provide either 'person_id' or "
|
||||
. "'persons_ids' argument in query");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
@@ -284,41 +289,41 @@ class ParticipationController extends AbstractController
|
||||
public function createSingle(Request $request)
|
||||
{
|
||||
$participation = $this->handleRequest($request, new Participation(), false);
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
$participation, 'The user is not allowed to create this participation');
|
||||
|
||||
|
||||
$form = $this->createCreateForm($participation);
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
|
||||
$em->persist($participation);
|
||||
$em->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans(
|
||||
'The participation was created'
|
||||
));
|
||||
|
||||
|
||||
if ($request->query->get('return_path'))
|
||||
{
|
||||
return $this->redirect($request->query->get('return_path'));
|
||||
|
||||
|
||||
} else {
|
||||
return $this->redirectToRoute('chill_event__event_show', array(
|
||||
'event_id' => $participation->getEvent()->getId()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:new.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'participation' => $participation
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
@@ -326,56 +331,56 @@ class ParticipationController extends AbstractController
|
||||
public function createMultiple(Request $request)
|
||||
{
|
||||
$participations = $this->handleRequest($request, new Participation(), true);
|
||||
|
||||
|
||||
foreach($participations as $participation) {
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::CREATE,
|
||||
$participation, 'The user is not allowed to create this participation');
|
||||
}
|
||||
|
||||
|
||||
$form = $this->createCreateFormMultiple($participations);
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
$data = $form->getData();
|
||||
|
||||
|
||||
foreach($data['participations'] as $participation) {
|
||||
$em->persist($participation);
|
||||
}
|
||||
|
||||
|
||||
$em->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans(
|
||||
'The participations were created'
|
||||
));
|
||||
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', array(
|
||||
'event_id' => $participations[0]->getEvent()->getId()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:new.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'participation' => $participation
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Handle the request to adapt $participation.
|
||||
*
|
||||
* If the request is multiple, the $participation object is cloned.
|
||||
*
|
||||
* If the request is multiple, the $participation object is cloned.
|
||||
* Limitations: the $participation should not be persisted.
|
||||
*
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Participation $participation
|
||||
* @param boolean $multiple (default false)
|
||||
* @param boolean $multiple (default false)
|
||||
* @return Participation|Participations[] return one single participation if $multiple == false
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the event/person is not found
|
||||
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException if the user does not have access to event/person
|
||||
*/
|
||||
protected function handleRequest(
|
||||
Request $request,
|
||||
Request $request,
|
||||
Participation $participation,
|
||||
$multiple = false)
|
||||
{
|
||||
@@ -384,37 +389,37 @@ class ParticipationController extends AbstractController
|
||||
throw new \LogicException("The participation object should not be managed by "
|
||||
. "the object manager using the method ".__METHOD__);
|
||||
}
|
||||
|
||||
|
||||
$event_id = $request->query->getInt('event_id', 0); // sf4 check:
|
||||
// prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
|
||||
|
||||
|
||||
if ($event_id !== NULL) {
|
||||
$event = $em->getRepository('ChillEventBundle:Event')
|
||||
->find($event_id);
|
||||
|
||||
|
||||
if ($event === NULL) {
|
||||
throw $this->createNotFoundException('The event with id '.$event_id.' is not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event,
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event,
|
||||
'The user is not allowed to see the event');
|
||||
|
||||
|
||||
$participation->setEvent($event);
|
||||
}
|
||||
|
||||
// this script should be able to handle multiple, so we translate
|
||||
|
||||
// this script should be able to handle multiple, so we translate
|
||||
// single person_id in an array
|
||||
$persons_ids = $request->query->has('person_id') ?
|
||||
[$request->query->getInt('person_id', 0)] // sf4 check:
|
||||
// prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
|
||||
: explode(',', $request->query->get('persons_ids'));
|
||||
$participations = array();
|
||||
|
||||
|
||||
foreach($persons_ids as $person_id) {
|
||||
|
||||
|
||||
// clone if we have to reuse the $participation
|
||||
$participation = count($persons_ids) > 1 ? clone $participation : $participation;
|
||||
|
||||
|
||||
if ($person_id !== NULL) {
|
||||
$person = $em->getRepository('ChillPersonBundle:Person')
|
||||
->find($person_id);
|
||||
@@ -428,13 +433,13 @@ class ParticipationController extends AbstractController
|
||||
|
||||
$participation->setPerson($person);
|
||||
}
|
||||
|
||||
|
||||
$participations[] = $participation;
|
||||
}
|
||||
|
||||
|
||||
return $multiple ? $participations : $participations[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Participation $participation
|
||||
* @param null $return_path
|
||||
@@ -442,7 +447,7 @@ class ParticipationController extends AbstractController
|
||||
*/
|
||||
public function createCreateForm(Participation $participation, $return_path = null)
|
||||
{
|
||||
|
||||
|
||||
$form = $this->createForm(ParticipationType::class, $participation, array(
|
||||
'event_type' => $participation->getEvent()->getType(),
|
||||
'action' => $this->generateUrl('chill_event_participation_create', array(
|
||||
@@ -451,14 +456,14 @@ class ParticipationController extends AbstractController
|
||||
'person_id' => $participation->getPerson()->getId()
|
||||
))
|
||||
));
|
||||
|
||||
|
||||
$form->add('submit', SubmitType::class, array(
|
||||
'label' => 'Create'
|
||||
));
|
||||
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $participations
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
@@ -470,7 +475,7 @@ class ParticipationController extends AbstractController
|
||||
'action' => $this->generateUrl('chill_event_participation_create', array(
|
||||
'event_id' => current($participations)->getEvent()->getId(),
|
||||
'persons_ids' => implode(',', array_map(
|
||||
function(Participation $p) { return $p->getPerson()->getId(); },
|
||||
function(Participation $p) { return $p->getPerson()->getId(); },
|
||||
$participations))
|
||||
)
|
||||
)));
|
||||
@@ -481,90 +486,90 @@ class ParticipationController extends AbstractController
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$form->add('submit', SubmitType::class, array(
|
||||
'label' => 'Create'
|
||||
));
|
||||
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* show an edit form for the participation with the given id.
|
||||
*
|
||||
*
|
||||
* @param int $participation_id
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the participation is not found
|
||||
* @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException if the user is not allowed to edit the participation
|
||||
*/
|
||||
public function editAction($participation_id)
|
||||
public function editAction($participation_id)
|
||||
{
|
||||
/* @var $participation Participation */
|
||||
$participation = $this->getDoctrine()->getManager()
|
||||
->getRepository('ChillEventBundle:Participation')
|
||||
->find($participation_id);
|
||||
|
||||
|
||||
if ($participation === NULL) {
|
||||
throw $this->createNotFoundException('The participation is not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
'You are not allowed to edit this participation');
|
||||
|
||||
|
||||
$form = $this->createEditForm($participation);
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:edit.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'participation' => $participation
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $participation_id
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|Response
|
||||
*/
|
||||
public function updateAction($participation_id, Request $request)
|
||||
public function updateAction($participation_id, Request $request)
|
||||
{
|
||||
/* @var $participation Participation */
|
||||
$participation = $this->getDoctrine()->getManager()
|
||||
->getRepository('ChillEventBundle:Participation')
|
||||
->find($participation_id);
|
||||
|
||||
|
||||
if ($participation === NULL) {
|
||||
throw $this->createNotFoundException('The participation is not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
'You are not allowed to edit this participation');
|
||||
|
||||
|
||||
$form = $this->createEditForm($participation);
|
||||
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
|
||||
$em->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans(
|
||||
'The participation was updated'
|
||||
));
|
||||
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', array(
|
||||
'event_id' => $participation->getEvent()->getId()
|
||||
));
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:edit.html.twig', array(
|
||||
'form' => $form->createView(),
|
||||
'participation' => $participation
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param Participation $participation
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
*/
|
||||
@@ -576,14 +581,14 @@ class ParticipationController extends AbstractController
|
||||
'participation_id' => $participation->getId()
|
||||
))
|
||||
));
|
||||
|
||||
|
||||
$form->add('submit', SubmitType::class, array(
|
||||
'label' => 'Edit'
|
||||
));
|
||||
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* show a form to edit multiple participation for the same event.
|
||||
*
|
||||
@@ -594,84 +599,84 @@ class ParticipationController extends AbstractController
|
||||
{
|
||||
$event = $this->getDoctrine()->getRepository('ChillEventBundle:Event')
|
||||
->find($event_id);
|
||||
|
||||
|
||||
if ($event === null) {
|
||||
throw $this->createNotFoundException("The event with id $event_id is not found");
|
||||
}
|
||||
|
||||
|
||||
// check for ACL, on Event level and on Participation Level
|
||||
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed "
|
||||
. "to see this event");
|
||||
foreach ($event->getParticipations() as $participation) {
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
"You are not allowed to update participation with id ".$participation->getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
switch ($event->getParticipations()->count()) {
|
||||
|
||||
|
||||
case 0:
|
||||
// if there aren't any participation, redirect to the 'show' view with an add flash
|
||||
$this->addFlash('warning', $this->get('translator')
|
||||
->trans( "There are no participation to edit for this event"));
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show',
|
||||
return $this->redirectToRoute('chill_event__event_show',
|
||||
array('event_id' => $event->getId()));
|
||||
|
||||
|
||||
case 1:
|
||||
// redirect to the form for a single participation
|
||||
return $this->redirectToRoute('chill_event_participation_edit', array(
|
||||
'participation_id' => $event->getParticipations()->current()->getId()
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$form = $this->createEditFormMultiple($event->getParticipations(), $event);
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array(
|
||||
'event' => $event,
|
||||
'participations' => $event->getParticipations(),
|
||||
'form' => $form->createView()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
public function updateMultipleAction($event_id, Request $request)
|
||||
{
|
||||
/* @var $event \Chill\EventBundle\Entity\Event */
|
||||
$event = $this->getDoctrine()->getRepository('ChillEventBundle:Event')
|
||||
->find($event_id);
|
||||
|
||||
|
||||
if ($event === null) {
|
||||
throw $this->createNotFoundException("The event with id $event_id is not found");
|
||||
}
|
||||
|
||||
|
||||
$this->denyAccessUnlessGranted('CHILL_EVENT_SEE', $event, "You are not allowed "
|
||||
. "to see this event");
|
||||
foreach ($event->getParticipations() as $participation) {
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
$this->denyAccessUnlessGranted(ParticipationVoter::UPDATE, $participation,
|
||||
"You are not allowed to update participation with id ".$participation->getId());
|
||||
}
|
||||
|
||||
|
||||
$form = $this->createEditFormMultiple($event->getParticipations(), $event);
|
||||
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans("The participations "
|
||||
. "have been successfully updated."));
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show',
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show',
|
||||
array('event_id' => $event->getId()));
|
||||
}
|
||||
|
||||
|
||||
return $this->render('ChillEventBundle:Participation:edit-multiple.html.twig', array(
|
||||
'event' => $event,
|
||||
'participations' => $event->getParticipations(),
|
||||
'form' => $form->createView()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ArrayIterator $participations
|
||||
* @param Event $event
|
||||
@@ -679,14 +684,14 @@ class ParticipationController extends AbstractController
|
||||
*/
|
||||
protected function createEditFormMultiple(ArrayIterator $participations, Event $event)
|
||||
{
|
||||
$form = $this->createForm(\Symfony\Component\Form\Extension\Core\Type\FormType::class,
|
||||
$form = $this->createForm(\Symfony\Component\Form\Extension\Core\Type\FormType::class,
|
||||
array('participations' => $participations), array(
|
||||
'method' => 'POST',
|
||||
'action' => $this->generateUrl('chill_event_participation_update_multiple', array(
|
||||
'event_id' => $event->getId()
|
||||
))
|
||||
));
|
||||
|
||||
|
||||
$form->add('participations', CollectionType::class, array(
|
||||
'entry_type' => ParticipationType::class,
|
||||
'entry_options' => array(
|
||||
@@ -694,14 +699,14 @@ class ParticipationController extends AbstractController
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$form->add('submit', SubmitType::class, array(
|
||||
'label' => 'Update'
|
||||
));
|
||||
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param integer $participation_id
|
||||
* @param Request $request
|
||||
@@ -713,28 +718,28 @@ class ParticipationController extends AbstractController
|
||||
$participation = $em->getRepository('ChillEventBundle:Participation')->findOneBy([
|
||||
'id' => $participation_id
|
||||
]);
|
||||
|
||||
|
||||
if (! $participation) {
|
||||
throw $this->createNotFoundException('Unable to find participation.');
|
||||
}
|
||||
|
||||
|
||||
/** @var Event $event */
|
||||
$event = $participation->getEvent();
|
||||
|
||||
|
||||
$form = $this->createDeleteForm($participation_id);
|
||||
|
||||
|
||||
if ($request->getMethod() === Request::METHOD_DELETE) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
||||
if ($form->isValid()) {
|
||||
|
||||
|
||||
$em->remove($participation);
|
||||
$em->flush();
|
||||
|
||||
|
||||
$this->addFlash('success', $this->get('translator')
|
||||
->trans("The participation has been sucessfully removed")
|
||||
);
|
||||
|
||||
|
||||
return $this->redirectToRoute('chill_event__event_show', [
|
||||
'event_id' => $event->getId()
|
||||
]);
|
||||
@@ -744,9 +749,9 @@ class ParticipationController extends AbstractController
|
||||
'event_id' => $event->getId(),
|
||||
'delete_form' => $form->createView()
|
||||
]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $participation_id
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
@@ -762,5 +767,5 @@ class ParticipationController extends AbstractController
|
||||
->getForm()
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -46,7 +46,7 @@ class EventType
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
@@ -55,7 +55,7 @@ class EventType
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active;
|
||||
|
||||
|
||||
/**
|
||||
* @var Collection
|
||||
* @ORM\OneToMany(
|
||||
|
@@ -44,7 +44,7 @@ class Role
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
@@ -53,7 +53,7 @@ class Role
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active;
|
||||
|
||||
|
||||
/**
|
||||
* @var EventType
|
||||
* @ORM\ManyToOne(
|
||||
|
@@ -41,19 +41,19 @@ class Status
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @ORM\Column(type="json_array")
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private $name;
|
||||
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
private $active;
|
||||
|
||||
|
||||
/**
|
||||
* @var EventType
|
||||
* @ORM\ManyToOne(
|
||||
|
@@ -504,6 +504,8 @@ class ApiController extends AbstractCRUDController
|
||||
$this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData])
|
||||
);
|
||||
}
|
||||
|
||||
throw new \Exception('Unable to handle such request method.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -20,6 +20,8 @@
|
||||
|
||||
namespace Chill\MainBundle\CRUD\Controller;
|
||||
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -209,22 +211,24 @@ class CRUDController extends AbstractController
|
||||
* This method:
|
||||
*
|
||||
* 1. Launch `onPreIndex`
|
||||
* x. check acl. If it does return a response instance, return it
|
||||
* x. launch `onPostCheckACL`. If it does return a response instance, return it
|
||||
* 1. count the items, using `countEntities`
|
||||
* 2. build a paginator element from the the number of entities ;
|
||||
* 3. Launch `onPreIndexQuery`. If it does return a response instance, return it
|
||||
* 3. build a query, using `queryEntities`
|
||||
* x. fetch the results, using `getQueryResult`
|
||||
* x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
|
||||
* 4. create default parameters:
|
||||
* 2. check acl. If it does return a response instance, return it
|
||||
* 3. launch `onPostCheckACL`. If it does return a response instance, return it
|
||||
* 4. count the items, using `countEntities`
|
||||
* 5. build a paginator element from the the number of entities ;
|
||||
* 6. Launch `onPreIndexQuery`. If it does return a response instance, return it
|
||||
* 7. fetch the results, using `getQueryResult`
|
||||
*
|
||||
* Internally, this build a query, using `queryEntities`
|
||||
*
|
||||
* 8. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it
|
||||
* 9. create default parameters:
|
||||
*
|
||||
* The default parameters are:
|
||||
*
|
||||
* * entities: the list en entities ;
|
||||
* * crud_name: the name of the crud ;
|
||||
* * paginator: a paginator element ;
|
||||
* 5. Launch rendering, the parameter is fetch using `getTemplateFor`
|
||||
* 10. Launch rendering, the parameter is fetch using `getTemplateFor`
|
||||
* The parameters may be personnalized using `generateTemplateParameter`.
|
||||
*
|
||||
* @param string $action
|
||||
@@ -249,7 +253,8 @@ class CRUDController extends AbstractController
|
||||
return $response;
|
||||
}
|
||||
|
||||
$totalItems = $this->countEntities($action, $request);
|
||||
$filterOrder = $this->buildFilterOrderHelper($action, $request);
|
||||
$totalItems = $this->countEntities($action, $request, $filterOrder);
|
||||
$paginator = $this->getPaginatorFactory()->create($totalItems);
|
||||
|
||||
$response = $this->onPreIndexBuildQuery($action, $request, $totalItems,
|
||||
@@ -259,16 +264,7 @@ class CRUDController extends AbstractController
|
||||
return $response;
|
||||
}
|
||||
|
||||
$query = $this->queryEntities($action, $request, $paginator);
|
||||
|
||||
$response = $this->onPostIndexBuildQuery($action, $request, $totalItems,
|
||||
$paginator, $query);
|
||||
|
||||
if ($response instanceof Response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $query);
|
||||
$entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $filterOrder);
|
||||
|
||||
$response = $this->onPostIndexFetchQuery($action, $request, $totalItems,
|
||||
$paginator, $entities);
|
||||
@@ -280,7 +276,8 @@ class CRUDController extends AbstractController
|
||||
$defaultTemplateParameters = [
|
||||
'entities' => $entities,
|
||||
'crud_name' => $this->getCrudName(),
|
||||
'paginator' => $paginator
|
||||
'paginator' => $paginator,
|
||||
'filter_order' => $filterOrder
|
||||
];
|
||||
|
||||
return $this->render(
|
||||
@@ -289,6 +286,11 @@ class CRUDController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* @param Request $request
|
||||
@@ -340,13 +342,19 @@ class CRUDController extends AbstractController
|
||||
*/
|
||||
protected function buildQueryEntities(string $action, Request $request)
|
||||
{
|
||||
return $this->getDoctrine()->getManager()
|
||||
$query = $this->getDoctrine()->getManager()
|
||||
->createQueryBuilder()
|
||||
->select('e')
|
||||
->from($this->getEntityClass(), 'e')
|
||||
;
|
||||
|
||||
$this->customizeQuery($action, $request, $query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
protected function customizeQuery(string $action, Request $request, $query): void {}
|
||||
|
||||
/**
|
||||
* Query the entity.
|
||||
*
|
||||
@@ -361,9 +369,9 @@ class CRUDController extends AbstractController
|
||||
* @param PaginatorInterface $paginator
|
||||
* @return type
|
||||
*/
|
||||
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator)
|
||||
protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator, ?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$query = $this->buildQueryEntities($action, $request)
|
||||
$query = $this->buildQueryEntities($action, $request, $filterOrder)
|
||||
->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
|
||||
->setMaxResults($paginator->getItemsPerPage());
|
||||
|
||||
@@ -393,11 +401,13 @@ class CRUDController extends AbstractController
|
||||
* @param Request $request
|
||||
* @param int $totalItems
|
||||
* @param PaginatorInterface $paginator
|
||||
* @param mixed $query
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query)
|
||||
protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator,
|
||||
?FilterOrderHelper $filterOrder = null)
|
||||
{
|
||||
$query = $this->queryEntities($action, $request, $paginator, $filterOrder);
|
||||
|
||||
return $query->getQuery()->getResult();
|
||||
}
|
||||
|
||||
@@ -408,9 +418,9 @@ class CRUDController extends AbstractController
|
||||
* @param Request $request
|
||||
* @return int
|
||||
*/
|
||||
protected function countEntities(string $action, Request $request): int
|
||||
protected function countEntities(string $action, Request $request, ?FilterOrderHelper $filterOrder = null): int
|
||||
{
|
||||
return $this->buildQueryEntities($action, $request)
|
||||
return $this->buildQueryEntities($action, $request, $filterOrder)
|
||||
->select('COUNT(e)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult()
|
||||
@@ -1183,6 +1193,11 @@ class CRUDController extends AbstractController
|
||||
return $this->get(Resolver::class);
|
||||
}
|
||||
|
||||
protected function getFilterOrderHelperFactory(): FilterOrderHelperFactoryInterface
|
||||
{
|
||||
return $this->get(FilterOrderHelperFactoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -1197,6 +1212,7 @@ class CRUDController extends AbstractController
|
||||
EventDispatcherInterface::class => EventDispatcherInterface::class,
|
||||
Resolver::class => Resolver::class,
|
||||
SerializerInterface::class => SerializerInterface::class,
|
||||
FilterOrderHelperFactoryInterface::class => FilterOrderHelperFactoryInterface::class,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
|
||||
use Chill\MainBundle\Security\ProvideRoleInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
||||
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
|
||||
@@ -38,6 +39,8 @@ class ChillMainBundle extends Bundle
|
||||
->addTag('chill_main.center_resolver');
|
||||
$container->registerForAutoconfiguration(ScopeResolverInterface::class)
|
||||
->addTag('chill_main.scope_resolver');
|
||||
$container->registerForAutoconfiguration(ChillEntityRenderInterface::class)
|
||||
->addTag('chill.render_entity');
|
||||
|
||||
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
||||
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
||||
|
@@ -10,18 +10,18 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
/**
|
||||
*
|
||||
* @author Julien Fastré <julien.fastre@champs-libres.coop
|
||||
*
|
||||
*
|
||||
*/
|
||||
class LoadCountriesCommand extends Command
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $entityManager;
|
||||
|
||||
|
||||
private $availableLanguages;
|
||||
|
||||
|
||||
/**
|
||||
* LoadCountriesCommand constructor.
|
||||
*
|
||||
@@ -34,7 +34,7 @@ class LoadCountriesCommand extends Command
|
||||
$this->availableLanguages=$availableLanguages;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-PHPdoc)
|
||||
* @see \Symfony\Component\Console\Command\Command::configure()
|
||||
@@ -45,7 +45,7 @@ class LoadCountriesCommand extends Command
|
||||
->setDescription('Load or update countries in db. This command does not delete existing countries, '.
|
||||
'but will update names according to available languages');
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-PHPdoc)
|
||||
* @see \Symfony\Component\Console\Command\Command::execute()
|
||||
@@ -54,43 +54,44 @@ class LoadCountriesCommand extends Command
|
||||
{
|
||||
$countries = static::prepareCountryList($this->availableLanguages);
|
||||
$em = $this->entityManager;
|
||||
|
||||
|
||||
foreach($countries as $country) {
|
||||
$countryStored = $em->getRepository('ChillMainBundle:Country')
|
||||
->findOneBy(array('countryCode' => $country->getCountryCode()));
|
||||
|
||||
|
||||
if (NULL === $countryStored) {
|
||||
$em->persist($country);
|
||||
} else {
|
||||
$countryStored->setName($country->getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
public static function prepareCountryList($languages)
|
||||
{
|
||||
$regionBundle = Intl::getRegionBundle();
|
||||
|
||||
$countries = [];
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$countries[$language] = $regionBundle->getCountryNames($language);
|
||||
}
|
||||
|
||||
|
||||
$countryEntities = array();
|
||||
|
||||
|
||||
foreach ($countries[$languages[0]] as $countryCode => $name) {
|
||||
$names = array();
|
||||
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$names[$language] = $countries[$language][$countryCode];
|
||||
}
|
||||
|
||||
|
||||
$country = new \Chill\MainBundle\Entity\Country();
|
||||
$country->setName($names)->setCountryCode($countryCode);
|
||||
$countryEntities[] = $country;
|
||||
}
|
||||
|
||||
|
||||
return $countryEntities;
|
||||
}
|
||||
}
|
||||
|
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace Chill\MainBundle\Command;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Chill\MainBundle\Entity\Country;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@@ -69,6 +71,9 @@ class LoadPostalCodesCommand extends Command
|
||||
. "using the postal code and name. \n"
|
||||
. "The CSV file must have the following columns: "
|
||||
. "postal code, label, country code."
|
||||
. "Optionally, the csv file can have the following "
|
||||
. "columns after the country code: reference code, latitude, longitude, source. "
|
||||
. "The latitude and longitude columns are supposed to be in WGS84 and expressed in decimal degrees. "
|
||||
. "The CSV file should not have any header row.")
|
||||
->addArgument('csv_file', InputArgument::REQUIRED, "the path to "
|
||||
. "the csv file. See the help for specifications.")
|
||||
@@ -163,7 +168,7 @@ class LoadPostalCodesCommand extends Command
|
||||
}
|
||||
$em = $this->entityManager;
|
||||
$country = $em
|
||||
->getRepository('ChillMainBundle:Country')
|
||||
->getRepository(Country::class)
|
||||
->findOneBy(array('countryCode' => $row[2]));
|
||||
|
||||
if ($country === NULL) {
|
||||
@@ -173,7 +178,7 @@ class LoadPostalCodesCommand extends Command
|
||||
|
||||
// try to find an existing postal code
|
||||
$existingPC = $em
|
||||
->getRepository('ChillMainBundle:PostalCode')
|
||||
->getRepository(PostalCode::class)
|
||||
->findBy(array('code' => $row[0], 'name' => $row[1]));
|
||||
|
||||
if (count($existingPC) > 0) {
|
||||
@@ -187,6 +192,18 @@ class LoadPostalCodesCommand extends Command
|
||||
->setCountry($country)
|
||||
;
|
||||
|
||||
if (NULL != $row[3]){
|
||||
$postalCode->setRefPostalCodeId($row[3]);
|
||||
}
|
||||
|
||||
if (NULL != $row[4] & NULL != $row[5]){
|
||||
$postalCode->setCenter(Point::fromLonLat((float) $row[5], (float) $row[4]));
|
||||
}
|
||||
|
||||
if (NULL != $row[6]){
|
||||
$postalCode->setPostalCodeSource($row[6]);
|
||||
}
|
||||
|
||||
$errors = $this->validator->validate($postalCode);
|
||||
|
||||
if ($errors->count() == 0) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user