Compare commits

...

324 Commits

Author SHA1 Message Date
3a98a316fe improve workflow decision form 2022-02-01 10:02:33 +01:00
829bf01eca fix workflow redirection in accompanying period work edit 2022-01-31 21:09:00 +01:00
170ce2d51c minor fix and renaming 2022-01-31 16:55:19 +01:00
971789d5cf Merge remote-tracking branch 'origin/master' into foldlist_in_entity 2022-01-31 15:51:21 +01:00
72815e4b70 Merge branch 'homepage/rewrite' 2022-01-31 15:40:40 +01:00
44183957b8 Merge remote-tracking branch 'origin/master' into homepage/rewrite 2022-01-31 15:36:53 +01:00
5f6cffa08a fix regression in serach results appareance in addpersons 2022-01-31 14:49:23 +01:00
222dae3c32 Merge remote-tracking branch 'origin/master' into issue420_closeModalAddPerson 2022-01-31 14:24:45 +01:00
08c13b8c98 Merge remote-tracking branch 'origin/master' into issue412_413_activity 2022-01-31 14:22:34 +01:00
1bc2500b28 update changelog [ci-skip] 2022-01-31 14:18:10 +01:00
f648a9351b workflow: fix some stuffs 2022-01-31 14:17:10 +01:00
ffe466a334 fix icon for searching 2022-01-31 14:16:33 +01:00
nobohan
3978e7c959 upd CHANGELOG 2022-01-31 13:48:21 +01:00
nobohan
2eb5c45a4d accompanying course: fetch scopes only for app (not for banner) 2022-01-31 13:46:43 +01:00
nobohan
fefe208260 accompanying course: fetch users and suggested referrers only for app, not for banner 2022-01-31 13:32:34 +01:00
c14101714c update changelog 2022-01-31 13:09:13 +01:00
8ec5636c57 Merge remote-tracking branch 'origin/master' into household-editor/push-to-household 2022-01-31 13:07:26 +01:00
fba7060a91 update changelog 2022-01-31 13:05:41 +01:00
6894fa7101 Merge branch 'issue389_add_age' into 'master'
add age/ person text component

See merge request Chill-Projet/chill-bundles!308
2022-01-31 12:04:48 +00:00
41fa1d9ca6 fix cs 2022-01-31 13:02:43 +01:00
f4bca2f410 add person text in accompanying period work 2022-01-31 13:00:48 +01:00
6198891202 add anchor to retrieve evaluations section in accompanyingCourseWorkEdit page 2022-01-31 12:47:31 +01:00
c806c06279 homepage_widget: basically build tables for evaluations, tasks and courses 2022-01-31 12:46:44 +01:00
e04c02055c add age in some place and re-organize some usage of the component 2022-01-31 12:45:50 +01:00
ac5675933d Merge branch 'issue411_openingDate_parcours' into 'master'
Opening date of parcour

See merge request Chill-Projet/chill-bundles!313
2022-01-31 11:07:07 +00:00
e7db71b0f3 fast actions are custom overrided buttons 2022-01-31 12:03:42 +01:00
nobohan
1967fc4bed upd CHANGELOG 2022-01-31 12:01:52 +01:00
nobohan
befd5dac42 person: accompanying course: treat validation error when editing on-the-fly entities 2022-01-31 12:00:40 +01:00
186b8847d9 Merge remote-tracking branch 'origin/issue389_add_age' into issue389_add_age 2022-01-31 11:46:07 +01:00
90d0cbc3b1 Merge remote-tracking branch 'origin/master' into issue389_add_age 2022-01-31 11:44:55 +01:00
ac3d39b151 Merge branch 'master' into 'issue389_add_age'
# Conflicts:
#   CHANGELOG.md
2022-01-31 10:25:32 +00:00
efb5bd64b4 notification-list: todo add a comment counter 2022-01-31 11:25:23 +01:00
76a7b019eb Merge branch 'master' into 'issue411_openingDate_parcours'
# Conflicts:
#   CHANGELOG.md
2022-01-31 10:19:54 +00:00
230d73498e workflow list: mask title 2022-01-31 11:02:01 +01:00
nobohan
a68a43adc0 upd CHANGELOG 2022-01-31 10:55:11 +01:00
nobohan
5a75e38f33 accompanying course: close modal when edit participation 2022-01-31 10:48:04 +01:00
1c02eb23fd fix user search 2022-01-31 10:41:08 +01:00
1ba5df1280 hop 2022-01-31 09:45:37 +01:00
73c4a5d39d better color coherency with buttons 2022-01-30 19:45:09 +01:00
0a58c43952 fix date format on popover 2022-01-30 19:43:17 +01:00
a3b823d33f improve workflow button 2022-01-30 16:43:09 +01:00
de45555c5a add popover html content 2022-01-30 16:35:47 +01:00
0add020e57 workflows in entity: other vue component call workflow modal 2022-01-30 13:53:51 +01:00
84a76d2c76 ListWorkflow: styles for list (like in twig) 2022-01-30 13:36:37 +01:00
d88207132b workflows in entity: twig call once vue component that list in modal with add button, or just display add button if none 2022-01-30 11:26:04 +01:00
932d0e86d9 Merge branch 'foldlist_in_entity' of gitlab.com:Chill-Projet/chill-bundles into foldlist_in_entity 2022-01-29 12:37:48 +01:00
671f1223b5 Merge branch 'issue_mes_parcours' into 'master'
Mes parcours

See merge request Chill-Projet/chill-bundles!307
2022-01-29 02:09:04 +00:00
1a04d903fc fixes for page 'mes parcours' 2022-01-29 03:08:34 +01:00
125dd4d980 Merge remote-tracking branch 'origin/master' into issue_mes_parcours 2022-01-29 02:58:32 +01:00
2ddab027ed Merge remote-tracking branch 'origin/master' into issue410_restyle_parcourslist_personSearch 2022-01-29 02:57:05 +01:00
1724400d7c button requestor on the right 2022-01-29 02:56:06 +01:00
d1a0934bb1 search household in household mmebers editor 2022-01-29 02:38:47 +01:00
bc90664480 api endpoint for search household 2022-01-29 01:14:30 +01:00
99dc7490cb missing translation 2022-01-29 00:33:09 +01:00
5ac485e06e example of workflow + finalize normalization 2022-01-29 00:20:35 +01:00
dc184762d6 workflow history 2022-01-29 00:20:35 +01:00
86e7b0f007 rewrite workflow and handle finalize differently 2022-01-29 00:20:35 +01:00
fdafe7c82b normalizer for entityworkflow 2022-01-29 00:20:35 +01:00
d285d84068 wip 2022-01-28 22:30:18 +01:00
da22532587 cancel comments in entity notifications 2022-01-28 22:27:30 +01:00
b83ad77fd8 homepage_widget: details + courses table 2022-01-28 18:22:33 +01:00
00ac6aa1b9 homepage_widget: init counters on load, arrange MyCustoms tab 2022-01-28 18:10:48 +01:00
318c001904 changelog updated 2022-01-28 18:07:20 +01:00
e2d406b97b php validation added 2022-01-28 18:06:13 +01:00
nobohan
efff496f7a hot fix: pick-entity: set input.value to null if multiple=false 2022-01-28 18:01:29 +01:00
fe4eaa92be fix bug display current openingDate in datepicker 2022-01-28 17:36:59 +01:00
cb17a7e8a5 Visual improvements of date input fields in social action create form 2022-01-28 17:06:09 +01:00
ad4153a07e New component for parcours startDate 2022-01-28 17:05:41 +01:00
e95093f144 Merge branch 'homepage/rewrite' of gitlab.com:Chill-Projet/chill-bundles into homepage/rewrite 2022-01-28 16:36:01 +01:00
59cdf07c7e add form in notification thread inside entities show 2022-01-28 16:23:41 +01:00
292c9380ad endpoint for recent accompanying period attributions 2022-01-28 15:39:37 +01:00
701ad17299 Merge branch 'homepage/rewrite' of gitlab.com:Chill-Projet/chill-bundles into homepage/rewrite 2022-01-28 15:24:05 +01:00
7f41f14959 resume page: move notify button in masonry bloc and notification list below in content 2022-01-28 15:21:44 +01:00
fcd5fba13e add history for user in accompanying period (+ counter) 2022-01-28 14:42:18 +01:00
2c57eab4d2 workflows list: rename tabs translations 2022-01-28 14:18:11 +01:00
2cd51eed2e Merge remote-tracking branch 'origin/master' into homepage/rewrite 2022-01-28 14:13:28 +01:00
72e306b583 change add notification button color 2022-01-28 12:46:58 +01:00
917dd49d07 workflows: add freeze mechanism on breadcrumbs, edit document button, and history 2022-01-28 12:41:44 +01:00
71ca912749 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2022-01-28 12:25:06 +01:00
4620e32b82 changelog updated 2022-01-28 12:20:29 +01:00
0cda5d637d styling of parcours list in person search changed 2022-01-28 12:16:32 +01:00
646f39b9ed Merge conflicts fixed 2022-01-28 12:15:51 +01:00
nobohan
105fe16122 upd CHANGELOG 2022-01-28 11:40:30 +01:00
nobohan
4d1a553474 show activity attendee (présence) in the activity list 2022-01-28 11:40:08 +01:00
5bfdee0c28 corrections review 2022-01-28 11:09:14 +01:00
ae33392ad0 update changelog 2022-01-28 10:58:24 +01:00
0e0fe90277 Merge branch 'issue399_dynamicPicker_asideActivity' into 'master'
dynamic picker aside activity

See merge request Chill-Projet/chill-bundles!305
2022-01-28 09:55:27 +00:00
792eafce72 dynamic picker aside activity 2022-01-28 09:55:26 +00:00
616d01a6c9 Merge branch 'issue403_residential_address' into 'master'
residentialAddress

See merge request Chill-Projet/chill-bundles!302
2022-01-28 09:48:00 +00:00
juminet
21edc74ada residentialAddress 2022-01-28 09:48:00 +00:00
ab23290599 refund extension_list_notification template: change place and content 2022-01-28 10:22:48 +01:00
1a2e1eaf2a put notification comments in a separate include 2022-01-28 10:22:17 +01:00
feaee8a0b1 a few more implementations in twig templates 2022-01-28 10:07:34 +01:00
0a26e7f326 using serialized age instead of function + translation fix 2022-01-28 09:57:14 +01:00
ebf85d4e4d Merge branch 'homepage/rewrite' of gitlab.com:Chill-Projet/chill-bundles into homepage/rewrite 2022-01-28 09:56:52 +01:00
72cffd7a3d minor changes 2022-01-28 09:56:20 +01:00
65418b17ce Merge branch 'issues_362_filiations' into 'master'
filiations: small adjustments (colors, labels, ...)

See merge request Chill-Projet/chill-bundles!309
2022-01-28 08:49:59 +00:00
juminet
8432c215a3 filiations: small adjustments (colors, labels, ...) 2022-01-28 08:49:59 +00:00
nobohan
87e1f8f077 hot fix: initialize typed comment in CommentEmbeddable 2022-01-28 09:47:36 +01:00
nobohan
33f393203e activity admin: change validation rule for social action visible field 2022-01-27 19:43:08 +01:00
6aa1e136b4 fix bug when suggested person is thirdparty 2022-01-27 18:14:32 +01:00
10a9b6c909 using normalizer for age and added obele 2022-01-27 18:13:46 +01:00
ce912e4405 ajout compteurs 2022-01-27 17:28:59 +01:00
ab11d3e8b3 Merge branch 'issue400_sort_activity' into 'master'
activity: order activities by date and by id

See merge request Chill-Projet/chill-bundles!301
2022-01-27 16:12:08 +00:00
juminet
445fe0cee4 activity: order activities by date and by id 2022-01-27 16:12:07 +00:00
7c6c8de456 Merge branch 'issue394_address_confidential' into 'master'
confidential address

See merge request Chill-Projet/chill-bundles!299
2022-01-27 16:08:58 +00:00
0378df42b6 fix cs 2022-01-27 17:08:00 +01:00
ad8d40cb1c work on confidential / blur module 2022-01-27 16:52:46 +01:00
f4516f8369 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2022-01-27 16:31:41 +01:00
5b25f82963 changelog updated 2022-01-27 16:24:35 +01:00
8720cc730e implementation of person-text 2022-01-27 16:19:19 +01:00
44d1535bfb style fixes 2022-01-27 16:18:54 +01:00
8d663cdee6 creation of vue component person-text 2022-01-27 16:18:35 +01:00
7c8b08c3a7 Merge branch 'master' into issue394_address_confidential 2022-01-27 15:39:10 +01:00
6df570d96c set referrer app 2022-01-27 15:22:15 +01:00
3ce650eea6 Merge branch 'issue389_add_age' of gitlab.com:Chill-Projet/chill-bundles into issue389_add_age 2022-01-27 13:03:02 +01:00
6ef7d9b47b adding age in renderbox and renderstring, implementation of renderbox option in household summary 2022-01-27 12:59:14 +01:00
af3847366b adding age in renderbox and renderstring, implementation of renderbox option in household summary 2022-01-27 12:33:50 +01:00
d70f8aa712 phpstan fixes 2022-01-27 11:50:26 +01:00
14463dcd38 changelog updated 2022-01-27 11:45:03 +01:00
b9c2d63a53 mes parcours page created 2022-01-27 11:42:14 +01:00
3fd4c6339a Merge conflicts resolved 2022-01-27 11:34:57 +01:00
4d76de5f9f fix normalization for person in docgen 2022-01-26 23:52:27 +01:00
0a92ad905b variables for docgen 2022-01-26 23:49:24 +01:00
7af7135971 use trait to implement TrackCreationInterface and TrackUpdateInterface 2022-01-26 21:51:13 +01:00
c70a4dc664 fix loading of evaluations 2022-01-26 21:46:07 +01:00
dc0fae7549 fixes person resources 2022-01-26 17:53:09 +01:00
b38924cc3d Revert "Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles"
This reverts commit 58c4e37116.
2022-01-26 16:38:58 +01:00
58c4e37116 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2022-01-26 16:04:09 +01:00
3656825d20 bug fix in form and controller 2022-01-26 15:56:10 +01:00
39a7cecd24 homepage_widget: improve notifications rows 2022-01-26 15:44:17 +01:00
6d382f93e8 use pickDynamicUserType 2022-01-26 15:08:47 +01:00
6e554e74ab Merge branch 'issue382_person_resource' 2022-01-26 14:43:50 +01:00
4c125865cf fix of twig if condition for person resources 2022-01-26 14:21:39 +01:00
12c68a4c9f Merge branch 'issue406_truncate_comment' into 'master'
Truncate comment

See merge request Chill-Projet/chill-bundles!304
2022-01-26 13:06:04 +00:00
66ac5c7435 Merge remote-tracking branch 'origin/master' into issue406_truncate_comment 2022-01-26 14:03:49 +01:00
e4b46a188c Merge branch 'master' into issue406_truncate_comment 2022-01-26 14:01:42 +01:00
7a78e8a6a7 fixes for pesonnes ressources 2022-01-26 14:00:45 +01:00
4496836cb2 Merge branch 'issue382_person_resource' into 'master'
Creation of PersonResource

See merge request Chill-Projet/chill-bundles!291
2022-01-26 12:52:15 +00:00
88d1fe24b4 Creation of PersonResource 2022-01-26 12:52:15 +00:00
0ea1708fe9 Merge branch 'master' into issue382_person_resource 2022-01-26 13:50:58 +01:00
a9b7cf93e9 adaptation for list for regulation 2022-01-26 13:50:19 +01:00
5d36e56641 changelog updated 2022-01-26 12:57:14 +01:00
f00dfad98e csfixes 2022-01-26 12:56:02 +01:00
7943c22ae7 comments truncated and link added to comment page 2022-01-26 12:55:48 +01:00
d365fcea39 merge conflict in changelog resolved 2022-01-26 12:13:37 +01:00
9f9f9d7881 csfixes 2022-01-26 12:10:51 +01:00
20f814766c merge fixes 2022-01-26 12:10:18 +01:00
ed00a882af cs-fixes 2022-01-26 12:08:18 +01:00
5d7501d36e violation added if all three association entities are null 2022-01-26 12:08:18 +01:00
ab5f9923a6 CSfixer changes 2022-01-26 12:08:18 +01:00
1a5e8b9d7b Fix user/thirdparty/freetext field in edit form 2022-01-26 12:08:18 +01:00
f7f2bb0caa reorganize templates to fix edit form 2022-01-26 12:08:18 +01:00
bf7fcfa41a bugfixes in display of dynamicPicker 2022-01-26 12:08:18 +01:00
9056ea8449 styling changes collapse form 2022-01-26 12:08:18 +01:00
f0e349a2b0 show-hide files combined into one js file 2022-01-26 12:08:18 +01:00
2ccc6f1976 fix backend error of data transformer 2022-01-26 12:08:18 +01:00
cf041cf49e merge dynamic types 2022-01-26 12:08:18 +01:00
72d593e416 improve migration 2022-01-26 12:08:18 +01:00
287dca07aa php cs-fixes 2022-01-26 12:08:18 +01:00
910f9a7e8e update changelog 2022-01-26 12:08:18 +01:00
9457483a6b different dynamic entity picker types created 2022-01-26 12:08:18 +01:00
6d07168529 type variable added and file renamed in transformer 2022-01-26 12:08:18 +01:00
420f81ea24 fix phpstan error 2022-01-26 12:08:18 +01:00
416b62fc60 show hide functional 2022-01-26 12:08:18 +01:00
ecae7dab44 add placeholders for inputs 2022-01-26 12:07:39 +01:00
126bd1a4eb fix for comment embeddable null + template adjustments 2022-01-26 12:07:39 +01:00
450792e446 fix embeddableComment field, but still cannot be null and date+user_id are not set...? 2022-01-26 12:07:39 +01:00
ec155b1d67 bug fixed in entity 2022-01-26 12:07:39 +01:00
02b2af7d51 attempt to make show hide work 2022-01-26 12:07:39 +01:00
0a6a2c968c all views created for person resource 2022-01-26 12:07:39 +01:00
4b188e2df6 adjustments to form + rendering of resource kinds 2022-01-26 12:07:39 +01:00
d6bf1988f6 migration adjusted 2022-01-26 12:07:39 +01:00
94c9505c05 js files put in place + select menus added for selecting person/thirdparty/... show-hide not working yet 2022-01-26 12:07:39 +01:00
4a5a1440ff form created + general layout of templates 2022-01-26 12:06:44 +01:00
b31cc460fa route and menu entry added in person menu + start of templates/controller 2022-01-26 12:06:44 +01:00
48c3432191 changelog entry added 2022-01-26 12:06:44 +01:00
7b4405f6fe entities + migration created 2022-01-26 12:04:16 +01:00
ce3a74326d cs-fixes 2022-01-26 12:01:14 +01:00
51ab013bd7 violation added if all three association entities are null 2022-01-26 11:20:47 +01:00
41a6366efe vue homepage_widget: improve translations, titles, tabs order, add no data statement 2022-01-26 10:13:58 +01:00
c3c2fd30f0 better title proportions 2022-01-25 21:42:04 +01:00
1952e4aa5a render table results for works tab 2022-01-25 21:39:20 +01:00
1e1311b7c8 add counter pills in tabs 2022-01-25 21:09:11 +01:00
fc1ed8b71e display table with notifications datas, prepare others tabs to render datas 2022-01-25 20:08:39 +01:00
2144b247b3 store all makeFetch, with isLoaded check 2022-01-25 18:36:54 +01:00
e4629ed599 store first makeFetch, with loading spinner 2022-01-25 17:40:08 +01:00
f27dca6fb5 CSfixer changes 2022-01-25 16:41:44 +01:00
8c27ec0720 Fix user/thirdparty/freetext field in edit form 2022-01-25 16:38:37 +01:00
8392e2e6bd reorganize templates to fix edit form 2022-01-25 16:09:11 +01:00
963b0b1ed2 bugfixes in display of dynamicPicker 2022-01-25 16:08:40 +01:00
Pol Dellaiera
7513187a6d Revert "fix cs"
This reverts commit 7b3e9cb490.
2022-01-25 15:54:20 +01:00
Pol Dellaiera
990fc0e484 Remove obsolete file. 2022-01-25 15:50:07 +01:00
8fce27a128 move styles in better place 2022-01-25 15:43:45 +01:00
55c2c41cea styling changes collapse form 2022-01-25 14:46:09 +01:00
0bd10b6dfa show-hide files combined into one js file 2022-01-25 14:45:38 +01:00
8cd01d4317 fix backend error of data transformer 2022-01-25 14:43:51 +01:00
71293e3378 Merge branch '397_homepage' into homepage/rewrite 2022-01-25 14:41:53 +01:00
cb58afa44a Merge branch 'master' into homepage/rewrite 2022-01-25 14:40:38 +01:00
7b3e9cb490 fix cs 2022-01-25 14:40:28 +01:00
fb720a382f sticky buttons smaller 2022-01-25 14:32:57 +01:00
a4bfa1cb16 Merge remote-tracking branch 'origin/master' into homepage/rewrite 2022-01-25 14:28:29 +01:00
502f2aceed init sub-components and active tab mechanism 2022-01-25 13:02:24 +01:00
Pol Dellaiera
eb9cc0b63f Update minor things. 2022-01-25 12:19:14 +01:00
Pol Dellaiera
7ee554b48d Update minor things. 2022-01-25 11:56:30 +01:00
Pol Dellaiera
2e6d281bfc cs: Autofix coding standard based on updated rules. 2022-01-25 11:46:29 +01:00
Pol Dellaiera
4b1bf7ce23 Remove obsolete file. 2022-01-25 11:33:14 +01:00
Pol Dellaiera
2d574fea2e chore: Remove duplicate entries from composer.json and normalize it. 2022-01-25 11:29:59 +01:00
0e2772336f create new app vue homepage_widget, with store 2022-01-25 11:27:31 +01:00
320d11671a prepare homepage_widget template 2022-01-25 10:54:08 +01:00
6ef5390a26 update change log with release 2022-01-24 20:00:14 +01:00
dec26e2ba4 merge dynamic types 2022-01-24 19:42:06 +01:00
514d75fa5f improve migration 2022-01-24 19:26:16 +01:00
0ac51da31a Merge remote-tracking branch 'origin/issue386_dynamic_type_picker' into issue382_person_resource 2022-01-24 19:23:24 +01:00
90d43b0bd3 Merge remote-tracking branch 'origin/master' into issue382_person_resource 2022-01-24 19:21:06 +01:00
5fe0790912 fix error when creating evaluation 2022-01-24 19:17:04 +01:00
ff66aa5590 fix debug message 2022-01-24 15:14:45 +01:00
8e2d3616b5 Merge remote-tracking branch 'origin/master' into issue383_referent_in_acc_course 2022-01-24 15:04:55 +01:00
c046824ea3 Merge branch 'issue385_comments_accourse' into 'master'
Bug fix pinned comments parcours

See merge request Chill-Projet/chill-bundles!297
2022-01-24 13:55:50 +00:00
a9b216b16b Merge remote-tracking branch 'origin/master' into issue385_comments_accourse 2022-01-24 14:54:00 +01:00
f866eae581 Merge branch 'issue347_document_gen_activity' into 'master'
activity: doc gen

See merge request Chill-Projet/chill-bundles!295
2022-01-24 13:51:14 +00:00
juminet
21d4e49fce activity: doc gen 2022-01-24 13:51:14 +00:00
17b887160a Merge branch '105_worflow' into 'master'
105 worflow

See merge request Chill-Projet/chill-bundles!275
2022-01-24 13:17:46 +00:00
c7dbaae8d6 105 worflow 2022-01-24 13:17:46 +00:00
nobohan
25ee11e4ff upd CHANGELOG 2022-01-24 13:48:47 +01:00
nobohan
df661fc7c4 address: render blur address if confidential in address render box (twig) 2022-01-24 12:13:34 +01:00
nobohan
6b78c06dfb address: use address.confidential in AddressRenderBox.vue 2022-01-24 12:04:23 +01:00
daff4e4200 Merge branch 'household/composition-add' into 'master'
Household/composition add + fixes household composition editor

See merge request Chill-Projet/chill-bundles!290
2022-01-24 10:59:00 +00:00
53b3f98bba Household/composition add + fixes household composition editor 2022-01-24 10:59:00 +00:00
2b47868d88 Merge branch 'notification/improve' into 'master'
Improve notifications

See merge request Chill-Projet/chill-bundles!294
2022-01-24 10:09:57 +00:00
54c2b92962 Improve notifications 2022-01-24 10:09:57 +00:00
nobohan
d087f051f0 address: remove maxlenght constraint in vuejs form for corridor, flat, steps, floor 2022-01-24 11:08:25 +01:00
nobohan
b3edba3abe address: add confidential in vuejs edit form 2022-01-24 11:04:17 +01:00
nobohan
02d8ceba25 address: add confidential in normalizer + code style fix 2022-01-24 10:42:05 +01:00
nobohan
741043c177 address: alter some address field to TEXT + add confidential field 2022-01-24 10:05:47 +01:00
c42f62de4c fix phpstan errors: undefined variable or type invalid 2022-01-24 00:32:47 +01:00
e23ef35b75 list of unread notificaiton api endpoint 2022-01-24 00:24:20 +01:00
eccc75aecf fix cs 2022-01-23 23:22:04 +01:00
7e2fbf93f9 task: api endpoint for my tasks 2022-01-23 23:13:25 +01:00
6ab8f95f7d list of "my evaluation near end" 2022-01-23 22:40:17 +01:00
ad05b3bf05 fix variable name in work list page 2022-01-23 22:25:52 +01:00
1faa9812db fix variable name in work list page 2022-01-23 22:24:48 +01:00
a786578cbe fix cs 2022-01-23 21:52:27 +01:00
c08bd76e4b adding parameter until and since on 'work near end' 2022-01-23 21:52:16 +01:00
20a4950c60 accompanying period work list for my, near end date 2022-01-23 13:23:50 +01:00
cdbae1eade bugfix in parcours display if deathdate of associated person is not defined (ex. for thirdparty) 2022-01-21 17:00:04 +01:00
45f3bb00d6 php csfixes 2022-01-21 16:50:24 +01:00
2b9f0e5177 changelog updated 2022-01-21 16:50:09 +01:00
fcf2ae364f all comments are kept when after a second is flagged 2022-01-21 16:47:02 +01:00
92788ccdc0 php cs-fixes 2022-01-21 15:58:55 +01:00
84993ca05b update changelog 2022-01-21 15:55:22 +01:00
972657b38e different dynamic entity picker types created 2022-01-21 15:53:52 +01:00
b26d0905a3 type variable added and file renamed in transformer 2022-01-21 15:01:12 +01:00
4ab0a28f7d fix phpstan error 2022-01-21 13:05:38 +01:00
52009bd7c6 show hide functional 2022-01-21 12:35:33 +01:00
7acd6c7fc3 add placeholders for inputs 2022-01-21 12:34:14 +01:00
f26cba415b fix for comment embeddable null + template adjustments 2022-01-21 12:08:41 +01:00
8a2e588190 fix embeddableComment field, but still cannot be null and date+user_id are not set...? 2022-01-20 17:14:14 +01:00
d63d9c2f4e bug fixed in entity 2022-01-20 17:13:28 +01:00
dcdee1d6e3 attempt to make show hide work 2022-01-20 17:12:18 +01:00
dd589af1be all views created for person resource 2022-01-20 17:11:51 +01:00
nobohan
ce594692b3 accompanying course: confirm DRAFT acc. period when suggestedReferrers is unique or null 2022-01-20 16:59:01 +01:00
nobohan
17076024f7 accompanying course: fix filtering of suggested referrers 2022-01-20 14:27:36 +01:00
nobohan
61607fee3d accompanying course: code fix 2022-01-20 14:14:30 +01:00
nobohan
25ebeebdfb accompanying course: display closed acc. period in list 2022-01-20 08:32:48 +01:00
nobohan
343b2a6f2f accompanying course: re-open: translations + fine-tuning 2022-01-20 08:21:09 +01:00
nobohan
d4efe81dbb person: can reopen an accompanying course 2022-01-19 21:46:40 +01:00
nobohan
9d6afc4bb2 accompanying course: close social issues selector on select 2022-01-19 21:04:35 +01:00
2bdf116698 adjustments to form + rendering of resource kinds 2022-01-19 19:46:01 +01:00
96dc711b4f migration adjusted 2022-01-19 19:38:57 +01:00
c736c2b5bb update changelog 2022-01-19 17:48:46 +01:00
nobohan
d1918e4be0 accompanying course: filter referrer by job 2022-01-19 17:48:00 +01:00
b3aca957ff Merge remote-tracking branch 'origin/master' into issue377_addFields_toPerson 2022-01-19 17:47:34 +01:00
03bd4d1942 update logic to adapt to altnames 2022-01-19 17:46:27 +01:00
nobohan
040c5f8847 accompanying course: change wording for scopes 2022-01-19 16:18:04 +01:00
nobohan
9b5990916f job in accompanying course: fix error in setting job property 2022-01-19 15:40:38 +01:00
nobohan
74e94637b9 job in accompanying course: validation 2022-01-19 15:40:38 +01:00
nobohan
216c035bac vuejs: add missing translations for multiselect 2022-01-19 15:40:38 +01:00
nobohan
16c86daafb accompanying course: add selector for accompanying course job 2022-01-19 15:40:38 +01:00
nobohan
175fa7bf2f php code fix 2022-01-19 15:40:38 +01:00
nobohan
9f7f6e33e8 user-job: make an endpoint for userJob + doc swagger 2022-01-19 15:40:38 +01:00
nobohan
3471bdec0d accompanyingPeriod: add job 2022-01-19 15:40:38 +01:00
409cd40460 js code put in seperate file for compilation 2022-01-19 15:37:30 +01:00
2811e61439 fix filtering of names 2022-01-19 15:13:22 +01:00
2ff34688bb fix validation for notification 2022-01-19 13:59:03 +01:00
8133dd7385 Merge branch 'issue375_dead_persons' into 'master'
person: add obele sign for dead persons

See merge request Chill-Projet/chill-bundles!292
2022-01-19 11:23:52 +00:00
juminet
256dab3739 person: add obele sign for dead persons 2022-01-19 11:23:51 +00:00
532a751509 Merge branch 'issue381_address_in_location' into 'master'
address in location

See merge request Chill-Projet/chill-bundles!289
2022-01-19 11:22:33 +00:00
juminet
f0a7565302 [location] fix address saving in admin form 2022-01-19 11:22:33 +00:00
64c4f1ece2 Merge branch 'issue374_documents_in_activity_bug' into 'master'
Issue374 documents in activity bug

See merge request Chill-Projet/chill-bundles!287
2022-01-19 10:59:44 +00:00
juminet
ad983d80d2 Issue374 documents in activity bug 2022-01-19 10:59:44 +00:00
001e2f9622 js files put in place + select menus added for selecting person/thirdparty/... show-hide not working yet 2022-01-19 09:48:39 +01:00
58ac4b01ef form created + general layout of templates 2022-01-18 14:54:03 +01:00
6e4bfd45c6 route and menu entry added in person menu + start of templates/controller 2022-01-18 13:55:09 +01:00
ff0b0678a5 changelog entry added 2022-01-18 11:11:29 +01:00
3b56cc818b entities + migration created 2022-01-18 11:08:24 +01:00
21d5f974eb advanced search possible 2022-01-18 10:20:45 +01:00
59b2b07a21 space added between deathdate and age + changelog updated 2022-01-17 18:04:43 +01:00
eaf9f72fdd lastname added in uppercase 2022-01-17 16:34:54 +01:00
169442decc changelog updated 2022-01-17 16:29:46 +01:00
cf8e25e823 search parameter also passed on in normal list template 2022-01-17 16:26:51 +01:00
96b1f31665 javascript added to easily fill in form fields with name elements 2022-01-17 16:24:35 +01:00
2ad798c0bf Merge branch 'master' into notification_finitions 2022-01-17 15:29:48 +01:00
7e932e838f Squashed commit of the following:
commit 9e767fa3e0788d87437c235e51fcdc4f26f75d98
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Mon Jan 17 15:28:02 2022 +0100

    traductions

commit db65134743
Author: nobohan <juminet@gmail.com>
Date:   Mon Jan 17 12:17:22 2022 +0100

    add person: increase z-index of toast and wait for validation before closing modal

commit 7af4c3434e
Merge: a09c8ee8a 46c6d0e29
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Jan 16 22:51:45 2022 +0100

    Merge remote-tracking branch 'origin/master' into issue357_front_end_validation

commit a09c8ee8af
Author: nobohan <juminet@gmail.com>
Date:   Wed Jan 12 15:47:11 2022 +0100

    upd CHANGELOG

commit a312a9463d
Author: nobohan <juminet@gmail.com>
Date:   Wed Jan 12 15:29:32 2022 +0100

    address: display error message if some fields are empty (street & streetnumber)

commit 0035128138
Author: nobohan <juminet@gmail.com>
Date:   Wed Jan 12 14:47:43 2022 +0100

    address: display error message if some fields are empty

commit 49cb154672
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 20:58:00 2022 +0100

    address: add field validation (WIP)

commit 1a7ec9e396
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 17:16:43 2022 +0100

    Activity: fix vuejs warning

commit fa0b9271c2
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 16:13:23 2022 +0100

    location: treat 422 error when POSTing new location

commit c7b9a1a3fe
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 16:00:29 2022 +0100

    location: fix error when creating a new location: a new location could not be added to the availableLocations due to refactoring

commit f1c61a2387
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 15:20:33 2022 +0100

    person: treat 422 error in AddPerson for thirdparty

commit 8f6a70b240
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 11:30:05 2022 +0100

    person: add validation for required fields in on-the-fly person

commit 40e4bf953f
Author: nobohan <juminet@gmail.com>
Date:   Tue Jan 11 09:34:15 2022 +0100

    vuejs: better violations message in 422 error handling

commit 378f3a16fc
Author: nobohan <juminet@gmail.com>
Date:   Mon Jan 10 18:11:02 2022 +0100

    person: on-the-fly person: first implementation of makeFetch for posting person
2022-01-17 15:28:49 +01:00
41354097f3 order of form fields changed 2022-01-17 14:25:28 +01:00
9a3f35703b parameter passed to person creation page 2022-01-17 14:24:46 +01:00
5423de3bd9 fields added to person creation form 2022-01-17 14:23:54 +01:00
5e3d421b56 remove the possibility to generate a document from accompanying period work 2022-01-16 23:59:02 +01:00
71ca033b08 Merge branch 'issue363_localisation_type_not_available_to_users' into 'master'
localisation type not available to edit by users

See merge request Chill-Projet/chill-bundles!282
2022-01-16 22:51:29 +00:00
juminet
2c774e814e localisation type not available to edit by users 2022-01-16 22:51:29 +00:00
3034ba411f [course list in person context] show renderbox for referent 2022-01-16 23:39:47 +01:00
ce6e51df87 Merge branch 'issue341_user_main_location' into 'master'
[main] Add mainLocation field to User entity

See merge request Chill-Projet/chill-bundles!283
2022-01-16 22:36:55 +00:00
juminet
6843b4cb2a [main] Add mainLocation field to User entity 2022-01-16 22:36:55 +00:00
dcbce270ad Merge branch 'issue376_internal_activityType_display' into 'master'
Display of activity types

See merge request Chill-Projet/chill-bundles!286
2022-01-16 22:18:16 +00:00
3f5a6c6b15 Display of activity types 2022-01-16 22:18:16 +00:00
d806551477 notification, list item: manage option fold_item with macro 2022-01-15 20:33:31 +01:00
120ce40dbe notification list: use bootstrap accordion to fold/unfold notification content 2022-01-15 17:13:20 +01:00
22022e5143 JS toggle class read/unread status works on *all* container (change DOM selector) 2022-01-14 20:02:00 +01:00
20fcaa5428 notification: add prefix in object 2022-01-14 15:03:36 +01:00
819017112d JS toggle class read/unread when clicking on vue component NotificationReadToggle.vue 2022-01-13 18:56:07 +01:00
d7d7fb5693 add a chill button "tpchild", to create on-the-fly thirdparty (kind=child) 2022-01-13 16:15:49 +01:00
650e0d79be improve notification list and show styles 2022-01-13 15:40:41 +01:00
0afccd12a9 fix error when twig insert onthefly with parent undefined 2022-01-13 14:11:53 +01:00
7abe3e1b2d chill-no-data-statement smaller (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/354) 2022-01-13 14:11:08 +01:00
371 changed files with 14691 additions and 2759 deletions

View File

@@ -11,10 +11,92 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* renommer "dossier numéro" en "parcours numéro" dans les résultats de recherche
* renomme date de début en date d'ouverture dans le formulaire parcours
## Test releases ## Test releases
### test release 2021-01-31
[fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413
[homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
* [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409)
* [person] accompanying course: close modal when edit participation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420)
* [person] accompanying course: treat validation error when editing on-the-fly entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420)
* [activity] show activity attendee (présence) in the activity list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/412)
* [activity] admin: change validation rule for social action visible field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/413)
* [parcours]: component added to change the opening date of a parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/411)
* [search]: listing of parcours display changed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/410)
* [user]: page with accompanying periods to which is user is referent (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/408)
* [person] age added to renderstring + renderbox/ vue component created to display person text (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/389)
* [household member editor] allow to push to existing household
* [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal
### test release 2021-01-28
* [person] improve filiations vis graph: disable physics, use chill colors for persons-households-course, increase label of relations, remove labels on household arrows and other improvements (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/286, https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/362)
* [activity] Order activity by date and by id (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/364)
* [main] increase length of 4 Address fields (change to TEXT, no size limits) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/277)
* [main] Add confidential option for address, in edit and view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/165)
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
* [person] Add residential address entity, form and list for each person
* [aside_activity]: dynamicUserPickerType used (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/399)
* dispatching list
### test release 2021-01-26
* [parcours] comments truncated if too long + link added (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/406)
* [person]: possibility to add person resources (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/382)
* [person ressources]: module added
### test release 2022-01-24
* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
* [notification: formulaire création] descend la box avec la description dans le bas du formulaire
* [notification for activity]: fix link to activity
* [notification] add "URGENT" before accompanying course with emergency = true
* [notification] add a "read more" button on system notification
* [notification] add `[Chill]` in the subject of each notification, automatically
* [notification] add a counter for notification in activity list and accompanying period list, and search results
* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
* [workflow] add breadcrumb to show steps
* [popover] add popover html popup mechanism (used by workflow breadcrumb)
* [templates] improve updatedBy macro in item metadatas
* [parcours]: bug fix when comment is pinned all other comments remain in the collection (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/385)
* [workflow]
* add My workflow section with my opened subscriptions
* apply workflow on documents, accompanyingCourseWork and Evaluations
* [wopi-link] a new vue component allow to open wopi link in a fullscreen chill-themed modal
### test release 2022-01-19
* vuejs: add dead information on all on-the-fly person render boxes, in vis graph and other templates (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/271)
* [thirdparty] fix bug in 3rd party view: types was replaced by thirdPartyTypes
* [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246)
* [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176)
* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
* [forms] dynamic picker types for user/person/thirdparty types created (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/386)
### test release 2022-01-17
* [main] Add editableByUser field to locationType entity, adapt the admin template and add this condition in the location-type endpoint (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/297)
* [main] Add mainLocation field to User entity and add it in user form type
* rewrite page which allow to select activity
* [main] Add mainLocation field to User entity and add it in user form type
* [course list in person context] show full username/label for ref
* [accompanying period work] remove the possibility to generate document from an accompanying period work
* vuejs: add validation on required fields for AddPerson, Address and Location components
* vuejs: treat 422 validation errors in locations and AddPerson components
* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
## Test releases
* vuejs: add validation on required fields for AddPerson, Address and Location components
* vuejs: treat 422 validation errors in locations and AddPerson components
* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
### test release 2022-01-12 ### test release 2022-01-12
* fix thirdparty normalizer on telephone field: https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/322 * fix thirdparty normalizer on telephone field: https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/322

View File

@@ -52,9 +52,6 @@
"twig/string-extra": "^3.3", "twig/string-extra": "^3.3",
"twig/twig": "^3.0" "twig/twig": "^3.0"
}, },
"conflict": {
"symfony/symfony": "*"
},
"require-dev": { "require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", "doctrine/doctrine-fixtures-bundle": "^3.3",
"drupol/php-conventions": "^5", "drupol/php-conventions": "^5",
@@ -71,23 +68,17 @@
"symfony/var-dumper": "^4.4", "symfony/var-dumper": "^4.4",
"symfony/web-profiler-bundle": "^4.4" "symfony/web-profiler-bundle": "^4.4"
}, },
"config": { "conflict": {
"bin-dir": "bin", "symfony/symfony": "*"
"optimize-autoloader": true,
"sort-packages": true,
"vendor-dir": "tests/app/vendor",
"allow-plugins": {
"composer/package-versions-deprecated": true,
"phpstan/extension-installer": true,
"ergebnis/composer-normalize": true,
"phpro/grumphp": true
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Chill\\ActivityBundle\\": "src/Bundle/ChillActivityBundle", "Chill\\ActivityBundle\\": "src/Bundle/ChillActivityBundle",
"Chill\\AsideActivityBundle\\": "src/Bundle/ChillAsideActivityBundle/src",
"Chill\\BudgetBundle\\": "src/Bundle/ChillBudgetBundle", "Chill\\BudgetBundle\\": "src/Bundle/ChillBudgetBundle",
"Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle",
"Chill\\CustomFieldsBundle\\": "src/Bundle/ChillCustomFieldsBundle", "Chill\\CustomFieldsBundle\\": "src/Bundle/ChillCustomFieldsBundle",
"Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle",
"Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle", "Chill\\DocStoreBundle\\": "src/Bundle/ChillDocStoreBundle",
"Chill\\EventBundle\\": "src/Bundle/ChillEventBundle", "Chill\\EventBundle\\": "src/Bundle/ChillEventBundle",
"Chill\\FamilyMemberBundle\\": "src/Bundle/ChillFamilyMemberBundle", "Chill\\FamilyMemberBundle\\": "src/Bundle/ChillFamilyMemberBundle",
@@ -96,9 +87,6 @@
"Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle", "Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
"Chill\\AsideActivityBundle\\": "src/Bundle/ChillAsideActivityBundle/src",
"Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle",
"Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle",
"Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src" "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src"
} }
}, },
@@ -111,10 +99,10 @@
"config": { "config": {
"allow-plugins": { "allow-plugins": {
"composer/package-versions-deprecated": true, "composer/package-versions-deprecated": true,
"phpstan/extension-installer": true,
"ergebnis/composer-normalize": true, "ergebnis/composer-normalize": true,
"ocramius/package-versions": true,
"phpro/grumphp": true, "phpro/grumphp": true,
"ocramius/package-versions": true "phpstan/extension-installer": true
}, },
"bin-dir": "bin", "bin-dir": "bin",
"optimize-autoloader": true, "optimize-autoloader": true,
@@ -123,8 +111,8 @@
}, },
"scripts": { "scripts": {
"auto-scripts": { "auto-scripts": {
"cache:clear": "symfony-cmd", "assets:install %PUBLIC_DIR%": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd" "cache:clear": "symfony-cmd"
} }
} }
} }

View File

@@ -1,3 +0,0 @@
add npm/yarn dependency in package.json :
"select2-bootstrap-theme": "0.1.0-beta.10",

View File

@@ -80,11 +80,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php path: src/Bundle/ChillPersonBundle/Form/ChoiceLoader/PersonChoiceLoader.php
-
message: "#^Foreach overwrites \\$action with its value variable\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
- -
message: "#^Foreach overwrites \\$action with its value variable\\.$#" message: "#^Foreach overwrites \\$action with its value variable\\.$#"
count: 1 count: 1

View File

@@ -30,36 +30,6 @@ parameters:
count: 2 count: 2
path: src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php path: src/Bundle/ChillPersonBundle/Household/MembersEditorFactory.php
-
message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:buildQueryBySocialActionWithDescendants\\(\\) has invalid type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\SocialAction\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Parameter \\$action of method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:countBySocialActionWithDescendants\\(\\) has invalid type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\SocialAction\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$action$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$limit$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$offset$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
-
message: "#^Undefined variable\\: \\$orderBy$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
- -
message: "#^Variable variables are not allowed\\.$#" message: "#^Variable variables are not allowed\\.$#"
count: 4 count: 4

View File

@@ -400,11 +400,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php path: src/Bundle/ChillPersonBundle/Form/Type/PersonPhoneType.php
-
message: "#^Method Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\AccompanyingPeriodWorkRepository\\:\\:buildQueryBySocialActionWithDescendants\\(\\) has invalid return type Chill\\\\PersonBundle\\\\Repository\\\\AccompanyingPeriod\\\\QueryBuilder\\.$#"
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/AccompanyingPeriodWorkRepository.php
- -
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 3 count: 3

View File

@@ -31,6 +31,7 @@ use DateTime;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
@@ -38,8 +39,8 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use function array_key_exists; use function array_key_exists;
final class ActivityController extends AbstractController final class ActivityController extends AbstractController
@@ -255,7 +256,7 @@ final class ActivityController extends AbstractController
if ($person instanceof Person) { if ($person instanceof Person) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $person); $this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
$activities = $this->activityACLAwareRepository $activities = $this->activityACLAwareRepository
->findByPerson($person, ActivityVoter::SEE, 0, null); ->findByPerson($person, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']);
$event = new PrivacyEvent($person, [ $event = new PrivacyEvent($person, [
'element_class' => Activity::class, 'element_class' => Activity::class,
@@ -268,7 +269,7 @@ final class ActivityController extends AbstractController
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod); $this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);
$activities = $this->activityACLAwareRepository $activities = $this->activityACLAwareRepository
->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE); ->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']);
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig'; $view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
} }
@@ -471,20 +472,21 @@ final class ActivityController extends AbstractController
public function showAction(Request $request, int $id): Response public function showAction(Request $request, int $id): Response
{ {
$view = null; $entity = $this->activityRepository->find($id);
[$person, $accompanyingPeriod] = $this->getEntity($request); if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
}
$accompanyingPeriod = $entity->getAccompanyingPeriod();
$person = $entity->getPerson();
if ($accompanyingPeriod instanceof AccompanyingPeriod) { if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig'; $view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) { } elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:showPerson.html.twig'; $view = 'ChillActivityBundle:Activity:showPerson.html.twig';
} } else {
throw new RuntimeException('the activity should be linked with a period or person');
$entity = $this->activityRepository->find($id);
if (null === $entity) {
throw $this->createNotFoundException('Unable to find Activity entity.');
} }
if (null !== $accompanyingPeriod) { if (null !== $accompanyingPeriod) {
@@ -493,8 +495,7 @@ final class ActivityController extends AbstractController
$entity->personsNotAssociated = $entity->getPersonsNotAssociated(); $entity->personsNotAssociated = $entity->getPersonsNotAssociated();
} }
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période $this->denyAccessUnlessGranted(ActivityVoter::SEE, $entity);
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_SEE', $entity);
$deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod); $deleteForm = $this->createDeleteForm($entity->getId(), $person, $accompanyingPeriod);

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Entity; namespace Chill\ActivityBundle\Entity;
use Chill\ActivityBundle\Validator\Constraints as ActivityValidator; use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\Document; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
@@ -67,7 +67,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType") * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
* @SerializedName("activityType") * @SerializedName("activityType")
* @ORM\JoinColumn(name="type_id") * @ORM\JoinColumn(name="type_id")
*/ */
@@ -75,16 +75,19 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence") * @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence")
* @Groups({"docgen:read"})
*/ */
private ?ActivityPresence $attendee = null; private ?ActivityPresence $attendee = null;
/** /**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_") * @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_")
* @Groups({"docgen:read"})
*/ */
private CommentEmbeddable $comment; private CommentEmbeddable $comment;
/** /**
* @ORM\Column(type="datetime") * @ORM\Column(type="datetime")
* @Groups({"docgen:read"})
*/ */
private DateTime $date; private DateTime $date;
@@ -100,6 +103,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\Column(type="boolean", options={"default": false}) * @ORM\Column(type="boolean", options={"default": false})
* @Groups({"docgen:read"})
*/ */
private bool $emergency = false; private bool $emergency = false;
@@ -107,13 +111,13 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
*/ */
private ?int $id = null; private ?int $id = null;
/** /**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
* @groups({"read"}) * @groups({"read", "docgen:read"})
*/ */
private ?Location $location = null; private ?Location $location = null;
@@ -124,42 +128,45 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
*/ */
private ?Collection $persons = null; private ?Collection $persons = null;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason") * @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason")
* @Groups({"docgen:read"})
*/ */
private Collection $reasons; private Collection $reasons;
/** /**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
* @Groups({"docgen:read"})
*/ */
private ?Scope $scope = null; private ?Scope $scope = null;
/** /**
* @ORM\Column(type="string", options={"default": ""}) * @ORM\Column(type="string", options={"default": ""})
* @Groups({"docgen:read"})
*/ */
private string $sentReceived = ''; private string $sentReceived = '';
/** /**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction") * @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
*/ */
private Collection $socialActions; private Collection $socialActions;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue") * @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue")
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue") * @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
*/ */
private Collection $socialIssues; private Collection $socialIssues;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty") * @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
*/ */
private ?Collection $thirdParties = null; private ?Collection $thirdParties = null;
@@ -170,12 +177,13 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/** /**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User") * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
* @Groups({"docgen:read"})
*/ */
private User $user; private User $user;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User") * @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
*/ */
private ?Collection $users = null; private ?Collection $users = null;
@@ -191,7 +199,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
$this->socialActions = new ArrayCollection(); $this->socialActions = new ArrayCollection();
} }
public function addDocument(Document $document): self public function addDocument(StoredObject $document): self
{ {
$this->documents[] = $document; $this->documents[] = $document;
@@ -302,6 +310,18 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->documents; return $this->documents;
} }
/**
* @Groups({"docgen:read"})
*/
public function getDurationMinute(): int
{
if (null === $this->durationTime) {
return 0;
}
return (int) round(($this->durationTime->getTimestamp() + $this->durationTime->getOffset()) / 60.0, 0);
}
public function getDurationTime(): ?DateTime public function getDurationTime(): ?DateTime
{ {
return $this->durationTime; return $this->durationTime;
@@ -402,6 +422,18 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->travelTime; return $this->travelTime;
} }
/**
* @Groups({"docgen:read"})
*/
public function getTravelTimeMinute(): int
{
if (null === $this->travelTime) {
return 0;
}
return (int) round(($this->travelTime->getTimestamp() + $this->travelTime->getOffset()) / 60.0, 0);
}
/** /**
* @deprecated * @deprecated
*/ */
@@ -425,7 +457,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->getEmergency(); return $this->getEmergency();
} }
public function removeDocument(Document $document): void public function removeDocument(StoredObject $document): void
{ {
$this->documents->removeElement($document); $this->documents->removeElement($document);
} }

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Entity; namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
/** /**
* Class ActivityPresence. * Class ActivityPresence.
@@ -31,11 +32,14 @@ class ActivityPresence
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @Serializer\Groups({"docgen:read"})
*/ */
private ?int $id; private ?int $id;
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json")
* @Serializer\Groups({"docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/ */
private array $name = []; private array $name = [];

View File

@@ -13,8 +13,10 @@ namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException; use InvalidArgumentException;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/** /**
* Class ActivityType. * Class ActivityType.
@@ -118,6 +120,7 @@ class ActivityType
* @ORM\Id * @ORM\Id
* @ORM\Column(name="id", type="integer") * @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO") * @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"docgen:read"})
*/ */
private ?int $id; private ?int $id;
@@ -133,7 +136,8 @@ class ActivityType
/** /**
* @ORM\Column(type="json") * @ORM\Column(type="json")
* @Groups({"read"}) * @Groups({"read", "docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/ */
private array $name = []; private array $name = [];
@@ -190,7 +194,6 @@ class ActivityType
/** /**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1}) * @ORM\Column(type="smallint", nullable=false, options={"default": 1})
* @Assert\EqualTo(propertyPath="socialIssuesVisible", message="This parameter must be equal to social issue parameter")
*/ */
private int $socialActionsVisible = self::FIELD_INVISIBLE; private int $socialActionsVisible = self::FIELD_INVISIBLE;
@@ -260,6 +263,23 @@ class ActivityType
*/ */
private int $userVisible = self::FIELD_REQUIRED; private int $userVisible = self::FIELD_REQUIRED;
/**
* @Assert\Callback
*
* @param mixed $payload
*/
public function checkSocialActionsVisibility(ExecutionContextInterface $context, $payload)
{
if ($this->socialIssuesVisible !== $this->socialActionsVisible) {
if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) {
$context
->buildViolation('The socialActionsVisible value is not compatible with the socialIssuesVisible value')
->atPath('socialActionsVisible')
->addViolation();
}
}
}
/** /**
* Get active * Get active
* return true if the type is active. * return true if the type is active.

View File

@@ -307,6 +307,7 @@ class ActivityType extends AbstractType
'allow_add' => true, 'allow_add' => true,
'button_add_label' => 'activity.Insert a document', 'button_add_label' => 'activity.Insert a document',
'button_remove_label' => 'activity.Remove a document', 'button_remove_label' => 'activity.Remove a document',
'empty_collection_explain' => 'No documents',
]); ]);
} }

View File

@@ -9,8 +9,9 @@ div.new-activity-select-type {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: flex-start;
gap: 12px; gap: 12px;
margin-bottom: 30px;
div.bloc { div.bloc {
width: 200px; width: 200px;
@@ -27,26 +28,28 @@ div.new-activity-select-type {
// precise dashboard specific details // precise dashboard specific details
p.date-label { p.date-label {
display: inline-block; display: inline-block;
margin: 0 0.5em 0 0; margin: 0 0.5em 0 0;
font-weight: 700; font-weight: 700;
font-size: 18pt; font-size: 18pt;
} }
div.dashboard, div.dashboard,
h4.badge-title,
h3.badge-title,
h2.badge-title { h2.badge-title {
ul.list-content { ul.list-content {
font-size: 70%; font-size: 70%;
list-style-type: none; list-style-type: none;
padding-left: 0; padding-left: 0;
margin: 0; margin: 0;
li { li {
margin-bottom: 0.2em; margin-bottom: 0.2em;
// exception: change bg color for action badges above dashboard // exception: change bg color for action badges above dashboard
.bg-light { .bg-light {
background-color: $chill-light-gray !important; background-color: $chill-light-gray !important;
} }
} }
} }
} }
//// ACTIVITY SHOW AND FORM PAGES //// ACTIVITY SHOW AND FORM PAGES

View File

@@ -11,7 +11,7 @@ import Location from './components/Location.vue';
export default { export default {
name: "App", name: "App",
props: ['hasSocialIssues', 'hasLocation', 'hasPerson'], props: ['hasSocialIssues', 'hasLocation', 'hasPerson'],
components: { components: {
ConcernedGroups, ConcernedGroups,
SocialIssuesAcc, SocialIssuesAcc,

View File

@@ -1,45 +1,48 @@
<template> <template>
<teleport to="#add-persons" v-if="isComponentVisible"> <teleport to="#add-persons" v-if="isComponentVisible">
<div class="flex-bloc concerned-groups" :class="getContext"> <div class="flex-bloc concerned-groups" :class="getContext">
<persons-bloc <persons-bloc
v-for="bloc in contextPersonsBlocs" v-for="bloc in contextPersonsBlocs"
v-bind:key="bloc.key" v-bind:key="bloc.key"
v-bind:bloc="bloc" v-bind:bloc="bloc"
v-bind:blocWidth="getBlocWidth" v-bind:blocWidth="getBlocWidth"
v-bind:setPersonsInBloc="setPersonsInBloc"> v-bind:setPersonsInBloc="setPersonsInBloc">
</persons-bloc> </persons-bloc>
</div> </div>
<div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0"> <div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0">
<ul class="list-suggest add-items inline"> <ul class="list-suggest add-items inline">
<li v-for="(p, i) in suggestedEntities" @click="addSuggestedEntity(p)" :key="`suggestedEntities-${i}`"> <li v-for="(p, i) in suggestedEntities" @click="addSuggestedEntity(p)" :key="`suggestedEntities-${i}`">
<span>{{ p.text }}</span> <person-text v-if="p.type === 'person'" :person="p"></person-text>
</li> <span v-else>{{ p.text }}</span>
</ul> </li>
</div> </ul>
</div>
<add-persons <add-persons
buttonTitle="activity.add_persons" buttonTitle="activity.add_persons"
modalTitle="activity.add_persons" modalTitle="activity.add_persons"
v-bind:key="addPersons.key" v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions" v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons" @addNewPersons="addNewPersons"
ref="addPersons"> ref="addPersons">
</add-persons> </add-persons>
</teleport> </teleport>
</template> </template>
<script> <script>
import { mapState, mapGetters } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue'; import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
import PersonsBloc from './ConcernedGroups/PersonsBloc.vue'; import PersonsBloc from './ConcernedGroups/PersonsBloc.vue';
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
export default { export default {
name: "ConcernedGroups", name: "ConcernedGroups",
components: { components: {
AddPersons, AddPersons,
PersonsBloc PersonsBloc,
PersonText
}, },
data() { data() {
return { return {

View File

@@ -1,21 +1,29 @@
<template> <template>
<li> <li>
<span :title="person.text"> <span :title="person.text">
<span class="chill_denomination" @click.prevent="$emit('remove', person)">{{ textCutted }}</span> <span class="chill_denomination" @click.prevent="$emit('remove', person)">
<person-text :person="person" :isCut="true"></person-text>
</span>
</span> </span>
</li> </li>
</template> </template>
<script> <script>
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
export default { export default {
name: "PersonBadge", name: "PersonBadge",
props: ['person'], props: ['person'],
computed: { components: {
textCutted() { PersonText
let more = (this.person.text.length > 15) ?'…' : '';
return this.person.text.slice(0,15) + more;
}
}, },
// computed: {
// textCutted() {
// let more = (this.person.text.length > 15) ?'…' : '';
// return this.person.text.slice(0,15) + more;
// }
// },
emits: ['remove'], emits: ['remove'],
} }
</script> </script>

View File

@@ -24,7 +24,7 @@
v-model="location" v-model="location"
> >
</VueMultiselect> </VueMultiselect>
<new-location v-bind:locations="locations"></new-location> <new-location v-bind:availableLocations="availableLocations"></new-location>
</div> </div>
</div> </div>
</teleport> </teleport>

View File

@@ -18,15 +18,6 @@
</template> </template>
<template v-slot:body> <template v-slot:body>
<form> <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"> <div class="form-floating mb-3">
<select class="form-select form-select-lg" id="type" required v-model="selectType"> <select class="form-select form-select-lg" id="type" required v-model="selectType">
<option selected disabled value="">{{ $t('activity.choose_location_type') }}</option> <option selected disabled value="">{{ $t('activity.choose_location_type') }}</option>
@@ -62,6 +53,12 @@
<input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder /> <input class="form-control form-control-lg" id="email" v-model="inputEmail" placeholder />
<label for="email">{{ $t('activity.location_fields.email') }}</label> <label for="email">{{ $t('activity.location_fields.email') }}</label>
</div> </div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</form> </form>
</template> </template>
<template v-slot:footer> <template v-slot:footer>
@@ -81,7 +78,8 @@
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue'; import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import { mapState } from "vuex"; import { mapState } from "vuex";
import { getLocationTypes, postLocation } from "../../api"; import { getLocationTypes } from "../../api";
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
export default { export default {
name: "NewLocation", name: "NewLocation",
@@ -89,7 +87,7 @@ export default {
Modal, Modal,
AddAddress, AddAddress,
}, },
props: ['locations'], props: ['availableLocations'],
data() { data() {
return { return {
errors: [], errors: [],
@@ -223,7 +221,6 @@ export default {
}, },
saveNewLocation() { saveNewLocation() {
if (this.checkForm()) { if (this.checkForm()) {
console.log('saveNewLocation', this.selected);
let body = { let body = {
type: 'location', type: 'location',
name: this.selected.name, name: this.selected.name,
@@ -242,23 +239,28 @@ export default {
} }
}); });
} }
postLocation(body)
.then( makeFetch('POST', '/api/1.0/main/location.json', body)
location => new Promise(resolve => { .then(response => {
this.locations.push(location); this.$store.dispatch('addAvailableLocationGroup', {
this.$store.dispatch('updateLocation', location); locationGroup: 'Localisations nouvellement créées',
resolve(); locations: [response]
this.modal.showModal = false; });
}) this.$store.dispatch('updateLocation', response);
).catch( this.modal.showModal = false;
err => { })
this.errors.push(err.message); .catch((error) => {
if (error.name === 'ValidationException') {
for (let v of error.violations) {
this.errors.push(v);
}
} else {
this.errors.push('An error occurred');
} }
); })
}; };
}, },
submitNewAddress(payload) { submitNewAddress(payload) {
console.log('submitNewAddress', payload);
this.selected.addressId = payload.addressId; this.selected.addressId = payload.addressId;
this.addAddress.context.addressId = payload.addressId; this.addAddress.context.addressId = payload.addressId;
this.addAddress.context.edit = true; this.addAddress.context.edit = true;

View File

@@ -12,7 +12,11 @@ const hasLocation = document.querySelector('#location') !== null;
const hasPerson = document.querySelector('#add-persons') !== null; const hasPerson = document.querySelector('#add-persons') !== null;
const app = createApp({ const app = createApp({
template: `<app :hasSocialIssues="hasSocialIssues", :hasLocation="hasLocation", :hasPerson="hasPerson"></app>`, template: `<app
:hasSocialIssues="hasSocialIssues"
:hasLocation="hasLocation"
:hasPerson="hasPerson"
></app>`,
data() { data() {
return { return {
hasSocialIssues, hasSocialIssues,

View File

@@ -240,6 +240,9 @@ const store = createStore({
}); });
commit("updateActionsSelected", payload); commit("updateActionsSelected", payload);
}, },
addAvailableLocationGroup({ commit }, payload) {
commit("addAvailableLocationGroup", payload);
},
addPersonsInvolved({ commit }, payload) { addPersonsInvolved({ commit }, payload) {
//console.log('### action addPersonsInvolved', payload.result.type); //console.log('### action addPersonsInvolved', payload.result.type);
switch (payload.result.type) { switch (payload.result.type) {

View File

@@ -41,6 +41,17 @@
</div> </div>
{% endif %} {% endif %}
{% if activity.attendee and t.attendeeVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Attendee'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
{{ activity.attendee.name|localize_translatable_string }}
</p>
</div>
</div>
{% endif %}
{% if activity.sentReceived is not empty and t.sentReceivedVisible %} {% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<div class="wl-row"> <div class="wl-row">
<div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div> <div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div>
@@ -143,9 +154,17 @@
</div> </div>
<div class="item-row separator"> <div class="item-row separator">
<ul class="record_actions"> <div class="item-col item-meta">
{{ recordAction }} {% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %}
</ul> {% if notif_counter.total > 0 %}
{{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }}
{% endif %}
</div>
<div class="item-col">
<ul class="record_actions">
{{ recordAction }}
</ul>
</div>
</div> </div>
</div> </div>

View File

@@ -8,6 +8,7 @@
action: 'show', displayBadge: true, action: 'show', displayBadge: true,
targetEntity: { name: type, id: entity.id }, targetEntity: { name: type, id: entity.id },
buttonText: entity|chill_entity_render_string, buttonText: entity|chill_entity_render_string,
isDead: entity.deathdate is defined and entity.deathdate is not null,
parent: parent parent: parent
} %} } %}
{% endmacro %} {% endmacro %}

View File

@@ -83,15 +83,15 @@
{{ form_row(edit_form.comment) }} {{ form_row(edit_form.comment) }}
{% endif %} {% endif %}
{%- if edit_form.documents is defined -%}
{{ form_row(edit_form.documents) }}
{% endif %}
{%- if edit_form.attendee is defined -%} {%- if edit_form.attendee is defined -%}
{{ form_row(edit_form.attendee) }} {{ form_row(edit_form.attendee) }}
{% endif %} {% endif %}
{# TODO .. status #} {%- if edit_form.documents is defined -%}
{{ form_row(edit_form.documents) }}
{% endif %}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
{% set person_id = null %} {% set person_id = null %}
{% if entity.person %} {% if entity.person %}

View File

@@ -15,7 +15,7 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { 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 }}"]',
@@ -24,10 +24,12 @@
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ encore_entry_script_tags('vue_activity') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ encore_entry_link_tags('vue_activity') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{% endblock %} {% endblock %}

View File

@@ -30,7 +30,7 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { 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 }}"]',
@@ -39,9 +39,11 @@
window.activity = {{ activity_json|json_encode|raw }}; window.activity = {{ activity_json|json_encode|raw }};
</script> </script>
{{ encore_entry_script_tags('vue_activity') }} {{ encore_entry_script_tags('vue_activity') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }} {{ encore_entry_link_tags('vue_activity') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{% endblock %} {% endblock %}

View File

@@ -1,58 +1,62 @@
{% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %} {% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %}
{% if no_action is not defined or no_action == false %} {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
<li> {% if no_action is not defined or no_action == false %}
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
'entityId': activity.id
}) }}">{{ 'notification.Notify'|trans }}</a>
</li>
{% endif %}
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
{#
Disable person_id in following links, for redirect to accompanyingCourse context
#}
{% set person_id = null %}
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_list',{
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-primary"
title="{{ 'See activity in accompanying course context'|trans }}">
<i class="fa fa-random fa-fw"></i>
{{ 'Period number %number%'|trans({'%number%': accompanying_course_id}) }}
</a>
</li>
{% endif %}
<li>
<a href="{{ path('chill_activity_activity_show', {'id': activity.id,
'person_id': person_id,
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-show"
title="{{ 'Show'|trans }}"></a>
</li>
{% if no_action is not defined or no_action == false %}
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
<li> <li>
<a href="{{ path('chill_activity_activity_edit', {'id': activity.id, <a class="btn btn-misc" href="{{ chill_path_add_return_path('chill_main_notification_create', {
'person_id': person_id, 'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
'accompanying_period_id': accompanying_course_id 'entityId': activity.id
}) }}" }) }}">
class="btn btn-update" <i class="fa fa-paper-plane fa-fw"></i>
title="{{ 'Edit'|trans }}"></a> {{ 'notification.Notify'|trans }}</a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_ACTIVITY_DELETE', activity) %} {% if context == 'person' and activity.accompanyingPeriod is not empty %}
{#
Disable person_id in following links, for redirect to accompanyingCourse context
#}
{% set person_id = null %}
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
<li> <li>
<a href="{{ path('chill_activity_activity_delete', {'id': activity.id, <a href="{{ chill_path_add_return_path('chill_activity_activity_list',{
'person_id': person_id,
'accompanying_period_id': accompanying_course_id 'accompanying_period_id': accompanying_course_id
}) }}" }) }}"
class="btn btn-delete" class="btn btn-primary"
title="{{ 'Delete'|trans }}"></a> title="{{ 'See activity in accompanying course context'|trans }}">
<i class="fa fa-random fa-fw"></i>
{{ 'Period number %number%'|trans({'%number%': accompanying_course_id}) }}
</a>
</li> </li>
{% endif %} {% endif %}
<li>
<a href="{{ path('chill_activity_activity_show', {'id': activity.id,
'person_id': person_id,
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-show"
title="{{ 'Show'|trans }}"></a>
</li>
{% if no_action is not defined or no_action == false %}
{% 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-update"
title="{{ 'Edit'|trans }}"></a>
</li>
{% 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-delete"
title="{{ 'Delete'|trans }}"></a>
</li>
{% endif %}
{% endif %}
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}

View File

@@ -48,7 +48,7 @@
<li class="associated-persons"> <li class="associated-persons">
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span> <span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
{% for p in activity.personsAssociated %} {% for p in activity.personsAssociated %}
<span class="badge-person">{{ p|chill_entity_render_box }}</span> <span class="badge-person">{{ p|chill_entity_render_box({'addAgeBadge': true}) }}</span>
{% endfor %} {% endfor %}
</li> </li>
</ul> </ul>

View File

@@ -14,7 +14,7 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript"> <script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]', chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',

View File

@@ -25,7 +25,7 @@
'activityData': activityData 'activityData': activityData
}) }}"> }) }}">
<div class="bloc btn btn-primary btn-lg btn-block"> <div class="btn btn-primary">
{{ activityType.name|localize_translatable_string }} {{ activityType.name|localize_translatable_string }}
</div> </div>
</a> </a>

View File

@@ -86,7 +86,7 @@
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with { {% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
'context': context, 'context': context,
'render': 'bloc', 'render': 'bloc',
'badge_person': 'true' 'badge_person': true
} %} } %}
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2> <h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
@@ -165,11 +165,7 @@
<dt class="inline">{{ 'Attendee'|trans }}</dt> <dt class="inline">{{ 'Attendee'|trans }}</dt>
<dd> <dd>
{% if entity.attendee is not null %} {% if entity.attendee is not null %}
{% if entity.attendee %} {{ entity.attendee.name|localize_translatable_string }}
{{ 'present'|trans|capitalize }}
{% else %}
{{ 'not present'|trans|capitalize }}
{% endif %}
{% else %} {% else %}
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span> <span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
{% endif %} {% endif %}
@@ -181,6 +177,13 @@
</div> </div>
</div> </div>
<div class="notification notification-list">
{% set notifications = chill_list_notifications('Chill\\ActivityBundle\\Entity\\Activity', entity.id) %}
{% if notifications is not empty %}
{{ notifications|raw }}
{% endif %}
</div>
{% set person_id = null %} {% set person_id = null %}
{% if person %} {% if person %}
{% set person_id = person.id %} {% set person_id = person.id %}
@@ -197,18 +200,21 @@
{{ 'Back to the list'|trans }} {{ 'Back to the list'|trans }}
</a> </a>
</li> </li>
{% if is_granted('CHILL_ACTIVITY_UPDATE', entity) %} <li>
<li> <a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}">
<a class="btn btn-update" href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"> {{ 'notification.Notify'|trans }}
{{ 'Edit'|trans }}
</a> </a>
</li> </li>
{% if is_granted('CHILL_ACTIVITY_UPDATE', entity) %}
<li>
<a href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-update">{{ 'Edit'|trans }}</a>
</li>
{% endif %} {% endif %}
{% if is_granted('CHILL_ACTIVITY_DELETE', entity) %} {% if is_granted('CHILL_ACTIVITY_DELETE', entity) %}
<li> <li>
<a href="{{ path('chill_activity_activity_delete', { 'id': entity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete"> <a href="{{ path('chill_activity_activity_delete', { 'id': entity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
{{ 'Delete'|trans }} class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
</a>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>

View File

@@ -7,11 +7,13 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
@@ -23,19 +25,5 @@
{% endblock content %} {% endblock content %}
{% block block_post_menu %} {% block block_post_menu %}
<div class="post-menu pt-4"> <div class="post-menu pt-4"></div>
<div class="d-grid gap-2">
<a class="btn btn-primary" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}">
<i class="fa fa-paper-plane fa-fw"></i>
{{ 'notification.Notify'|trans }}
</a>
</div>
{% set notifications = chill_list_notifications('Chill\\ActivityBundle\\Entity\\Activity', entity.id) %}
{% if notifications is not empty %}
{{ notifications|raw }}
{% endif %}
</div>
{% endblock %} {% endblock %}

View File

@@ -7,11 +7,13 @@
{% block js %} {% block js %}
{{ parent() }} {{ parent() }}
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
@@ -26,8 +28,7 @@
<div class="post-menu pt-4"> <div class="post-menu pt-4">
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<a class="btn btn-primary" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}"> <a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity', 'entityId': entity.id}) }}">
<i class="fa fa-paper-plane fa-fw"></i>
{{ 'notification.Notify'|trans }} {{ 'notification.Notify'|trans }}
</a> </a>
</div> </div>

View File

@@ -0,0 +1,220 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\Service\DocGenerator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\Document;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Templating\Entity\PersonRender;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ActivityContext implements
DocGeneratorContextWithAdminFormInterface,
DocGeneratorContextWithPublicFormInterface
{
private BaseContextData $baseContextData;
private DocumentCategoryRepository $documentCategoryRepository;
private EntityManagerInterface $em;
private NormalizerInterface $normalizer;
private PersonRender $personRender;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
public function __construct(
DocumentCategoryRepository $documentCategoryRepository,
NormalizerInterface $normalizer,
TranslatableStringHelperInterface $translatableStringHelper,
EntityManagerInterface $em,
PersonRender $personRender,
TranslatorInterface $translator,
BaseContextData $baseContextData
) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
$this->em = $em;
$this->personRender = $personRender;
$this->translator = $translator;
$this->baseContextData = $baseContextData;
}
public function adminFormReverseTransform(array $data): array
{
return $data;
}
public function adminFormTransform(array $data): array
{
return [
'mainPerson' => $data['mainPerson'] ?? false,
'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'),
'person1' => $data['person1'] ?? false,
'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'),
'person2' => $data['person2'] ?? false,
'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'),
];
}
public function buildAdminForm(FormBuilderInterface $builder): void
{
$builder
->add('mainPerson', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for main person',
])
->add('mainPersonLabel', TextType::class, [
'label' => 'main person label',
'required' => true,
])
->add('person1', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for person 1',
])
->add('person1Label', TextType::class, [
'label' => 'person 1 label',
'required' => true,
])
->add('person2', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for person 2',
])
->add('person2Label', TextType::class, [
'label' => 'person 2 label',
'required' => true,
]);
}
/**
* @param Activity $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$options = $template->getOptions();
$persons = $entity->getPersons();
foreach (['mainPerson', 'person1', 'person2'] as $key) {
if ($options[$key] ?? false) {
$builder->add($key, EntityType::class, [
'class' => Person::class,
'choices' => $persons,
'choice_label' => function (Person $p) {
return $this->personRender->renderString($p, []);
},
'multiple' => false,
'expanded' => true,
'label' => $options[$key . 'Label'],
]);
}
}
}
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
if (!$entity instanceof Activity) {
throw new UnexpectedTypeException($entity, Activity::class);
}
$options = $template->getOptions();
$data = [];
$data = array_merge($data, $this->baseContextData->getData());
$data['activity'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => Activity::class, 'groups' => 'docgen:read']);
$data['course'] = $this->normalizer->normalize($entity->getAccompanyingPeriod(), 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);
$data['person'] = $this->normalizer->normalize($entity->getPerson(), 'docgen', ['docgen:expects' => Person::class, 'groups' => 'docgen:read']);
foreach (['mainPerson', 'person1', 'person2'] as $k) {
if ($options[$k]) {
$data[$k] = $this->normalizer->normalize($contextGenerationData[$k], 'docgen', [
'docgen:expects' => Person::class,
'groups' => 'docgen:read',
'docgen:person:with-household' => true,
'docgen:person:with-relations' => true,
]);
}
}
return $data;
}
public function getDescription(): string
{
return 'docgen.A basic context for activity';
}
public function getEntityClass(): string
{
return Activity::class;
}
public function getFormData(DocGeneratorTemplate $template, $entity): array
{
return [
'activity' => $entity,
];
}
public static function getKey(): string
{
return self::class;
}
public function getName(): string
{
return 'docgen.Activity basic';
}
public function hasAdminForm(): bool
{
return true;
}
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool
{
$options = $template->getOptions();
return $options['mainPerson'] || $options['person1'] || $options['person2'];
}
/**
* @param Activity $entity
*/
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new StoredObject();
// TODO push document to remote
$this->em->persist($doc);
$entity->addDocument($doc);
}
}

View File

@@ -29,9 +29,13 @@ use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
final class ActivityVoterTest extends KernelTestCase final class ActivityVoterTest extends KernelTestCase
{ {
use PrepareActivityTrait; use PrepareActivityTrait;
use PrepareCenterTrait; use PrepareCenterTrait;
use PreparePersonTrait; use PreparePersonTrait;
use PrepareScopeTrait; use PrepareScopeTrait;
use PrepareUserTrait; use PrepareUserTrait;
/** /**

View File

@@ -32,3 +32,8 @@ services:
autowire: true autowire: true
autoconfigure: true autoconfigure: true
resource: '../Validator/Constraints/' resource: '../Validator/Constraints/'
Chill\ActivityBundle\Service\DocGenerator\:
autowire: true
autoconfigure: true
resource: '../Service/DocGenerator/'

View File

@@ -76,7 +76,7 @@ activity:
Insert a document: Insérer un document Insert a document: Insérer un document
Remove a document: Supprimer le document Remove a document: Supprimer le document
comment: Commentaire comment: Commentaire
No documents: Pas de documents
#timeline #timeline
'%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"' '%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"'
@@ -228,3 +228,7 @@ See activity in accompanying course context: Voir l'activité dans le contexte d
You get notified of an activity which does not exists any more: Cette notification ne correspond pas à une activité valide. You get notified of an activity which does not exists any more: Cette notification ne correspond pas à une activité valide.
you are not allowed to see it details: La notification fait référence à une activité à laquelle vous n'avez pas accès. you are not allowed to see it details: La notification fait référence à une activité à laquelle vous n'avez pas accès.
This is the minimal activity data: Activité n° This is the minimal activity data: Activité n°
docgen:
Activity basic: Echange
A basic context for activity: Contexte pour les échanges

View File

@@ -20,3 +20,4 @@ For this type of activity, you must add at least one social action: Pour ce type
# admin # admin
This parameter must be equal to social issue parameter: Ce paramètre doit être égal au paramètre "Visibilité du champs Problématiques sociales" This parameter must be equal to social issue parameter: Ce paramètre doit être égal au paramètre "Visibilité du champs Problématiques sociales"
The socialActionsVisible value is not compatible with the socialIssuesVisible value: Cette valeur du paramètre "Visibilité du champs Actions sociales" n'est pas compatible avec la valeur du paramètre "Visibilité du champs Problématiques sociales"

View File

@@ -32,6 +32,8 @@ final class AsideActivityController extends CRUDController
{ {
$asideActivity = new AsideActivity(); $asideActivity = new AsideActivity();
$asideActivity->setAgent($this->getUser());
$duration = $request->query->get('duration', '300'); $duration = $request->query->get('duration', '300');
$duration = DateTime::createFromFormat('U', $duration); $duration = DateTime::createFromFormat('U', $duration);
$asideActivity->setDuration($duration); $asideActivity->setDuration($duration);

View File

@@ -14,9 +14,9 @@ namespace Chill\AsideActivityBundle\Form;
use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Entity\AsideActivity;
use Chill\AsideActivityBundle\Entity\AsideActivityCategory; use Chill\AsideActivityBundle\Entity\AsideActivityCategory;
use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; use Chill\AsideActivityBundle\Templating\Entity\CategoryRender;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use DateInterval; use DateInterval;
use DateTime; use DateTime;
use DateTimeImmutable; use DateTimeImmutable;
@@ -68,22 +68,10 @@ final class AsideActivityFormType extends AbstractType
]; ];
$builder $builder
->add( ->add('agent', PickUserDynamicType::class, [
'agent', 'label' => 'For agent',
EntityType::class, 'required' => true,
[ ])
'label' => 'For agent',
'required' => true,
'class' => User::class,
'data' => $this->storage->getToken()->getUser(),
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('u')->where('u.enabled = true');
},
'attr' => ['class' => 'select2 '],
'placeholder' => 'Choose the agent for whom this activity is created',
'choice_label' => 'username',
]
)
->add( ->add(
'date', 'date',
ChillDateType::class, ChillDateType::class,

View File

@@ -1,5 +1,15 @@
{% extends '@ChillMain/Admin/layout.html.twig' %} {% extends '@ChillMain/Admin/layout.html.twig' %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block title %} {% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %} {% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %} {% endblock %}
@@ -8,4 +18,5 @@
{% embed '@ChillMain/CRUD/_new_content.html.twig' %} {% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %} {% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %} {% endembed %}
{% endblock %} {% endblock %}

View File

@@ -17,7 +17,7 @@
<select class="form-select" v-model="template"> <select class="form-select" v-model="template">
<option disabled selected value="">{{ $t('choose_a_template') }}</option> <option disabled selected value="">{{ $t('choose_a_template') }}</option>
<template v-for="t in templates"> <template v-for="t in templates">
<option v-bind:value="t.id">{{ t.name.fr }}</option> <option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option>
</template> </template>
</select> </select>
<button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button> <button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button>
@@ -48,6 +48,7 @@ export default {
required: true, required: true,
}, },
templates: { templates: {
type: Array,
required: true, required: true,
}, },
// beforeMove execute "something" before // beforeMove execute "something" before
@@ -73,7 +74,11 @@ export default {
return true; return true;
}, },
getDescription() { getDescription() {
return this.templates.find(t => t.id === this.template).description || ''; let desc = this.templates.find(t => t.id === this.template);
if (null === desc) {
return '';
}
return desc.description || '';
}, },
}, },
methods: { methods: {

View File

@@ -0,0 +1,61 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
<div class="flex-table accompanying_course_work-list">
<div class="item-bloc document-item bg-chill-llight-gray">
<div class="row justify-content-center my-4">
<div class="col-2">
<i class="fa fa-4x fa-file-text-o text-success"></i>
</div>
<div class="col-8">
<h3>{{ document.title }}</h3>
<small>{{ document.object.type }}</small>
{% if document.description is not empty %}
<blockquote class="chill-user-quote mt-2">
{{ document.description }}
</blockquote>
{% endif %}
</div>
</div>
</div>
</div>
{% set freezed = false %}
{% for step in entity_workflow.stepsChained %}
{% if loop.last %}
{% if step.previous is not null and step.previous.freezeAfter == true %}
{% set freezed = true %}
{% endif %}
{% endif %}
{% endfor %}
{% if display_action is defined and display_action == true %}
<ul class="record_actions">
<li>
{{ m.download_button(document.object, document.title) }}
</li>
<li>
{% if not freezed %}
{% set button = {
'changeIcon': 'fa-unlock',
} %}{#
'changeClass' string
'noText' boolean
#}
{# vue component #}
<span
data-module="wopi-link"
data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.object.uuid}) }}"
data-doc-title="{{ document.title|e('html_attr') }}"
data-doc-type="{{ document.object.type|e('html_attr') }}"
data-button="{{ button|json_encode }}"
></span>
{% else %}
<a class="btn btn-update change-icon disabled" href="#" title="{{ 'workflow.freezed document'|trans }}">
<i class="fa fa-lock me-2"></i>{{ 'Update document'|trans }}
</a>
{% endif %}
</li>
</ul>
{% endif %}

View File

@@ -0,0 +1,19 @@
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as m %}
<div class="flex-grow-1 {% if add_classes is defined %}{{ add_classes }}{% else %}h2{% endif %}">
<div>
{% if concerne is defined and concerne == true %}
<span class="item-key">{{ 'Concerne'|trans }}: </span>
{% endif %}
{{ 'workflow.Document (n°%doc%)'|trans({'%doc%': document.id}) }}
{% if description is defined and description == true %}
{{ ' — ' ~ document.title }}
{% endif %}
</div>
{% if breadcrumb is defined and breadcrumb == true %}
{{ m.breadcrumb(_context) }}
{% endif %}
</div>

View File

@@ -6,61 +6,72 @@
{% block title %} {% block title %}
{# {{ 'Detail of document of %name%'|trans({ '%name%': accompanyingCourse|chill_entity_render_string } ) }} #} {# {{ 'Detail of document of %name%'|trans({ '%name%': accompanyingCourse|chill_entity_render_string } ) }} #}
{% endblock %} {{ 'Document %title%' | trans({ '%title%': document.title }) }}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }} {{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="document-show">
<h1>{{ 'Document %title%' | trans({ '%title%': document.title }) }}</h1> <h1>{{ block('title') }}</h1>
<dl class="chill_view_data"> <dl class="chill_view_data">
<dt>{{ 'Title'|trans }}</dt> <dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd> <dd>{{ document.title }}</dd>
{% if document.category is not null %} {% if document.category is not null %}
<dt>{{ 'Category'|trans }}</dt> <dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</dd> <dd>{{ document.category.name|localize_translatable_string }}</dd>
{% endif %} {% endif %}
<dt>{{ 'Description' | trans }}</dt>
<dd>
{% if document.description is empty %}
<span class="chill-no-data-statement">{{ 'Any description'|trans }}</span>
{% else %}
<blockquote class="chill-user-quote">
{{ document.description|chill_markdown_to_html }}
</blockquote>
{% endif %}
</dd>
</dl>
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
{{ 'Back to the list' | trans }}
</a>
</li>
<li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('accompanying_course_document_edit', {'id': document.id, 'course': accompanyingCourse.id}) }}" class="btn btn-edit">
{{ 'Edit' | trans }}
</a>
</li>
{% endif %}
{% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %}
{% if workflows_frame is not empty %}
<li>
{{ workflows_frame|raw }}
</li>
{% endif %}
</ul>
</div>
{% endblock %}
{% block block_post_menu %}
<div class="post-menu pt-4"></div>
{% endblock %}
<dt>{{ 'Description' | trans }}</dt> {% block js %}
<dd> {{ parent() }}
{% if document.description is empty %} {{ encore_entry_script_tags('mod_async_upload') }}
<span class="chill-no-data-statement">{{ 'Any description'|trans }}</span> {{ encore_entry_script_tags('mod_entity_workflow_pick') }}
{% else %} {% endblock %}
<blockquote class="chill-user-quote">
{{ document.description|chill_markdown_to_html }}
</blockquote>
{% endif %}
</dd>
</dl>
<ul class="record_actions">
<li class="cancel">
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
{{ 'Back to the list' | trans }}
</a>
</li>
<li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('accompanying_course_document_edit', {'id': document.id, 'course': accompanyingCourse.id}) }}" class="btn btn-edit">
{{ 'Edit' | trans }}
</a>
</li>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,74 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\DocStoreBundle\Workflow;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandlerInterface
{
private EntityRepository $repository;
/**
* TODO: injecter le repository directement.
*/
public function __construct(EntityManagerInterface $em)
{
$this->repository = $em->getRepository(AccompanyingCourseDocument::class);
}
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingCourseDocument
{
return $this->repository->find($entityWorkflow->getRelatedEntityId());
}
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
{
return null;
}
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
{
return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig';
}
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array
{
return [
'entity_workflow' => $entityWorkflow,
'document' => $this->getRelatedEntity($entityWorkflow),
];
}
public function getTemplateTitle(EntityWorkflow $entityWorkflow, array $options = []): string
{
return '@ChillDocStore/AccompanyingCourseDocument/_workflow.title.html.twig';
}
public function getTemplateTitleData(EntityWorkflow $entityWorkflow, array $options = []): array
{
return $this->getTemplateData($entityWorkflow, $options);
}
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
{
return $entityWorkflow->getRelatedEntityClass() === AccompanyingCourseDocument::class;
}
public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool
{
return true;
}
}

View File

@@ -27,3 +27,8 @@ services:
autoconfigure: true autoconfigure: true
tags: tags:
- { name: chill.role } - { name: chill.role }
Chill\DocStoreBundle\Workflow\:
resource: './../Workflow/'
autoconfigure: true
autowire: true

View File

@@ -9,6 +9,7 @@ Create new document: Créer un nouveau document
New document for %name%: Nouveau document pour %name% New document for %name%: Nouveau document pour %name%
Editing document for %name%: Modification d'un document pour %name% Editing document for %name%: Modification d'un document pour %name%
Edit Document: Modification d'un document Edit Document: Modification d'un document
Update document: Modifier le document
Existing document: Document existant Existing document: Document existant
No document to download: Aucun document à télécharger No document to download: Aucun document à télécharger
'Choose a document category': Choisissez une catégorie de document 'Choose a document category': Choisissez une catégorie de document

View File

@@ -31,6 +31,7 @@ use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass; use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
use Chill\MainBundle\Templating\UI\NotificationCounterInterface; use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle; use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -56,6 +57,8 @@ class ChillMainBundle extends Bundle
->addTag('chill_main.notification_handler'); ->addTag('chill_main.notification_handler');
$container->registerForAutoconfiguration(NotificationCounterInterface::class) $container->registerForAutoconfiguration(NotificationCounterInterface::class)
->addTag('chill.count_notification.user'); ->addTag('chill.count_notification.user');
$container->registerForAutoconfiguration(EntityWorkflowHandlerInterface::class)
->addTag('chill_main.workflow_handler');
$container->addCompilerPass(new SearchableServicesCompilerPass()); $container->addCompilerPass(new SearchableServicesCompilerPass());
$container->addCompilerPass(new ConfigConsistencyCompilerPass()); $container->addCompilerPass(new ConfigConsistencyCompilerPass());

View File

@@ -24,6 +24,7 @@ class LocationTypeApiController extends ApiController
$query->andWhere( $query->andWhere(
$query->expr()->andX( $query->expr()->andX(
$query->expr()->eq('e.availableForUsers', "'TRUE'"), $query->expr()->eq('e.availableForUsers', "'TRUE'"),
$query->expr()->eq('e.editableByUsers', "'TRUE'"),
$query->expr()->eq('e.active', "'TRUE'"), $query->expr()->eq('e.active', "'TRUE'"),
) )
); );

View File

@@ -13,13 +13,19 @@ namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\NotificationRepository;
use Chill\MainBundle\Security\Authorization\NotificationVoter; use Chill\MainBundle\Security\Authorization\NotificationVoter;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\MainBundle\Serializer\Model\Counter;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use RuntimeException; use RuntimeException;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface;
use UnexpectedValueException; use UnexpectedValueException;
/** /**
@@ -29,12 +35,26 @@ class NotificationApiController
{ {
private EntityManagerInterface $entityManager; private EntityManagerInterface $entityManager;
private NotificationRepository $notificationRepository;
private PaginatorFactory $paginatorFactory;
private Security $security; private Security $security;
public function __construct(EntityManagerInterface $entityManager, Security $security) private SerializerInterface $serializer;
{
public function __construct(
EntityManagerInterface $entityManager,
NotificationRepository $notificationRepository,
PaginatorFactory $paginatorFactory,
Security $security,
SerializerInterface $serializer
) {
$this->entityManager = $entityManager; $this->entityManager = $entityManager;
$this->notificationRepository = $notificationRepository;
$this->paginatorFactory = $paginatorFactory;
$this->security = $security; $this->security = $security;
$this->serializer = $serializer;
} }
/** /**
@@ -53,6 +73,37 @@ class NotificationApiController
return $this->markAs('unread', $notification); return $this->markAs('unread', $notification);
} }
/**
* @Route("/my/unread")
*/
public function myUnreadNotifications(Request $request): JsonResponse
{
$total = $this->notificationRepository->countUnreadByUser($this->security->getUser());
if ($request->query->getBoolean('countOnly')) {
return new JsonResponse(
$this->serializer->serialize(new Counter($total), 'json', ['groups' => ['read']]),
JsonResponse::HTTP_OK,
[],
true
);
}
$paginator = $this->paginatorFactory->create($total);
$notifications = $this->notificationRepository->findUnreadByUser(
$this->security->getUser(),
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
$collection = new Collection($notifications, $paginator);
return new JsonResponse(
$this->serializer->serialize($collection, 'json', ['groups' => ['read']]),
JsonResponse::HTTP_OK,
[],
true
);
}
private function markAs(string $target, Notification $notification): JsonResponse private function markAs(string $target, Notification $notification): JsonResponse
{ {
if (!$this->security->isGranted(NotificationVoter::NOTIFICATION_TOGGLE_READ_STATUS, $notification)) { if (!$this->security->isGranted(NotificationVoter::NOTIFICATION_TOGGLE_READ_STATUS, $notification)) {

View File

@@ -0,0 +1,118 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Doctrine\ORM\EntityManagerInterface;
use LogicException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
class WorkflowApiController
{
private EntityManagerInterface $entityManager;
private Security $security;
public function __construct(Security $security, EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
$this->security = $security;
}
/**
* @Route("/api/1.0/main/workflow/{id}/subscribe", methods={"POST"})
*/
public function subscribe(EntityWorkflow $entityWorkflow, Request $request): Response
{
return $this->handleSubscription($entityWorkflow, $request, 'subscribe');
}
/**
* @Route("/api/1.0/main/workflow/{id}/unsubscribe", methods={"POST"})
*/
public function unsubscribe(EntityWorkflow $entityWorkflow, Request $request): Response
{
return $this->handleSubscription($entityWorkflow, $request, 'unsubscribe');
}
private function handleSubscription(EntityWorkflow $entityWorkflow, Request $request, string $action): JsonResponse
{
if (!$this->security->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
throw new AccessDeniedException();
}
if (!$request->query->has('subscribe')) {
throw new BadRequestHttpException('missing subscribe parameter');
}
$user = $this->security->getUser();
switch ($request->query->get('subscribe')) {
case 'final':
switch ($action) {
case 'subscribe':
$entityWorkflow->addSubscriberToFinal($user);
break;
case 'unsubscribe':
$entityWorkflow->removeSubscriberToFinal($user);
break;
default:
throw new LogicException();
}
break;
case 'step':
switch ($action) {
case 'subscribe':
$entityWorkflow->addSubscriberToStep($user);
break;
case 'unsubscribe':
$entityWorkflow->removeSubscriberToStep($user);
break;
default:
throw new LogicException();
}
break;
default:
throw new BadRequestHttpException('subscribe parameter must be equal to "step" or "final"');
}
$this->entityManager->flush();
return new JsonResponse(
[
'step' => $entityWorkflow->isUserSubscribedToStep($user),
'final' => $entityWorkflow->isUserSubscribedToFinal($user),
],
JsonResponse::HTTP_OK,
[],
false
);
}
}

View File

@@ -0,0 +1,257 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
use Chill\MainBundle\Form\EntityWorkflowCommentType;
use Chill\MainBundle\Form\WorkflowStepType;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\TransitionBlocker;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count;
class WorkflowController extends AbstractController
{
private EntityManagerInterface $entityManager;
private EntityWorkflowManager $entityWorkflowManager;
private EntityWorkflowRepository $entityWorkflowRepository;
private PaginatorFactory $paginatorFactory;
private Registry $registry;
private TranslatorInterface $translator;
private ValidatorInterface $validator;
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator)
{
$this->entityWorkflowManager = $entityWorkflowManager;
$this->entityWorkflowRepository = $entityWorkflowRepository;
$this->validator = $validator;
$this->paginatorFactory = $paginatorFactory;
$this->registry = $registry;
$this->entityManager = $entityManager;
$this->translator = $translator;
}
/**
* @Route("/{_locale}/main/workflow/create", name="chill_main_workflow_create")
*/
public function create(Request $request): Response
{
if (!$request->query->has('entityClass')) {
throw new BadRequestHttpException('Missing entityClass parameter');
}
if (!$request->query->has('entityId')) {
throw new BadRequestHttpException('missing entityId parameter');
}
if (!$request->query->has('workflow')) {
throw new BadRequestHttpException('missing workflow parameter');
}
$entityWorkflow = new EntityWorkflow();
$entityWorkflow
->setRelatedEntityClass($request->query->get('entityClass'))
->setRelatedEntityId($request->query->getInt('entityId'))
->setWorkflowName($request->query->get('workflow'));
$errors = $this->validator->validate($entityWorkflow, null, ['creation']);
if (count($errors) > 0) {
$msg = [];
foreach ($errors as $error) {
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
$msg[] = $error->getMessage();
}
return new Response(implode("\n", $msg), Response::HTTP_UNPROCESSABLE_ENTITY);
}
$this->denyAccessUnlessGranted(EntityWorkflowVoter::CREATE, $entityWorkflow);
$em = $this->getDoctrine()->getManager();
$em->persist($entityWorkflow);
$em->flush();
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
}
/**
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
*/
public function myWorkflowsDest(Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
$total = $this->entityWorkflowRepository->countByDest($this->getUser());
$paginator = $this->paginatorFactory->create($total);
$workflows = $this->entityWorkflowRepository->findByDest(
$this->getUser(),
['createdAt' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
return $this->render(
'@ChillMain/Workflow/list.html.twig',
[
'workflows' => $this->buildHandler($workflows),
'paginator' => $paginator,
'step' => 'dest',
]
);
}
/**
* @Route("/{_locale}/main/workflow/list/subscribed", name="chill_main_workflow_list_subscribed")
*/
public function myWorkflowsSubscribed(Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
$total = $this->entityWorkflowRepository->countBySubscriber($this->getUser());
$paginator = $this->paginatorFactory->create($total);
$workflows = $this->entityWorkflowRepository->findBySubscriber(
$this->getUser(),
['createdAt' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
return $this->render(
'@ChillMain/Workflow/list.html.twig',
[
'workflows' => $this->buildHandler($workflows),
'paginator' => $paginator,
'step' => 'subscribed',
]
);
}
/**
* @Route("/{_locale}/main/workflow/{id}/show", name="chill_main_workflow_show")
*/
public function show(EntityWorkflow $entityWorkflow, Request $request): Response
{
$this->denyAccessUnlessGranted(EntityWorkflowVoter::SEE, $entityWorkflow);
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
// possible transition
$transitionForm = $this->createForm(
WorkflowStepType::class,
$entityWorkflow->getCurrentStep(),
['transition' => true, 'entity_workflow' => $entityWorkflow]
);
$transitionForm->handleRequest($request);
if ($transitionForm->isSubmitted() && $transitionForm->isValid()) {
if (!$workflow->can($entityWorkflow, $transition = $transitionForm['transition']->getData()->getName())) {
$blockers = $workflow->buildTransitionBlockerList($entityWorkflow, $transition);
$msgs = array_map(function (TransitionBlocker $tb) {
return $this->translator->trans(
$tb->getMessage(),
$tb->getParameters()
);
}, iterator_to_array($blockers));
throw $this->createAccessDeniedException(
sprintf(
"not allowed to apply transition {$transition}: %s",
implode(', ', $msgs)
)
);
}
$workflow->apply($entityWorkflow, $transition);
foreach ($transitionForm['future_dest_users']->getData() as $user) {
$entityWorkflow->getCurrentStep()->addDestUser($user);
}
$this->entityManager->flush();
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
}
if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) {
$this->addFlash('error', $this->translator->trans('This form contains errors'));
}
}
/*
$commentForm = $this->createForm(EntityWorkflowCommentType::class, $newComment = new EntityWorkflowComment());
$commentForm->handleRequest($request);
if ($commentForm->isSubmitted() && $commentForm->isValid()) {
$this->entityManager->persist($newComment);
$this->entityManager->flush();
$this->addFlash('success', $this->translator->trans('workflow.Comment added'));
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
} elseif ($commentForm->isSubmitted() && !$commentForm->isValid()) {
$this->addFlash('error', $this->translator->trans('This form contains errors'));
}
*/
return $this->render(
'@ChillMain/Workflow/index.html.twig',
[
'handler_template' => $handler->getTemplate($entityWorkflow),
'handler_template_title' => $handler->getTemplateTitle($entityWorkflow),
'handler_template_data' => $handler->getTemplateData($entityWorkflow),
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
'entity_workflow' => $entityWorkflow,
//'comment_form' => $commentForm->createView(),
]
);
}
private function buildHandler(array $workflows): array
{
$lines = [];
foreach ($workflows as $workflow) {
$handler = $this->entityWorkflowManager->getHandler($workflow);
$lines[] = [
'handler' => $handler,
'entity_workflow' => $workflow,
];
}
return $lines;
}
}

View File

@@ -168,6 +168,7 @@ class ChillMainExtension extends Extension implements
$loader->load('services/timeline.yaml'); $loader->load('services/timeline.yaml');
$loader->load('services/search.yaml'); $loader->load('services/search.yaml');
$loader->load('services/serializer.yaml'); $loader->load('services/serializer.yaml');
$loader->load('services/mailer.yaml');
$this->configureCruds($container, $config['cruds'], $config['apis'], $loader); $this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
} }
@@ -391,6 +392,26 @@ class ChillMainExtension extends Extension implements
], ],
], ],
], ],
[
'class' => \Chill\MainBundle\Entity\UserJob::class,
'name' => 'user_job',
'base_path' => '/api/1.0/main/user-job',
'base_role' => 'ROLE_USER',
'actions' => [
'_index' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
],
],
'_entity' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
],
],
],
],
[ [
'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class, 'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class,
'class' => \Chill\MainBundle\Entity\AddressReference::class, 'class' => \Chill\MainBundle\Entity\AddressReference::class,

View File

@@ -0,0 +1,56 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Doctrine\Model;
use Chill\MainBundle\Entity\User;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
trait TrackCreationTrait
{
/**
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": NULL})
*/
private ?DateTimeImmutable $createdAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?User $createdBy = null;
public function getCreatedAt(): ?DateTimeInterface
{
return $this->createdAt;
}
public function getCreatedBy(): ?User
{
return $this->createdBy;
}
public function setCreatedAt(DateTimeInterface $datetime): self
{
$this->createdAt = $datetime instanceof DateTime ? DateTimeImmutable::createFromMutable($datetime) : $datetime;
return $this;
}
public function setCreatedBy(User $user): self
{
$this->createdBy = $user;
return $this;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Doctrine\Model;
use Chill\MainBundle\Entity\User;
use DateTime;
use DateTimeImmutable;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
trait TrackUpdateTrait
{
/**
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": NULL})
*/
private ?DateTimeImmutable $updatedAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?User $updatedBy = null;
public function getUpdatedAt(): ?DateTimeInterface
{
return $this->updatedAt;
}
public function getUpdatedBy(): ?User
{
return $this->updatedBy;
}
public function setUpdatedAt(DateTimeInterface $datetime): self
{
$this->updatedAt = $datetime instanceof DateTime ? DateTimeImmutable::createFromMutable($datetime) : $datetime;
return $this;
}
public function setUpdatedBy(User $user): self
{
$this->updatedBy = $user;
return $this;
}
}

View File

@@ -42,10 +42,16 @@ class Address
*/ */
private $buildingName; private $buildingName;
/**
* @ORM\Column(type="boolean")
* @Groups({"write"})
*/
private bool $confidential = false;
/** /**
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Groups({"write"}) * @Groups({"write"})
*/ */
private $corridor; private $corridor;
@@ -78,7 +84,7 @@ class Address
/** /**
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Groups({"write"}) * @Groups({"write"})
*/ */
private $flat; private $flat;
@@ -86,7 +92,7 @@ class Address
/** /**
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Groups({"write"}) * @Groups({"write"})
*/ */
private $floor; private $floor;
@@ -143,7 +149,7 @@ class Address
/** /**
* @var string|null * @var string|null
* *
* @ORM\Column(type="string", length=16, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Groups({"write"}) * @Groups({"write"})
*/ */
private $steps; private $steps;
@@ -192,6 +198,7 @@ class Address
return (new Address()) return (new Address())
->setAddressReference($original->getAddressReference()) ->setAddressReference($original->getAddressReference())
->setBuildingName($original->getBuildingName()) ->setBuildingName($original->getBuildingName())
->setConfidential($original->getConfidential())
->setCorridor($original->getCorridor()) ->setCorridor($original->getCorridor())
->setCustoms($original->getCustoms()) ->setCustoms($original->getCustoms())
->setDistribution($original->getDistribution()) ->setDistribution($original->getDistribution())
@@ -229,6 +236,11 @@ class Address
return $this->buildingName; return $this->buildingName;
} }
public function getConfidential(): bool
{
return $this->confidential;
}
public function getCorridor(): ?string public function getCorridor(): ?string
{ {
return $this->corridor; return $this->corridor;
@@ -369,6 +381,13 @@ class Address
return $this; return $this;
} }
public function setConfidential(bool $confidential): self
{
$this->confidential = $confidential;
return $this;
}
public function setCorridor(?string $corridor): self public function setCorridor(?string $corridor): self
{ {
$this->corridor = $corridor; $this->corridor = $corridor;

View File

@@ -20,10 +20,9 @@ use Doctrine\ORM\Mapping as ORM;
class CommentEmbeddable class CommentEmbeddable
{ {
/** /**
* @var string
* @ORM\Column(type="text", nullable=true) * @ORM\Column(type="text", nullable=true)
*/ */
private $comment; private ?string $comment = null;
/** /**
* @var DateTime * @var DateTime
@@ -39,10 +38,7 @@ class CommentEmbeddable
*/ */
private $userId; private $userId;
/** public function getComment(): ?string
* @return string
*/
public function getComment()
{ {
return $this->comment; return $this->comment;
} }
@@ -68,9 +64,6 @@ class CommentEmbeddable
return empty($this->getComment()); return empty($this->getComment());
} }
/**
* @param string $comment
*/
public function setComment(?string $comment) public function setComment(?string $comment)
{ {
$this->comment = $comment; $this->comment = $comment;

View File

@@ -64,7 +64,7 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
/** /**
* @ORM\Column(type="string", length=255, nullable=true) * @ORM\Column(type="string", length=255, nullable=true)
* @Serializer\Groups({"read", "write"}) * @Serializer\Groups({"read", "write", "docgen:read"})
*/ */
private ?string $email = null; private ?string $email = null;

View File

@@ -67,6 +67,12 @@ class LocationType
*/ */
private ?string $defaultFor = null; private ?string $defaultFor = null;
/**
* @ORM\Column(type="boolean")
* @Serializer\Groups({"read"})
*/
private bool $editableByUsers = true;
/** /**
* @ORM\Id * @ORM\Id
* @ORM\GeneratedValue * @ORM\GeneratedValue
@@ -107,6 +113,11 @@ class LocationType
return $this->defaultFor; return $this->defaultFor;
} }
public function getEditableByUsers(): ?bool
{
return $this->editableByUsers;
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@@ -152,6 +163,13 @@ class LocationType
return $this; return $this;
} }
public function setEditableByUsers(bool $editableByUsers): self
{
$this->editableByUsers = $editableByUsers;
return $this;
}
public function setTitle(array $title): self public function setTitle(array $title): self
{ {
$this->title = $title; $this->title = $title;

View File

@@ -17,11 +17,15 @@ use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/** /**
* @ORM\Entity * @ORM\Entity
* @ORM\Table( * @ORM\Table(
* name="chill_main_notification", * name="chill_main_notification",
* indexes={
* @ORM\Index(name="chill_main_notification_related_entity_idx", columns={"relatedentityclass", "relatedentityid"})
* }
* ) * )
* @ORM\HasLifecycleCallbacks * @ORM\HasLifecycleCallbacks
*/ */
@@ -32,6 +36,7 @@ class Notification implements TrackUpdateInterface
/** /**
* @ORM\ManyToMany(targetEntity=User::class) * @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_notification_addresses_user") * @ORM\JoinTable(name="chill_main_notification_addresses_user")
* @Assert\Count(min="1", minMessage="notification.At least one addressee")
*/ */
private Collection $addressees; private Collection $addressees;
@@ -80,6 +85,7 @@ class Notification implements TrackUpdateInterface
/** /**
* @ORM\Column(type="text", options={"default": ""}) * @ORM\Column(type="text", options={"default": ""})
* @Assert\NotBlank(message="notification.Title must be defined")
*/ */
private string $title = ''; private string $title = '';
@@ -286,9 +292,9 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
public function setMessage(string $message): self public function setMessage(?string $message): self
{ {
$this->message = $message; $this->message = (string) $message;
return $this; return $this;
} }
@@ -314,9 +320,9 @@ class Notification implements TrackUpdateInterface
return $this; return $this;
} }
public function setTitle(string $title): Notification public function setTitle(?string $title): Notification
{ {
$this->title = $title; $this->title = (string) $title;
return $this; return $this;
} }

View File

@@ -0,0 +1,166 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=ResidentialAddressRepository::class)
* @ORM\Table(name="chill_main_residential_address")
*/
class ResidentialAddress
{
/**
* @ORM\ManyToOne(targetEntity=Address::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?Address $address = null;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="residentialAddressComment_")
*/
private CommentEmbeddable $comment;
/**
* @ORM\Column(type="datetime_immutable", nullable=true)
*/
private ?DateTimeImmutable $endDate = null;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?Person $hostPerson = null;
/**
* @ORM\ManyToOne(targetEntity=ThirdParty::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?ThirdParty $hostThirdParty = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=false)
*/
private Person $person;
/**
* @ORM\Column(type="datetime_immutable")
*/
private ?DateTimeImmutable $startDate = null;
public function __construct()
{
$this->comment = new CommentEmbeddable();
}
public function getAddress(): ?Address
{
return $this->address;
}
public function getComment(): CommentEmbeddable
{
return $this->comment;
}
public function getEndDate(): ?DateTimeImmutable
{
return $this->endDate;
}
public function getHostPerson(): ?Person
{
return $this->hostPerson;
}
public function getHostThirdParty(): ?ThirdParty
{
return $this->hostThirdParty;
}
public function getId(): ?int
{
return $this->id;
}
public function getPerson(): ?Person
{
return $this->person;
}
public function getStartDate(): ?DateTimeImmutable
{
return $this->startDate;
}
public function setAddress(?Address $address): self
{
$this->address = $address;
return $this;
}
public function setComment(CommentEmbeddable $comment): self
{
$this->comment = $comment;
return $this;
}
public function setEndDate(?DateTimeImmutable $endDate): self
{
$this->endDate = $endDate;
return $this;
}
public function setHostPerson(?Person $hostPerson): self
{
$this->hostPerson = $hostPerson;
return $this;
}
public function setHostThirdParty(?ThirdParty $hostThirdParty): self
{
$this->hostThirdParty = $hostThirdParty;
return $this;
}
public function setPerson(?Person $person): self
{
$this->person = $person;
return $this;
}
public function setStartDate(DateTimeImmutable $startDate): self
{
$this->startDate = $startDate;
return $this;
}
}

View File

@@ -97,6 +97,11 @@ class User implements AdvancedUserInterface
*/ */
private ?Center $mainCenter = null; private ?Center $mainCenter = null;
/**
* @ORM\ManyToOne(targetEntity=Location::class)
*/
private ?Location $mainLocation = null;
/** /**
* @ORM\ManyToOne(targetEntity=Scope::class) * @ORM\ManyToOne(targetEntity=Scope::class)
*/ */
@@ -228,6 +233,11 @@ class User implements AdvancedUserInterface
return $this->mainCenter; return $this->mainCenter;
} }
public function getMainLocation(): ?Location
{
return $this->mainLocation;
}
public function getMainScope(): ?Scope public function getMainScope(): ?Scope
{ {
return $this->mainScope; return $this->mainScope;
@@ -405,6 +415,13 @@ class User implements AdvancedUserInterface
return $this; return $this;
} }
public function setMainLocation(?Location $mainLocation): User
{
$this->mainLocation = $mainLocation;
return $this;
}
public function setMainScope(?Scope $mainScope): User public function setMainScope(?Scope $mainScope): User
{ {
$this->mainScope = $mainScope; $this->mainScope = $mainScope;

View File

@@ -0,0 +1,461 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity\Workflow;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Iterator;
use RuntimeException;
use Symfony\Component\Serializer\Annotation as Serializer;
use function count;
use function is_array;
/**
* @ORM\Entity
* @ORM\Table("chill_main_workflow_entity")
* @EntityWorkflowCreation(groups={"creation"})
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "entity_workflow": EntityWorkflow::class
* })
*/
class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* @ORM\OneToMany(targetEntity=EntityWorkflowComment::class, mappedBy="entityWorkflow", orphanRemoval=true)
*
* @var Collection|EntityWorkflowComment[]
*/
private Collection $comments;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255)
*/
private string $relatedEntityClass = '';
/**
* @ORM\Column(type="integer")
*/
private int $relatedEntityId;
/**
* @ORM\OneToMany(targetEntity=EntityWorkflowStep::class, mappedBy="entityWorkflow", orphanRemoval=true, cascade={"persist"})
* @ORM\OrderBy({"transitionAt": "ASC", "id": "ASC"})
*
* @var Collection|EntityWorkflowStep[]
*/
private Collection $steps;
/**
* @var null|array|EntityWorkflowStep[]
*/
private ?array $stepsChainedCache = null;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_final")
*
* @var Collection|User[]
*/
private Collection $subscriberToFinal;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_step")
*
* @var Collection|User[]
*/
private Collection $subscriberToStep;
/**
* a step which will store all the transition data.
*/
private ?EntityWorkflowStep $transitionningStep = null;
/**
* @ORM\Column(type="text")
*/
private string $workflowName;
public function __construct()
{
$this->subscriberToFinal = new ArrayCollection();
$this->subscriberToStep = new ArrayCollection();
$this->comments = new ArrayCollection();
$this->steps = new ArrayCollection();
$initialStep = new EntityWorkflowStep();
$initialStep
->setCurrentStep('initial');
$this->addStep($initialStep);
}
public function addComment(EntityWorkflowComment $comment): self
{
if (!$this->comments->contains($comment)) {
$this->comments[] = $comment;
$comment->setEntityWorkflow($this);
}
return $this;
}
/**
* @internal You should prepare a step and run a workflow transition instead of manually adding a step
*/
public function addStep(EntityWorkflowStep $step): self
{
if (!$this->steps->contains($step)) {
$this->steps[] = $step;
$step->setEntityWorkflow($this);
}
return $this;
}
public function addSubscriberToFinal(User $user): self
{
if (!$this->subscriberToFinal->contains($user)) {
$this->subscriberToFinal[] = $user;
}
return $this;
}
public function addSubscriberToStep(User $user): self
{
if (!$this->subscriberToStep->contains($user)) {
$this->subscriberToStep[] = $user;
}
return $this;
}
public function getComments(): Collection
{
return $this->comments;
}
public function getCurrentStep(): ?EntityWorkflowStep
{
$step = $this->steps->last();
if (false !== $step) {
return $step;
}
return null;
}
public function getCurrentStepChained(): ?EntityWorkflowStep
{
$steps = $this->getStepsChained();
$currentStep = $this->getCurrentStep();
foreach ($steps as $step) {
if ($step === $currentStep) {
return $step;
}
}
return null;
}
public function getCurrentStepCreatedAt(): ?DateTimeInterface
{
if (null !== $previous = $this->getPreviousStepIfAny()) {
return $previous->getTransitionAt();
}
return null;
}
public function getCurrentStepCreatedBy(): ?User
{
if (null !== $previous = $this->getPreviousStepIfAny()) {
return $previous->getTransitionBy();
}
return null;
}
public function getId(): ?int
{
return $this->id;
}
public function getRelatedEntityClass(): string
{
return $this->relatedEntityClass;
}
public function getRelatedEntityId(): int
{
return $this->relatedEntityId;
}
/**
* Method used by MarkingStore.
*
* get a string representation of the step
*/
public function getStep(): string
{
return $this->getCurrentStep()->getCurrentStep();
}
public function getStepAfter(EntityWorkflowStep $step): ?EntityWorkflowStep
{
$iterator = $this->steps->getIterator();
if ($iterator instanceof Iterator) {
$iterator->rewind();
while ($iterator->valid()) {
$curStep = $iterator->current();
if ($curStep === $step) {
$iterator->next();
if ($iterator->valid()) {
return $iterator->current();
}
return null;
}
$iterator->next();
}
return null;
}
throw new RuntimeException();
}
/**
* @return ArrayCollection|Collection
*/
public function getSteps()
{
return $this->steps;
}
public function getStepsChained(): array
{
if (is_array($this->stepsChainedCache)) {
return $this->stepsChainedCache;
}
$iterator = $this->steps->getIterator();
$current = null;
$steps = [];
$iterator->rewind();
do {
$previous = $current;
$current = $iterator->current();
$steps[] = $current;
$current->setPrevious($previous);
$iterator->next();
if ($iterator->valid()) {
$current->setNext($iterator->current());
} else {
$current->setNext(null);
}
} while ($iterator->valid());
$this->stepsChainedCache = $steps;
return $steps;
}
/**
* @return ArrayCollection|Collection
*/
public function getSubscriberToFinal()
{
return $this->subscriberToFinal;
}
/**
* @return ArrayCollection|Collection
*/
public function getSubscriberToStep()
{
return $this->subscriberToStep;
}
/**
* get the step which is transitionning. Should be called only by event which will
* concern the transition.
*/
public function getTransitionningStep(): ?EntityWorkflowStep
{
return $this->transitionningStep;
}
public function getWorkflowName(): string
{
return $this->workflowName;
}
public function isFinal(): bool
{
$steps = $this->getStepsChained();
if (1 === count($steps)) {
// the initial step cannot be finalized
return false;
}
/** @var EntityWorkflowStep $last */
$last = end($steps);
return $last->isFinal();
}
public function isFreeze(): bool
{
$steps = $this->getStepsChained();
if (1 === count($steps)) {
// the initial step cannot be finalized
return false;
}
/** @var EntityWorkflowStep $last */
$last = end($steps);
return $last->getPrevious()->isFreezeAfter();
}
public function isUserSubscribedToFinal(User $user): bool
{
return $this->subscriberToFinal->contains($user);
}
public function isUserSubscribedToStep(User $user): bool
{
return $this->subscriberToStep->contains($user);
}
public function prepareStepBeforeTransition(EntityWorkflowStep $step): self
{
$this->transitionningStep = $step;
return $this;
}
public function removeComment(EntityWorkflowComment $comment): self
{
if ($this->comments->removeElement($comment)) {
$comment->setEntityWorkflow(null);
}
return $this;
}
public function removeStep(EntityWorkflowStep $step): self
{
if ($this->steps->removeElement($step)) {
$step->setEntityWorkflow(null);
}
return $this;
}
public function removeSubscriberToFinal(User $user): self
{
$this->subscriberToFinal->removeElement($user);
return $this;
}
public function removeSubscriberToStep(User $user): self
{
$this->subscriberToStep->removeElement($user);
return $this;
}
public function setRelatedEntityClass(string $relatedEntityClass): EntityWorkflow
{
$this->relatedEntityClass = $relatedEntityClass;
return $this;
}
public function setRelatedEntityId(int $relatedEntityId): EntityWorkflow
{
$this->relatedEntityId = $relatedEntityId;
return $this;
}
/**
* Method use by marking store.
*
* @return $this
*/
public function setStep(string $step): self
{
$newStep = new EntityWorkflowStep();
$newStep->setCurrentStep($step);
// copy the freeze
if ($this->getCurrentStep()->isFreezeAfter()) {
$newStep->setFreezeAfter(true);
}
$this->addStep($newStep);
return $this;
}
public function setWorkflowName(string $workflowName): EntityWorkflow
{
$this->workflowName = $workflowName;
return $this;
}
private function getPreviousStepIfAny(): ?EntityWorkflowStep
{
if (1 === count($this->steps)) {
return null;
}
return $this->steps->get($this->steps->count() - 2);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity\Workflow;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("chill_main_workflow_entity_comment")
*/
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* @ORM\Column(type="text", options={"default": ""})
*/
private string $comment = '';
/**
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="comments")
*/
private ?EntityWorkflow $entityWorkflow = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
public function getComment(): string
{
return $this->comment;
}
public function getEntityWorkflow(): ?EntityWorkflow
{
return $this->entityWorkflow;
}
public function getId(): ?int
{
return $this->id;
}
public function setComment(string $comment): self
{
$this->comment = $comment;
return $this;
}
/**
* @internal use @see{EntityWorkflow::addComment}
*/
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): self
{
$this->entityWorkflow = $entityWorkflow;
return $this;
}
}

View File

@@ -0,0 +1,336 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity\Workflow;
use Chill\MainBundle\Entity\User;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
use function in_array;
/**
* @ORM\Entity
* @ORM\Table("chill_main_workflow_entity_step")
*/
class EntityWorkflowStep
{
/**
* @ORM\Column(type="text", options={"default": ""})
*/
private string $comment = '';
/**
* @ORM\Column(type="text")
*/
private ?string $currentStep = '';
/**
* @ORM\Column(type="json")
*/
private array $destEmail = [];
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user")
*/
private Collection $destUser;
/**
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
*/
private ?EntityWorkflow $entityWorkflow = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private bool $freezeAfter = false;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private bool $isFinal = false;
/**
* filled by @see{EntityWorkflow::getStepsChained}.
*/
private ?EntityWorkflowStep $next = null;
/**
* filled by @see{EntityWorkflow::getStepsChained}.
*/
private ?EntityWorkflowStep $previous = null;
/**
* @ORM\Column(type="text", nullable=true, options={"default": null})
*/
private ?string $transitionAfter = null;
/**
* @ORM\Column(type="datetime_immutable")
*/
private ?DateTimeImmutable $transitionAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?User $transitionBy = null;
/**
* @ORM\Column(type="text", nullable=true)
*/
private ?string $transitionByEmail = null;
public function __construct()
{
$this->destUser = new ArrayCollection();
}
public function addDestEmail(string $email): self
{
if (!in_array($email, $this->destEmail, true)) {
$this->destEmail[] = $email;
}
return $this;
}
public function addDestUser(User $user): self
{
if (!$this->destUser->contains($user)) {
$this->destUser[] = $user;
}
return $this;
}
public function getComment(): string
{
return $this->comment;
}
public function getCurrentStep(): ?string
{
return $this->currentStep;
}
public function getDestEmail(): array
{
return $this->destEmail;
}
/**
* @return ArrayCollection|Collection
*/
public function getDestUser()
{
return $this->destUser;
}
public function getEntityWorkflow(): ?EntityWorkflow
{
return $this->entityWorkflow;
}
public function getId(): ?int
{
return $this->id;
}
public function getNext(): ?EntityWorkflowStep
{
return $this->next;
}
public function getPrevious(): ?EntityWorkflowStep
{
return $this->previous;
}
public function getTransitionAfter(): ?string
{
return $this->transitionAfter;
}
public function getTransitionAt(): ?DateTimeImmutable
{
return $this->transitionAt;
}
public function getTransitionBy(): ?User
{
return $this->transitionBy;
}
public function getTransitionByEmail(): ?string
{
return $this->transitionByEmail;
}
public function isFinal(): bool
{
return $this->isFinal;
}
public function isFreezeAfter(): bool
{
return $this->freezeAfter;
}
public function removeDestEmail(string $email): self
{
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
return $email !== $existing;
});
return $this;
}
public function removeDestUser(User $user): self
{
$this->destUser->removeElement($user);
return $this;
}
public function setComment(?string $comment): EntityWorkflowStep
{
$this->comment = (string) $comment;
return $this;
}
public function setCurrentStep(?string $currentStep): EntityWorkflowStep
{
$this->currentStep = $currentStep;
return $this;
}
public function setDestEmail(array $destEmail): EntityWorkflowStep
{
$this->destEmail = $destEmail;
return $this;
}
/**
* @internal use @see(EntityWorkflow::addStep} instead
*/
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): EntityWorkflowStep
{
$this->entityWorkflow = $entityWorkflow;
return $this;
}
public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep
{
$this->freezeAfter = $freezeAfter;
return $this;
}
public function setIsFinal(bool $isFinal): EntityWorkflowStep
{
$this->isFinal = $isFinal;
return $this;
}
/**
* @return EntityWorkflowStep
*
* @internal
*/
public function setNext(?EntityWorkflowStep $next): self
{
$this->next = $next;
return $this;
}
/**
* @return EntityWorkflowStep
*
* @internal
*/
public function setPrevious(?EntityWorkflowStep $previous): self
{
$this->previous = $previous;
return $this;
}
public function setTransitionAfter(?string $transitionAfter): EntityWorkflowStep
{
$this->transitionAfter = $transitionAfter;
return $this;
}
public function setTransitionAt(?DateTimeImmutable $transitionAt): EntityWorkflowStep
{
$this->transitionAt = $transitionAt;
return $this;
}
public function setTransitionBy(?User $transitionBy): EntityWorkflowStep
{
$this->transitionBy = $transitionBy;
return $this;
}
public function setTransitionByEmail(?string $transitionByEmail): EntityWorkflowStep
{
$this->transitionByEmail = $transitionByEmail;
return $this;
}
/**
* @Assert\Callback
*
* @param mixed $payload
*/
public function validateOnCreation(ExecutionContextInterface $context, $payload): void
{
return;
if ($this->isFinalizeAfter()) {
if (0 !== count($this->getDestUser())) {
$context->buildViolation('workflow.No dest users when the workflow is finalized')
->atPath('finalizeAfter')
->addViolation();
}
} else {
if (0 === count($this->getDestUser())) {
$context->buildViolation('workflow.The next step must count at least one dest')
->atPath('finalizeAfter')
->addViolation();
}
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Form\Type\ChillTextareaType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class EntityWorkflowCommentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('comment', ChillTextareaType::class, [
'required' => false,
]);
}
}

View File

@@ -54,7 +54,6 @@ final class LocationFormType extends AbstractType
'label' => 'Address', 'label' => 'Address',
'use_valid_from' => false, 'use_valid_from' => false,
'use_valid_to' => false, 'use_valid_to' => false,
'mapped' => false,
]) ])
->add( ->add(
'active', 'active',

View File

@@ -39,6 +39,17 @@ final class LocationTypeType extends AbstractType
'expanded' => true, 'expanded' => true,
] ]
) )
->add(
'editableByUsers',
ChoiceType::class,
[
'choices' => [
'Yes' => true,
'No' => false,
],
'expanded' => true,
]
)
->add( ->add(
'addressRequired', 'addressRequired',
ChoiceType::class, ChoiceType::class,

View File

@@ -12,14 +12,18 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type\DataTransformer; namespace Chill\MainBundle\Form\Type\DataTransformer;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
use UnexpectedValueException;
use function array_key_exists; use function array_key_exists;
class UserToJsonTransformer implements DataTransformerInterface class EntityToJsonTransformer implements DataTransformerInterface
{ {
private DenormalizerInterface $denormalizer; private DenormalizerInterface $denormalizer;
@@ -27,23 +31,36 @@ class UserToJsonTransformer implements DataTransformerInterface
private SerializerInterface $serializer; private SerializerInterface $serializer;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple) private string $type;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, bool $multiple, string $type)
{ {
$this->denormalizer = $denormalizer; $this->denormalizer = $denormalizer;
$this->serializer = $serializer; $this->serializer = $serializer;
$this->multiple = $multiple; $this->multiple = $multiple;
$this->type = $type;
} }
public function reverseTransform($value) public function reverseTransform($value)
{ {
$denormalized = json_decode($value, true);
if ($this->multiple) { if ($this->multiple) {
if (null === $denormalized) {
return [];
}
return array_map( return array_map(
function ($item) { return $this->denormalizeOne($item); }, function ($item) { return $this->denormalizeOne($item); },
json_decode($value, true) $denormalized
); );
} }
return $this->denormalizeOne(json_decode($value, true)); if ('' === $value) {
return null;
}
return $this->denormalizeOne($denormalized);
} }
/** /**
@@ -60,7 +77,7 @@ class UserToJsonTransformer implements DataTransformerInterface
]); ]);
} }
private function denormalizeOne(array $item): User private function denormalizeOne(array $item)
{ {
if (!array_key_exists('type', $item)) { if (!array_key_exists('type', $item)) {
throw new TransformationFailedException('the key "type" is missing on element'); throw new TransformationFailedException('the key "type" is missing on element');
@@ -70,10 +87,30 @@ class UserToJsonTransformer implements DataTransformerInterface
throw new TransformationFailedException('the key "id" is missing on element'); throw new TransformationFailedException('the key "id" is missing on element');
} }
switch ($this->type) {
case 'user':
$class = User::class;
break;
case 'person':
$class = Person::class;
break;
case 'thirdparty':
$class = ThirdParty::class;
break;
default:
throw new UnexpectedValueException('This type is not supported');
}
return return
$this->denormalizer->denormalize( $this->denormalizer->denormalize(
['type' => $item['type'], 'id' => $item['id']], ['type' => $item['type'], 'id' => $item['id']],
User::class, $class,
'json', 'json',
[AbstractNormalizer::GROUPS => ['read']], [AbstractNormalizer::GROUPS => ['read']],
); );

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form\Type; namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\DataTransformer\UserToJsonTransformer; use Chill\MainBundle\Form\Type\DataTransformer\EntityToJsonTransformer;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
@@ -38,7 +38,7 @@ class PickUserDynamicType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder->addViewTransformer(new UserToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'])); $builder->addViewTransformer(new EntityToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'], 'user'));
} }
public function buildView(FormView $view, FormInterface $form, array $options) public function buildView(FormView $view, FormInterface $form, array $options)
@@ -58,6 +58,6 @@ class PickUserDynamicType extends AbstractType
public function getBlockPrefix() public function getBlockPrefix()
{ {
return 'pick_user_dynamic'; return 'pick_entity_dynamic';
} }
} }

View File

@@ -0,0 +1,73 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\ResidentialAddress;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
final class ResidentialAddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('startDate', DateType::class, [
'required' => true,
'input' => 'datetime_immutable',
'widget' => 'single_text',
])
->add('endDate', DateType::class, [
'required' => false,
'input' => 'datetime_immutable',
'widget' => 'single_text',
])
->add('comment', CommentType::class, [
'required' => false,
]);
if ('person' === $options['kind']) {
$builder
->add('hostPerson', PickPersonDynamicType::class, [
'label' => 'Person',
]);
}
if ('thirdparty' === $options['kind']) {
$builder
->add('hostThirdParty', PickThirdpartyDynamicType::class, [
'label' => 'Third party',
]);
}
if ('address' === $options['kind']) {
$builder
->add('address', PickAddressType::class, [
'required' => false,
'label' => 'Address',
'use_valid_from' => false,
'use_valid_to' => false,
]);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ResidentialAddress::class,
'kind' => null,
]);
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form; namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
@@ -75,6 +76,22 @@ class UserType extends AbstractType
'choice_label' => function (UserJob $c) { 'choice_label' => function (UserJob $c) {
return $this->translatableStringHelper->localize($c->getLabel()); return $this->translatableStringHelper->localize($c->getLabel());
}, },
])
->add('mainLocation', EntityType::class, [
'label' => 'Main location',
'required' => false,
'placeholder' => 'choose a location',
'class' => Location::class,
'choice_label' => function (Location $l) {
return $this->translatableStringHelper->localize($l->getLocationType()->getTitle()) . ' - ' . $l->getName();
},
'query_builder' => static function (EntityRepository $er) {
$qb = $er->createQueryBuilder('l');
$qb->orderBy('l.locationType');
$qb->where('l.availableForUsers = TRUE');
return $qb;
},
]); ]);
if ($options['is_creation']) { if ($options['is_creation']) {

View File

@@ -0,0 +1,180 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use LogicException;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\Transition;
use function array_key_exists;
class WorkflowStepType extends AbstractType
{
private EntityWorkflowManager $entityWorkflowManager;
private Registry $registry;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->entityWorkflowManager = $entityWorkflowManager;
$this->registry = $registry;
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var \Chill\MainBundle\Entity\Workflow\EntityWorkflow $entityWorkflow */
$entityWorkflow = $options['entity_workflow'];
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$place = $workflow->getMarking($entityWorkflow);
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata(array_keys($place->getPlaces())[0]);
if (true === $options['transition']) {
if (null === $options['entity_workflow']) {
throw new LogicException('if transition is true, entity_workflow should be defined');
}
$transitions = $this->registry
->get($options['entity_workflow'], $entityWorkflow->getWorkflowName())
->getEnabledTransitions($entityWorkflow);
$choices = array_combine(
array_map(
static function (Transition $transition) {
return $transition->getName();
},
$transitions
),
$transitions
);
if (array_key_exists('validationFilterInputLabels', $placeMetadata)) {
$inputLabels = $placeMetadata['validationFilterInputLabels'];
$builder->add('transitionFilter', ChoiceType::class, [
'multiple' => false,
'label' => 'workflow.My decision',
'choices' => [
'forward' => 'forward',
'backward' => 'backward',
'neutral' => 'neutral',
],
'choice_label' => function (string $key) use ($inputLabels) {
return $this->translatableStringHelper->localize($inputLabels[$key]);
},
'choice_attr' => static function (string $key) {
return [
$key => $key,
];
},
'mapped' => false,
'expanded' => true,
'data' => 'forward',
]);
}
$builder
->add('transition', ChoiceType::class, [
'label' => 'workflow.Next step',
'mapped' => false,
'multiple' => false,
'expanded' => true,
'choices' => $choices,
'choice_label' => function (Transition $transition) use ($workflow) {
$meta = $workflow->getMetadataStore()->getTransitionMetadata($transition);
if (array_key_exists('label', $meta)) {
return $this->translatableStringHelper->localize($meta['label']);
}
return $transition->getName();
},
'choice_attr' => static function (Transition $transition) use ($workflow) {
$toFinal = true;
$isForward = 'neutral';
$metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
if (array_key_exists('isForward', $metadata)) {
if ($metadata['isForward']) {
$isForward = 'forward';
} else {
$isForward = 'backward';
}
}
foreach ($transition->getTos() as $to) {
$meta = $workflow->getMetadataStore()->getPlaceMetadata($to);
if (
!array_key_exists('isFinal', $meta) || false === $meta['isFinal']
) {
$toFinal = false;
}
}
return [
'data-is-transition' => 'data-is-transition',
'data-to-final' => $toFinal ? '1' : '0',
'data-is-forward' => $isForward,
];
},
])
->add('future_dest_users', PickUserDynamicType::class, [
'label' => 'workflow.dest for next steps',
'multiple' => true,
'mapped' => false,
]);
}
if (
$handler->supportsFreeze($entityWorkflow)
&& !$entityWorkflow->isFreeze()
) {
$builder
->add('freezeAfter', CheckboxType::class, [
'required' => false,
'label' => 'workflow.Freeze',
'help' => 'workflow.The associated element will be freezed',
]);
}
$builder
->add('comment', ChillTextareaType::class, [
'required' => false,
'label' => 'Comment',
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefined('class', EntityWorkflowStep::class)
->setRequired('transition')
->setAllowedTypes('transition', 'bool')
->setRequired('entity_workflow')
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class WorkflowTransitionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('current_step', WorkflowStepType::class, [
'transition' => true,
'entity_workflow' => $options['entity_workflow'],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setRequired('entity_workflow')
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
}
}

View File

@@ -50,7 +50,7 @@ class NotificationMailer
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email
->to($dest->getEmail()) ->to($dest->getEmail())
->subject('Re: [Chill] ' . $comment->getNotification()->getTitle()) ->subject('Re: ' . $comment->getNotification()->getTitle())
->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig') ->textTemplate('@ChillMain/Notification/email_notification_comment_persist.fr.md.twig')
->context([ ->context([
'comment' => $comment, 'comment' => $comment,
@@ -79,11 +79,13 @@ class NotificationMailer
continue; continue;
} }
$email = new Email();
$email
->subject($notification->getTitle());
if ($notification->isSystem()) { if ($notification->isSystem()) {
$email = new Email();
$email $email
->text($notification->getMessage()) ->text($notification->getMessage());
->subject('[Chill] ' . $notification->getTitle());
} else { } else {
$email = new TemplatedEmail(); $email = new TemplatedEmail();
$email $email

View File

@@ -15,12 +15,15 @@ use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\NotificationRepository; use Chill\MainBundle\Repository\NotificationRepository;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use function array_key_exists;
/** /**
* Helps to find if a notification exist for a given entity. * Helps to find if a notification exist for a given entity.
*/ */
class NotificationPresence class NotificationPresence
{ {
private array $cache = [];
private NotificationRepository $notificationRepository; private NotificationRepository $notificationRepository;
private Security $security; private Security $security;
@@ -31,6 +34,29 @@ class NotificationPresence
$this->notificationRepository = $notificationRepository; $this->notificationRepository = $notificationRepository;
} }
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array
{
if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
return $this->cache[$relatedEntityClass][$relatedEntityId];
}
$user = $this->security->getUser();
if ($user instanceof User) {
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass,
$relatedEntityId,
$user
);
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
return $counter;
}
return ['unread' => 0, 'read' => 0];
}
/** /**
* @return array|Notification[] * @return array|Notification[]
*/ */

View File

@@ -23,6 +23,13 @@ class NotificationTwigExtension extends AbstractExtension
'needs_environment' => true, 'needs_environment' => true,
'is_safe' => ['html'], 'is_safe' => ['html'],
]), ]),
new TwigFunction('chill_count_notifications', [NotificationTwigExtensionRuntime::class, 'countNotificationsFor'], [
'is_safe' => [],
]),
new TwigFunction('chill_counter_notifications', [NotificationTwigExtensionRuntime::class, 'counterNotificationFor'], [
'needs_environment' => true,
'is_safe' => ['html'],
]),
]; ];
} }
} }

View File

@@ -11,17 +11,42 @@ declare(strict_types=1);
namespace Chill\MainBundle\Notification\Templating; namespace Chill\MainBundle\Notification\Templating;
use Chill\MainBundle\Entity\NotificationComment;
use Chill\MainBundle\Form\NotificationCommentType;
use Chill\MainBundle\Notification\NotificationPresence; use Chill\MainBundle\Notification\NotificationPresence;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment; use Twig\Environment;
use Twig\Extension\RuntimeExtensionInterface; use Twig\Extension\RuntimeExtensionInterface;
class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
{ {
private FormFactoryInterface $formFactory;
private NotificationPresence $notificationPresence; private NotificationPresence $notificationPresence;
public function __construct(NotificationPresence $notificationPresence) private UrlGeneratorInterface $urlGenerator;
public function __construct(FormFactoryInterface $formFactory, NotificationPresence $notificationPresence, UrlGeneratorInterface $urlGenerator)
{ {
$this->formFactory = $formFactory;
$this->notificationPresence = $notificationPresence; $this->notificationPresence = $notificationPresence;
$this->urlGenerator = $urlGenerator;
}
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
{
return $environment->render(
'@ChillMain/Notification/extension_counter_notifications_for.html.twig',
[
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId),
]
);
}
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array
{
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
} }
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
@@ -32,8 +57,24 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
return ''; return '';
} }
$appendCommentForms = [];
foreach ($notifications as $notification) {
$appendComment = new NotificationComment();
$appendCommentForms[$notification->getId()] = $this->formFactory->create(
NotificationCommentType::class,
$appendComment,
[
'action' => $this->urlGenerator->generate(
'chill_main_notification_show',
['id' => $notification->getId()]
),
]
)->createView();
}
return $environment->render('@ChillMain/Notification/extension_list_notifications_for.html.twig', [ return $environment->render('@ChillMain/Notification/extension_list_notifications_for.html.twig', [
'notifications' => $notifications, 'notifications' => $notifications, 'appendCommentForms' => $appendCommentForms,
]); ]);
} }
} }

View File

@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Notification; use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Doctrine\DBAL\Statement;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
@@ -24,6 +25,8 @@ final class NotificationRepository implements ObjectRepository
{ {
private EntityManagerInterface $em; private EntityManagerInterface $em;
private ?Statement $notificationByRelatedEntityAndUserAssociatedStatement = null;
private EntityRepository $repository; private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager) public function __construct(EntityManagerInterface $entityManager)
@@ -48,6 +51,30 @@ final class NotificationRepository implements ObjectRepository
->getSingleScalarResult(); ->getSingleScalarResult();
} }
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
{
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
$sql =
'SELECT
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = 1812 and cmnau.notification_id = cmn.id))::int) AS unread,
SUM((cmn.sender_id = 1812)::int) AS sent,
COUNT(cmn.*) AS total
FROM chill_main_notification cmn
WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
$this->notificationByRelatedEntityAndUserAssociatedStatement =
$this->em->getConnection()->prepare($sql);
}
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]);
$result = $results->fetchAssociative();
$results->free();
return $result;
}
public function countUnreadByUser(User $user): int public function countUnreadByUser(User $user): int
{ {
$sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId'; $sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = :userId';
@@ -153,11 +180,52 @@ final class NotificationRepository implements ObjectRepository
* @return array|Notification[] * @return array|Notification[]
*/ */
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
{
return
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user)
->select('n')
->getQuery()
->getResult();
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
{
return $this->repository->findOneBy($criteria, $orderBy);
}
/**
* @return array|Notification[]
*/
public function findUnreadByUser(User $user, int $limit = 20, int $offset = 0): array
{
$rsm = new Query\ResultSetMappingBuilder($this->em);
$rsm->addRootEntityFromClassMetadata(Notification::class, 'cmn');
$sql = 'SELECT ' . $rsm->generateSelectClause(['cmn' => 'cmn']) . ' ' .
'FROM chill_main_notification cmn ' .
'WHERE ' .
'EXISTS (select 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId and cmnau.notification_id = cmn.id) ' .
'ORDER BY cmn.date DESC ' .
'LIMIT :limit OFFSET :offset';
$nq = $this->em->createNativeQuery($sql, $rsm)
->setParameter('userId', $user->getId())
->setParameter('limit', $limit)
->setParameter('offset', $offset);
return $nq->getResult();
}
public function getClassName()
{
return Notification::class;
}
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder
{ {
$qb = $this->repository->createQueryBuilder('n'); $qb = $this->repository->createQueryBuilder('n');
$qb $qb
->select('n')
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass')) ->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'))
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')) ->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId'))
->andWhere($qb->expr()->isNotNull('n.sender')) ->andWhere($qb->expr()->isNotNull('n.sender'))
@@ -171,17 +239,7 @@ final class NotificationRepository implements ObjectRepository
->setParameter('relatedEntityId', $relatedEntityId) ->setParameter('relatedEntityId', $relatedEntityId)
->setParameter('user', $user); ->setParameter('user', $user);
return $qb->getQuery()->getResult(); return $qb;
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?Notification
{
return $this->repository->findOneBy($criteria, $orderBy);
}
public function getClassName()
{
return Notification::class;
} }
private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder

View File

@@ -0,0 +1,59 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\ResidentialAddress;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @method ResidentialAddress|null find($id, $lockMode = null, $lockVersion = null)
* @method ResidentialAddress|null findOneBy(array $criteria, array $orderBy = null)
* @method ResidentialAddress[] findAll()
* @method ResidentialAddress[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ResidentialAddressRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, ResidentialAddress::class);
}
// /**
// * @return ResidentialAddress[] Returns an array of ResidentialAddress objects
// */
/*
public function findByExampleField($value)
{
return $this->createQueryBuilder('r')
->andWhere('r.exampleField = :val')
->setParameter('val', $value)
->orderBy('r.id', 'ASC')
->setMaxResults(10)
->getQuery()
->getResult()
;
}
*/
/*
public function findOneBySomeField($value): ?ResidentialAddress
{
return $this->createQueryBuilder('r')
->andWhere('r.exampleField = :val')
->setParameter('val', $value)
->getQuery()
->getOneOrNullResult()
;
}
*/
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Repository\Workflow;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
class EntityWorkflowRepository implements ObjectRepository
{
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(EntityWorkflow::class);
}
public function countByDest(User $user): int
{
$qb = $this->buildQueryByDest($user)->select('count(ew)');
return (int) $qb->getQuery()->getSingleScalarResult();
}
public function countBySubscriber(User $user): int
{
$qb = $this->buildQueryBySubscriber($user)->select('count(ew)');
return (int) $qb->getQuery()->getSingleScalarResult();
}
public function find($id): ?EntityWorkflow
{
return $this->repository->find($id);
}
/**
* @return array|EntityWorkflow[]
*/
public function findAll(): array
{
return $this->repository->findAll();
}
/**
* @param null|mixed $limit
* @param null|mixed $offset
*
* @return array|EntityWorkflow[]
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findByDest(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
{
$qb = $this->buildQueryByDest($user)->select('ew');
foreach ($orderBy as $key => $sort) {
$qb->addOrderBy('ew.' . $key, $sort);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()->getResult();
}
public function findBySubscriber(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
{
$qb = $this->buildQueryBySubscriber($user)->select('ew');
foreach ($orderBy as $key => $sort) {
$qb->addOrderBy('ew.' . $key, $sort);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()->getResult();
}
public function findOneBy(array $criteria): ?EntityWorkflow
{
return $this->repository->findOneBy($criteria);
}
public function getClassName(): string
{
return EntityWorkflow::class;
}
private function buildQueryByDest(User $user): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('ew');
$qb->join('ew.steps', 'step');
$qb->where(
$qb->expr()->andX(
$qb->expr()->isMemberOf(':user', 'step.destUser'),
$qb->expr()->isNull('step.transitionAfter')
)
);
$qb->setParameter('user', $user);
return $qb;
}
private function buildQueryBySubscriber(User $user): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('ew');
$qb->where(
$qb->expr()->orX(
$qb->expr()->isMemberOf(':user', 'ew.subscriberToStep'),
$qb->expr()->isMemberOf(':user', 'ew.subscriberToFinal'),
)
);
$qb->setParameter('user', $user);
return $qb;
}
}

View File

@@ -1,5 +1,5 @@
// Access to Bootstrap variables and mixins // Access to Bootstrap variables and mixins
@import '~ChillMainAssets/module/bootstrap/shared'; @import 'ChillMainAssets/module/bootstrap/shared';
// Chill variables // Chill variables
@import './scss/chill_variables'; @import './scss/chill_variables';
@@ -240,6 +240,10 @@ table.table-bordered {
color: $gray-800; color: $gray-800;
font-size: 90%; font-size: 90%;
p {
margin-bottom: 0.75rem !important;
}
// test a bottom right decoration (to be confirmed) // test a bottom right decoration (to be confirmed)
&.test { &.test {
position: relative; position: relative;
@@ -273,11 +277,17 @@ table.table-bordered {
} }
} }
/// meta-data
div.updatedBy,
div.metadata {
span.user, span.date {
text-decoration: underline dotted;
}
}
div.metadata { div.metadata {
font-size: smaller; font-size: smaller;
color: $gray-600; color: $gray-600;
span.user, span.date { span.user, span.date {
text-decoration: underline dotted;
&:hover { &:hover {
color: $gray-700; color: $gray-700;
} }
@@ -318,6 +328,7 @@ dl.definition-inline {
.custom_field_no_data, .custom_field_no_data,
.chill-no-data-statement { .chill-no-data-statement {
font-style: italic; font-style: italic;
font-size: 90%;
} }
/// flash /// flash
@@ -418,3 +429,65 @@ span.item-key {
background-color: #0000000a; background-color: #0000000a;
//text-decoration: dotted underline; //text-decoration: dotted underline;
} }
/// Workflows
div.workflow {
section.step {
border: 1px solid $chill-l-gray;
padding: 1em 2em;
div.flex-table {
margin: 1.5em -2em;
}
}
div.to-decision,
div.decided {
font-variant: all-small-caps;
margin-left: 1em;
}
div.to-decision {
font-weight: 300;
}
div.decided {
font-weight: 600;
}
div.breadcrumb {
display: initial;
margin-bottom: 0;
padding-right: 0.5em;
background-color: tint-color($chill-yellow, 90%);
border: 1px solid $chill-yellow;
color: $primary;
border-radius: 1.5em;
font-size: 12pt;
font-weight: 500;
font-variant: small-caps;
span, a {
cursor: pointer;
text-decoration: none;
&:hover {
font-weight: 700;
}
}
}
}
// Override bootstrap popover styles
div.popover {
box-shadow: 0 0 10px -5px $dark;
z-index: 9999;
.popover-arrow {}
.popover-header {}
.popover-body {}
// Specific worflow breadcrumb popover
&.workflow-transition {
.popover-header {
font-variant: small-caps;
}
}
}
// increase toast message z-index (above all modals)
div.v-toast {
z-index: 10000!important;
}

View File

@@ -12,6 +12,7 @@
display: block; display: block;
top: calc(50% - 7px); top: calc(50% - 7px);
right: 10px; right: 10px;
line-height: 11px;
} }
} }
@@ -62,14 +63,19 @@ ul.list-suggest {
& span:hover { & span:hover {
color: $chill-l-gray; color: $chill-l-gray;
} }
.person-text {
span {
padding-left: 0px;
}
}
} }
} }
&.remove-items { &.remove-items {
li { li {
position: relative; position: relative;
span { & > span {
display: block; display: block;
padding-right: .75rem; padding-right: 1.75rem;
@include remove_link; @include remove_link;
} }
} }

View File

@@ -18,10 +18,13 @@ $chill-theme-buttons: (
"show": $chill-blue, "show": $chill-blue,
"view": $chill-blue, "view": $chill-blue,
"misc": $gray-300, "misc": $gray-300,
"download": $gray-300,
"cancel": $gray-300, "cancel": $gray-300,
"choose": $gray-300, "choose": $gray-300,
"notify": $gray-300, "notify": $chill-blue,
"search": $gray-300,
"unlink": $chill-red, "unlink": $chill-red,
"tpchild": $chill-pink,
); );
@each $button, $color in $chill-theme-buttons { @each $button, $color in $chill-theme-buttons {
@@ -50,6 +53,7 @@ $chill-theme-buttons: (
&.btn-unlink, &.btn-unlink,
&.btn-action, &.btn-action,
&.btn-edit, &.btn-edit,
&.btn-tpchild,
&.btn-update { &.btn-update {
&, &:hover { &, &:hover {
color: $light; color: $light;
@@ -75,6 +79,9 @@ $chill-theme-buttons: (
&.btn-remove::before, &.btn-remove::before,
&.btn-choose::before, &.btn-choose::before,
&.btn-notify::before, &.btn-notify::before,
&.btn-tpchild::before,
&.btn-download::before,
&.btn-search::before,
&.btn-cancel::before { &.btn-cancel::before {
font: normal normal normal 14px/1 ForkAwesome; font: normal normal normal 14px/1 ForkAwesome;
margin-right: 0.5em; margin-right: 0.5em;
@@ -101,6 +108,9 @@ $chill-theme-buttons: (
&.btn-choose::before { content: "\f00c"; } // fa-check // f046 fa-check-square-o &.btn-choose::before { content: "\f00c"; } // fa-check // f046 fa-check-square-o
&.btn-unlink::before { content: "\f127"; } // fa-chain-broken &.btn-unlink::before { content: "\f127"; } // fa-chain-broken
&.btn-notify::before { content: "\f1d8"; } // fa-paper-plane &.btn-notify::before { content: "\f1d8"; } // fa-paper-plane
&.btn-tpchild::before { content: "\f007"; } // fa-user
&.btn-download::before { content: "\f019"; } // fa-download
&.btn-search::before { content: "\f002"; } // fa-search
} }
@@ -126,3 +136,18 @@ $chill-theme-buttons: (
.btn-sm, .btn-group-sm > .btn { .btn-sm, .btn-group-sm > .btn {
min-width: 36px; min-width: 36px;
} }
// Homepage special fast action buttons
div.sticky-buttons {
position: fixed;
bottom: 3em;
right: 2em;
.btn-circle {
width: 50px; height: 50px;
border-radius: 50%;
text-align: center;
padding: 0.45rem 0.7rem;
display: block;
margin-bottom: 0.5rem;
}
}

View File

@@ -41,6 +41,15 @@ div.flex-table {
margin-right: 5px; margin-right: 5px;
} }
} }
div.item-meta {
flex-grow: 1 !important;
flex-shrink: 1 !important;
width: unset !important;
display: flex;
flex-direction: column;
justify-content: center;
}
} }
/* /*

View File

@@ -36,27 +36,57 @@ div.notification {
div.notification-list, div.notification-list,
div.notification-show { div.notification-show {
div.item-bloc { div.item-bloc {
div.item-row.header { div.item-row {
&.notification-header {
div.item-col { div.item-col {
&:first-child { &:first-child {
flex-grow: 1; flex-grow: 1;
}
&:last-child {
flex-grow: 0;
}
} }
&:last-child { ul.small_in_title {
flex-grow: 0; list-style-type: circle;
li {
span.item-key {
display: inline-block;
width: 3em;
}
}
} }
} }
div.notification-content {
ul.small_in_title { margin: 1.5rem;
list-style-type: circle; p {
li { margin-bottom: 0.75rem;
span.item-key {
display: inline-block;
width: 3em;
}
} }
} }
} }
} }
} }
// Override bootstrap accordion
div#workflow-fold,
div#notification-fold {
.accordion-button {
padding: 0;
background-color: unset;
&:not(.collapsed) {
background-color: unset;
box-shadow: unset;
}
}
}
// Counter
div.notification-counter {
span {
&:not(:first-child) {
&::before {
content: '/ ';
}
}
}
}

View File

@@ -106,6 +106,8 @@ section.chill-entity {
// used for comment-embeddable // used for comment-embeddable
&.entity-comment-embeddable { &.entity-comment-embeddable {
width: 100%; width: 100%;
/* already defined !!
div.metadata { div.metadata {
font-size: smaller; font-size: smaller;
color: $gray-600; color: $gray-600;
@@ -116,5 +118,6 @@ section.chill-entity {
} }
} }
} }
*/
} }
} }

View File

@@ -85,7 +85,9 @@ const fetchScopes = () => {
const ValidationException = (response) => { const ValidationException = (response) => {
const error = {}; const error = {};
error.name = 'ValidationException'; error.name = 'ValidationException';
error.violations = response.violations.map((violation) => `${violation.title}`); error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
error.titles = response.violations.map((violation) => violation.title);
error.propertyPaths = response.violations.map((violation) => violation.propertyPath);
return error; return error;
} }

View File

@@ -0,0 +1,12 @@
const buildLinkCreate = function(workflowName, relatedEntityClass, relatedEntityId) {
let params = new URLSearchParams();
params.set('entityClass', relatedEntityClass);
params.set('entityId', relatedEntityId);
params.set('workflow', workflowName);
return `/fr/main/workflow/create?`+params.toString();
};
export {
buildLinkCreate,
};

View File

@@ -1,19 +1,19 @@
/** /**
* Create a control to show or hide values * Create a control to show or hide values
* *
* Possible options are: * Possible options are:
* *
* - froms: an Element, an Array of Element, or a NodeList. A * - froms: an Element, an Array of Element, or a NodeList. A
* listener will be attached to **all** input of those elements * listener will be attached to **all** input of those elements
* and will trigger the check on changes * and will trigger the check on changes
* - test: a function which will test the element and will return true * - test: a function which will test the element and will return true
* if the content must be shown, false if it must be hidden. * if the content must be shown, false if it must be hidden.
* The function will receive the `froms` as first argument, and the * The function will receive the `froms` as first argument, and the
* event as second argument. * event as second argument.
* - container: an Element, an Array of Element, or a Node List. The * - container: an Element, an Array of Element, or a Node List. The
* child nodes will be hidden / shown inside this container * child nodes will be hidden / shown inside this container
* - event_name: the name of the event to listen to. `'change'` by default. * - event_name: the name of the event to listen to. `'change'` by default.
* *
* @param object options * @param object options
*/ */
var ShowHide = function(options) { var ShowHide = function(options) {
@@ -26,8 +26,10 @@ var ShowHide = function(options) {
container_content = [], container_content = [],
debug = 'debug' in options ? options.debug : false, debug = 'debug' in options ? options.debug : false,
load_event = 'load_event' in options ? options.load_event : 'load', load_event = 'load_event' in options ? options.load_event : 'load',
id = 'uid' in options ? options.id : Math.random(); id = 'uid' in options ? options.id : Math.random(),
toggle_callback = 'toggle_callback' in options ? options.toggle_callback : null
;
var bootstrap = function(event) { var bootstrap = function(event) {
if (debug) { if (debug) {
console.log('debug is activated on this show-hide', this); console.log('debug is activated on this show-hide', this);
@@ -43,10 +45,10 @@ var ShowHide = function(options) {
// attach the listener on each input // attach the listener on each input
for (let f of froms.values()) { for (let f of froms.values()) {
let let
inputs = f.querySelectorAll('input'), inputs = f.querySelectorAll('input'),
selects = f.querySelectorAll('select'); selects = f.querySelectorAll('select');
for (let input of inputs.values()) { for (let input of inputs.values()) {
if (debug) { if (debug) {
console.log('attaching event to input', input); console.log('attaching event to input', input);
@@ -66,10 +68,10 @@ var ShowHide = function(options) {
} }
// first launch of the show/hide // first launch of the show/hide
onChange(event); onChange(event);
}; };
var onChange = function (event) { var onChange = function (event) {
var result = test(froms, event), me; var result = test(froms, event), me;
@@ -88,45 +90,53 @@ var ShowHide = function(options) {
} else { } else {
throw "the result of test is not a boolean"; throw "the result of test is not a boolean";
} }
}; };
var forceHide = function() { var forceHide = function() {
if (debug) { if (debug) {
console.log('force hide'); console.log('force hide');
} }
for (let contents of container_content.values()) { if (toggle_callback !== null) {
for (let el of contents.values()) { toggle_callback(container, 'hide');
el.remove(); } else {
for (let contents of container_content.values()) {
for (let el of contents.values()) {
el.remove();
}
} }
} }
is_shown = false; is_shown = false;
}; };
var forceShow = function() { var forceShow = function() {
if (debug) { if (debug) {
console.log('show'); console.log('show');
} }
for (let i of container_content.keys()) { if (toggle_callback !== null) {
var contents = container_content[i]; toggle_callback(container, 'show');
for (let el of contents.values()) { } else {
container[i].appendChild(el); for (let i of container_content.keys()) {
var contents = container_content[i];
for (let el of contents.values()) {
container[i].appendChild(el);
}
} }
} }
is_shown = true; is_shown = true;
}; };
var forceCompute = function(event) { var forceCompute = function(event) {
onChange(event); onChange(event);
}; };
if (load_event !== null) { if (load_event !== null) {
window.addEventListener('load', bootstrap); window.addEventListener('load', bootstrap);
} else { } else {
bootstrap(null); bootstrap(null);
} }
return { return {
forceHide: forceHide, forceHide: forceHide,
forceShow: forceShow, forceShow: forceShow,

View File

@@ -1,21 +1,19 @@
require('./blur.scss'); require('./blur.scss');
var toggleBlur = function(e){ document.querySelectorAll('.confidential').forEach(function (el) {
let i = document.createElement('i');
var btn = e.target; const classes = ['fa', 'fa-eye', 'toggle'];
i.classList.add(...classes);
btn.previousElementSibling.classList.toggle("blur"); el.appendChild(i);
btn.classList.toggle("fa-eye"); const toggleBlur = function(e) {
btn.classList.toggle("fa-eye-slash"); for (let child of el.children) {
if (!child.classList.contains('toggle')) {
} child.classList.toggle('blur');
}
var infos = document.getElementsByClassName("confidential"); }
for(var i=0; i < infos.length; i++){ i.classList.toggle('fa-eye');
infos[i].insertAdjacentHTML('beforeend', '<i class="fa fa-eye toggle" aria-hidden="true"></i>'); i.classList.toggle('fa-eye-slash');
} }
i.addEventListener('click', toggleBlur);
var toggles = document.getElementsByClassName("toggle"); toggleBlur();
for(var i=0; i < toggles.length; i++){ });
toggles[i].addEventListener("click", toggleBlur);
}

View File

@@ -9,9 +9,10 @@ import Dropdown from 'bootstrap/js/src/dropdown';
import Modal from 'bootstrap/js/dist/modal'; import Modal from 'bootstrap/js/dist/modal';
import Collapse from 'bootstrap/js/src/collapse'; import Collapse from 'bootstrap/js/src/collapse';
import Carousel from 'bootstrap/js/src/carousel'; import Carousel from 'bootstrap/js/src/carousel';
import Popover from 'bootstrap/js/src/popover';
// //
// ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section // Carousel: ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section
// Initialize options, and show/hide controls in first/last slides // Initialize options, and show/hide controls in first/last slides
// //
let ACHeaderSlider = document.querySelector('#ACHeaderSlider'); let ACHeaderSlider = document.querySelector('#ACHeaderSlider');
@@ -48,3 +49,14 @@ if (ACHeaderSlider) {
} }
}) })
} }
//
// Popover: used in workflow breadcrumb,
// (expected in: contextual help, notification-box, workflow-box )
//
const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
const popoverList = triggerList.map(function (el) {
return new Popover(el, {
html: true,
});
});

View File

@@ -0,0 +1,34 @@
import { createApp } from "vue";
import ListWorkflowModalVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({});
// list workflow
document.querySelectorAll('[data-list-workflows]')
.forEach(function (el) {
const app = {
components: {
ListWorkflowModalVue,
},
template:
'<list-workflow-modal-vue ' +
':workflows="workflows" ' +
':allowCreate="allowCreate" ' +
':relatedEntityClass="relatedEntityClass" ' +
':relatedEntityId="relatedEntityId" ' +
':workflowsAvailables="workflowsAvailables" ' +
'></list-workflow-modal-vue>',
data() {
return {
workflows: JSON.parse(el.dataset.workflows),
allowCreate: el.dataset.allowCreate === "1",
relatedEntityClass: el.dataset.relatedEntityClass,
relatedEntityId: Number.parseInt(el.dataset.relatedEntityId),
workflowsAvailables: JSON.parse(el.dataset.workflowsAvailables),
}
}
};
createApp(app).use(i18n).mount(el);
})
;

View File

@@ -0,0 +1,32 @@
import {createApp} from "vue";
import EntityWorkflowVueSubscriber from 'ChillMainAssets/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
const i18n = _createI18n(appMessages);
let containers = document.querySelectorAll('[data-entity-workflow-subscribe]');
containers.forEach(container => {
let app = {
components: {
EntityWorkflowVueSubscriber,
},
template: '<entity-workflow-vue-subscriber :entityWorkflowId="this.entityWorkflowId" :subscriberStep="this.subscriberStep" :subscriberFinal="this.subscriberFinal" @subscriptionUpdated="onUpdate"></entity-workflow-vue-subscriber>',
data() {
return {
entityWorkflowId: Number.parseInt(container.dataset.entityWorkflowId),
subscriberStep: container.dataset.subscribeStep === "1",
subscriberFinal: container.dataset.subscribeFinal === "1",
}
},
methods: {
onUpdate(status) {
this.subscriberStep = status.step;
this.subscriberFinal = status.final;
}
}
}
createApp(app).use(i18n).mount(container);
})

View File

@@ -6,7 +6,7 @@ const i18n = _createI18n({});
window.addEventListener('DOMContentLoaded', function (e) { window.addEventListener('DOMContentLoaded', function (e) {
document.querySelectorAll('.notification_toggle_read_status') document.querySelectorAll('.notification_toggle_read_status')
.forEach(function (el) { .forEach(function (el, i) {
createApp({ createApp({
template: '<notification-read-toggle ' + template: '<notification-read-toggle ' +
':notificationId="notificationId" ' + ':notificationId="notificationId" ' +
@@ -26,13 +26,25 @@ window.addEventListener('DOMContentLoaded', function (e) {
buttonNoText: 'false' === el.dataset.buttonText, buttonNoText: 'false' === el.dataset.buttonText,
showUrl: el.dataset.showButtonUrl, showUrl: el.dataset.showButtonUrl,
isRead: 1 === +el.dataset.notificationCurrentIsRead, isRead: 1 === +el.dataset.notificationCurrentIsRead,
container: el.dataset.container
}
},
computed: {
getContainer() {
return document.querySelectorAll('div.' + this.container);
} }
}, },
methods: { methods: {
onMarkRead() { onMarkRead() {
if (typeof this.getContainer[i] !== 'undefined') {
this.getContainer[i].classList.replace('read', 'unread');
} else { throw 'data-container attribute is missing' }
this.isRead = false; this.isRead = false;
}, },
onMarkUnread() { onMarkUnread() {
if (typeof this.getContainer[i] !== 'undefined') {
this.getContainer[i].classList.replace('unread', 'read');
} else { throw 'data-container attribute is missing' }
this.isRead = true; this.isRead = true;
}, },
} }
@@ -40,4 +52,4 @@ window.addEventListener('DOMContentLoaded', function (e) {
.use(i18n) .use(i18n)
.mount(el); .mount(el);
}); });
}) });

View File

@@ -5,18 +5,27 @@ import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
const i18n = _createI18n(appMessages); const i18n = _createI18n(appMessages);
window.addEventListener('DOMContentLoaded', function(e) { let appsOnPage = new Map();
let apps = document.querySelectorAll('[data-module="pick-dynamic"]'); function loadDynamicPicker(element) {
let apps = element.querySelectorAll('[data-module="pick-dynamic"]');
apps.forEach(function(el) { apps.forEach(function(el) {
const const
isMultiple = parseInt(el.dataset.multiple) === 1, isMultiple = parseInt(el.dataset.multiple) === 1,
input = document.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'), uniqId = el.dataset.uniqid,
picked = isMultiple ? JSON.parse(input.value) : [JSON.parse(input.value)]; input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
picked = (isMultiple) ? (JSON.parse(input.value)) : ((input.value === '[]') ? (null) : ([JSON.parse(input.value)]));
createApp({ if (!isMultiple) {
if (input.value === '[]'){
input.value = null;
}
}
const app = createApp({
template: '<pick-entity ' + template: '<pick-entity ' +
':multiple="multiple" ' + ':multiple="multiple" ' +
':types="types" ' + ':types="types" ' +
@@ -31,7 +40,7 @@ window.addEventListener('DOMContentLoaded', function(e) {
return { return {
multiple: isMultiple, multiple: isMultiple,
types: JSON.parse(el.dataset.types), types: JSON.parse(el.dataset.types),
picked, picked: picked === null ? [] : picked,
uniqid: el.dataset.uniqid, uniqid: el.dataset.uniqid,
} }
}, },
@@ -65,5 +74,36 @@ window.addEventListener('DOMContentLoaded', function(e) {
}) })
.use(i18n) .use(i18n)
.mount(el); .mount(el);
appsOnPage.set(uniqId, app);
}); });
}); }
document.addEventListener('show-hide-show', function(e) {
console.log('creation event caught')
loadDynamicPicker(e.detail.container)
})
document.addEventListener('show-hide-hide', function(e) {
console.log('hiding event caught')
e.detail.container.querySelectorAll('[data-module="pick-dynamic"]').forEach((el) => {
let uniqId = el.dataset.uniqid;
console.log(uniqId);
if (appsOnPage.has(uniqId)) {
appsOnPage.get(uniqId).unmount();
console.log('App has been unmounted')
appsOnPage.delete(uniqId);
}
})
})
document.addEventListener('DOMContentLoaded', function(e) {
console.log('loaded event', e)
loadDynamicPicker(document)
})

View File

@@ -0,0 +1,29 @@
import { createApp } from 'vue';
import OpenWopiLink from 'ChillMainAssets/vuejs/_components/OpenWopiLink';
import {_createI18n} from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({});
window.addEventListener('DOMContentLoaded', function (e) {
document.querySelectorAll('span[data-module="wopi-link"]')
.forEach(function (el) {
createApp({
template: '<open-wopi-link :wopiUrl="wopiUrl" :title="title" :type="type" :button="button"></open-wopi-link>',
components: {
OpenWopiLink
},
data() {
return {
wopiUrl: el.dataset.wopiUrl,
title: el.dataset.docTitle,
type: el.dataset.docType,
button: el.dataset.button ? JSON.parse(el.dataset.button) : {}
}
}
})
.use(i18n)
.mount(el)
;
})
;
});

View File

@@ -0,0 +1,16 @@
import { createApp } from 'vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import { appMessages } from 'ChillMainAssets/vuejs/HomepageWidget/js/i18n';
import { store } from 'ChillMainAssets/vuejs/HomepageWidget/js/store';
import App from 'ChillMainAssets/vuejs/HomepageWidget/App';
const i18n = _createI18n(appMessages);
const app = createApp({
template: `<app></app>`,
})
.use(store)
.use(i18n)
.component('app', App)
.mount('#homepage_widget')
;

View File

@@ -0,0 +1,75 @@
import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js';
window.addEventListener('DOMContentLoaded', function() {
let
divTransitions = document.querySelector('#transitions'),
futureDestUsersContainer = document.querySelector('#futureDestUsers')
;
if (null !== divTransitions) {
new ShowHide({
load_event: null,
froms: [divTransitions],
container: [futureDestUsersContainer],
test: function(divs, arg2, arg3) {
for (let div of divs) {
for (let input of div.querySelectorAll('input')) {
if (input.checked) {
if (input.dataset.toFinal === "1") {
return false;
} else {
return true;
}
}
}
}
return true;
},
});
}
let
transitionFilterContainer = document.querySelector('#transitionFilter'),
transitions = document.querySelector('#transitions')
;
if (null !== transitionFilterContainer) {
transitions.querySelectorAll('.form-check').forEach(function(row) {
const isForward = row.querySelector('input').dataset.isForward;
console.log(row);
console.log(isForward);
new ShowHide({
load_event: null,
froms: [transitionFilterContainer],
container: row,
test: function (containers) {
for (let container of containers) {
for (let input of container.querySelectorAll('input')) {
if (input.checked) {
return isForward === input.value;
}
}
}
},
toggle_callback: function (c, dir) {
for (let div of c) {
let input = div.querySelector('input');
if ('hide' === dir) {
input.checked = false;
input.disabled = true;
} else {
input.disabled = false;
}
}
},
});
});
}
});

View File

@@ -98,6 +98,8 @@
v-bind:defaultz="this.defaultz" v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity" v-bind:entity="this.entity"
v-bind:flag="this.flag" v-bind:flag="this.flag"
v-bind:errors="this.errors"
v-bind:checkErrors="this.checkErrors"
@getCities="getCities" @getCities="getCities"
@getReferenceAddresses="getReferenceAddresses"> @getReferenceAddresses="getReferenceAddresses">
</edit-pane> </edit-pane>
@@ -123,6 +125,8 @@
v-bind:defaultz="this.defaultz" v-bind:defaultz="this.defaultz"
v-bind:entity="this.entity" v-bind:entity="this.entity"
v-bind:flag="this.flag" v-bind:flag="this.flag"
v-bind:errors="this.errors"
v-bind:checkErrors="this.checkErrors"
v-bind:insideModal="false" v-bind:insideModal="false"
@getCities="getCities" @getCities="getCities"
@getReferenceAddresses="getReferenceAddresses"> @getReferenceAddresses="getReferenceAddresses">
@@ -256,8 +260,10 @@ export default {
editPane: false, editPane: false,
datePane: false, datePane: false,
loading: false, loading: false,
success: false success: false,
dirty: false
}, },
errors: [],
defaultz: { defaultz: {
button: { button: {
text: { create: 'add_an_address_title', edit: 'edit_address' }, text: { create: 'add_an_address_title', edit: 'edit_address' },
@@ -529,6 +535,23 @@ export default {
}); });
}, },
checkErrors() {
this.errors = [];
if (this.flag.dirty) {
if (this.entity.selected.country === null) {
this.errors.push("Un pays doit être sélectionné.");
}
if (Object.keys(this.entity.selected.city).length === 0) {
this.errors.push("Une ville doit être sélectionnée.");
}
if (!this.entity.selected.isNoAddress) {
if (this.entity.selected.address.street === null || this.entity.selected.address.streetNumber === null) {
this.errors.push("Une adresse doit être sélectionnée.");
}
}
}
},
/* /*
* Make form ready for new changes * Make form ready for new changes
*/ */
@@ -539,6 +562,7 @@ export default {
this.entity.loaded.cities = []; this.entity.loaded.cities = [];
this.entity.loaded.countries = []; this.entity.loaded.countries = [];
this.entity.selected.confidential = this.context.edit ? this.entity.address.confidential : false;
this.entity.selected.isNoAddress = (this.context.edit && this.entity.address.text === '') ? true : false; this.entity.selected.isNoAddress = (this.context.edit && this.entity.address.text === '') ? true : false;
this.entity.selected.country = this.context.edit ? this.entity.address.country : {}; this.entity.selected.country = this.context.edit ? this.entity.address.country : {};
@@ -570,6 +594,7 @@ export default {
{ {
console.log('apply changes'); console.log('apply changes');
let newAddress = { let newAddress = {
'confidential': this.entity.selected.confidential,
'isNoAddress': this.entity.selected.isNoAddress, 'isNoAddress': this.entity.selected.isNoAddress,
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street, 'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
'streetNumber': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.streetNumber, 'streetNumber': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.streetNumber,

View File

@@ -6,7 +6,6 @@
<input class="form-control" <input class="form-control"
type="text" type="text"
name="floor" name="floor"
maxlength=16
:placeholder="$t('floor')" :placeholder="$t('floor')"
v-model="floor"/> v-model="floor"/>
<label for="floor">{{ $t('floor') }}</label> <label for="floor">{{ $t('floor') }}</label>
@@ -15,7 +14,6 @@
<input class="form-control" <input class="form-control"
type="text" type="text"
name="corridor" name="corridor"
maxlength=16
:placeholder="$t('corridor')" :placeholder="$t('corridor')"
v-model="corridor"/> v-model="corridor"/>
<label for="corridor">{{ $t('corridor') }}</label> <label for="corridor">{{ $t('corridor') }}</label>
@@ -24,7 +22,6 @@
<input class="form-control" <input class="form-control"
type="text" type="text"
name="steps" name="steps"
maxlength=16
:placeholder="$t('steps')" :placeholder="$t('steps')"
v-model="steps"/> v-model="steps"/>
<label for="steps">{{ $t('steps') }}</label> <label for="steps">{{ $t('steps') }}</label>
@@ -33,7 +30,6 @@
<input class="form-control" <input class="form-control"
type="text" type="text"
name="flat" name="flat"
maxlength=16
:placeholder="$t('flat')" :placeholder="$t('flat')"
v-model="flat"/> v-model="flat"/>
<label for="flat">{{ $t('flat') }}</label> <label for="flat">{{ $t('flat') }}</label>

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