Compare commits

...

218 Commits

Author SHA1 Message Date
cc1e2fde1f added case for workflow notification in vue component 2022-02-14 14:16:22 +01:00
61cf838458 if condition to check if a sender is assigned or if notification was assigned automatically by the system 2022-02-14 14:05:59 +01:00
e028c1ca2b thirdparty contact address blurred in view page if confidential 2022-02-14 11:44:12 +01:00
7dd6cd04ba Merge branch 'issue419_AddPersons_clear_ul' into 'master'
Issue419  remove ul-li in AddPersons

See merge request Chill-Projet/chill-bundles!328
2022-02-11 16:31:55 +00:00
1072f539dc Merge branch 'issue440_mesparcours_brouillon' into 'master'
Mes parcours brouillon

See merge request Chill-Projet/chill-bundles!330
2022-02-11 16:23:05 +00:00
9bcf4cce9f Merge branch 'master' into 'issue440_mesparcours_brouillon'
# Conflicts:
#   CHANGELOG.md
2022-02-11 16:22:51 +00:00
9223beac47 Merge branch 'issue416_chill_document_edit_button' into 'master'
Issue416 chill document edit button

See merge request Chill-Projet/chill-bundles!326
2022-02-11 16:21:15 +00:00
62c78e650f fix access denied when updating a work 2022-02-11 17:20:26 +01:00
e7d0c1cac6 fix cs 2022-02-11 17:14:14 +01:00
b859283766 fix error on creating an accompanying period work 2022-02-11 17:09:56 +01:00
e595500cb8 apply changes on person document 2022-02-11 17:01:36 +01:00
f34847f9f6 Merge remote-tracking branch 'origin/master' into issue416_chill_document_edit_button 2022-02-11 16:09:00 +01:00
0fa119c984 Merge branch 'issue_414_documents_list' into 'master'
created and updated traits added + template property added to Document entity

See merge request Chill-Projet/chill-bundles!316
2022-02-11 15:05:21 +00:00
270d00c1e8 fix phpstan and cs 2022-02-11 16:04:27 +01:00
983c5f3c6a fix probably missing variables in PersonDocumentType 2022-02-11 16:04:27 +01:00
c6141e473f fix test in twig template 2022-02-11 16:04:26 +01:00
c30cb5879d changelog updated 2022-02-11 16:04:24 +01:00
29e384ada5 scope placed within condition in edit form personDocument 2022-02-11 16:04:01 +01:00
c8e5ba4738 phpcsfixes 2022-02-11 16:04:01 +01:00
bf0b7f1bb2 Create AccompanyingCourseDocumentRepository and use to add pagination 2022-02-11 16:04:01 +01:00
d6cc69b919 Add pagination in the findBy() 2022-02-11 16:04:01 +01:00
ac33c6aad8 Add sticky buttons for create 2022-02-11 16:04:01 +01:00
89383a5ec4 deprecated service replaced and error removed from phpstan file 2022-02-11 16:04:00 +01:00
0fd26da299 comments in migration removed 2022-02-11 16:04:00 +01:00
6fa99655b2 delegates the acl for fetching person documents to an ACLAwareREpository 2022-02-11 16:04:00 +01:00
a387305ef8 remove scope in PersonDocumentType when disabled 2022-02-11 16:04:00 +01:00
e0c7daf3fd Pagination added 2022-02-11 16:04:00 +01:00
4304baeca5 scope put in condition in personDocument form - temporarily commented out 2022-02-11 16:04:00 +01:00
d2a567c79e condition added in show template for scope 2022-02-11 16:04:00 +01:00
3c04ced1a3 templates adjusted 2022-02-11 16:04:00 +01:00
a28d8a7287 only add scope field conditionally in personDocumentType analogue to SingleTaskType 2022-02-11 16:04:00 +01:00
1c055e842e created and updated traits added + template property added to Document entity 2022-02-11 16:04:00 +01:00
dea283d655 fix phpstan and cs 2022-02-11 16:03:22 +01:00
58c8373c81 Merge branch 'issue378_normalize_requestor' into 'master'
accompanying course: normalize the requestor entity

See merge request Chill-Projet/chill-bundles!329
2022-02-11 14:45:32 +00:00
a1ccfadac1 fix probably missing variables in PersonDocumentType 2022-02-11 15:42:40 +01:00
632b1262f9 Merge remote-tracking branch 'origin/master' into issue_414_documents_list 2022-02-11 15:34:31 +01:00
e157624479 Merge branch 'master' into 'issue378_normalize_requestor'
# Conflicts:
#   CHANGELOG.md
2022-02-11 14:31:53 +00:00
3a2370d536 fix missing cases for accompanying period draft list
fix when:

* if there isn't any information into course => better render box
* if there isn't any course into list => display warning
2022-02-11 15:26:17 +01:00
981c94906d Merge remote-tracking branch 'origin/master' into issue440_mesparcours_brouillon 2022-02-11 15:11:20 +01:00
adce90c151 Merge remote-tracking branch 'origin/master' into issue440_mesparcours_brouillon 2022-02-11 15:11:08 +01:00
83f8402081 Merge branch 'issue415_household_address' into 'master'
Display address of current household in history page

See merge request Chill-Projet/chill-bundles!334
2022-02-11 14:10:52 +00:00
22417ee9f9 fix tests for address render 2022-02-11 15:03:54 +01:00
3c5d269ece template fixes residential address 2022-02-11 14:58:11 +01:00
42579b5752 fix translation and how holder is shown 2022-02-11 14:49:59 +01:00
15023d348a Merge remote-tracking branch 'origin/master' into issue415_household_address 2022-02-11 14:39:27 +01:00
fbf777e134 fix cs 2022-02-11 14:35:10 +01:00
450277f32a fix test in twig template 2022-02-11 14:34:35 +01:00
93128cb61b if conditions joined 2022-02-11 14:28:42 +01:00
d3d655293e Merge remote-tracking branch 'origin/master' into issue_414_documents_list 2022-02-11 14:24:36 +01:00
0592601aa6 Merge branch 'household-editor/show-full-address' 2022-02-11 14:22:41 +01:00
a17f5948fb Merge remote-tracking branch 'origin/master' 2022-02-11 14:22:27 +01:00
6578f66fec update changelog 2022-02-11 14:22:10 +01:00
3f2a19a6f0 Merge remote-tracking branch 'origin/master' into household-editor/show-full-address 2022-02-11 14:21:06 +01:00
d5160ead4c Apply 1 suggestion(s) to 1 file(s) 2022-02-11 13:18:51 +00:00
a6e9cbdece Merge branch 'fix-tests-2022-02-11' into 'master'
fix ci

See merge request Chill-Projet/chill-bundles!335
2022-02-11 13:18:32 +00:00
657bf7075b fix ci 2022-02-11 13:18:32 +00:00
f1c3e8dc1b Merge remote-tracking branch 'origin/master' 2022-02-11 14:18:20 +01:00
a9624b36f7 create person and household option added to dropdown 2022-02-11 14:17:59 +01:00
db4aec9a18 Merge branch 'issue427_returnPath' into 'master'
on the fly component: add returnPath when viewing

See merge request Chill-Projet/chill-bundles!324
2022-02-11 13:16:51 +00:00
c34ce48a53 Merge branch 'master' into 'issue427_returnPath'
# Conflicts:
#   CHANGELOG.md
2022-02-11 13:16:40 +00:00
93345c09a1 fix cs 2022-02-11 13:45:21 +01:00
b5a1985fa1 test "find household by reference": add exception in data provider
The data provider cannot generate data if no address reference in
database. Throw an exception in this case.
2022-02-11 13:43:29 +01:00
9b4c350213 fix tests for accompanying period comments 2022-02-11 13:36:35 +01:00
199a441593 fix tests for EntityWorkflow 2022-02-11 13:24:29 +01:00
f42d106e3c Merge branch 'accent440' 2022-02-11 13:17:19 +01:00
13eddbfc5b changelog 2022-02-11 13:16:48 +01:00
7c30126fed Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2022-02-11 13:14:45 +01:00
e3c5f6f95f fix phpcs, psalm and phpstan 2022-02-11 13:07:38 +01:00
a86f1a05ac Merge branch 'accent440' 2022-02-11 13:04:35 +01:00
9ba0cb0c2a homepage_widget: fix translation for emergency badge 2022-02-11 12:56:15 +01:00
add8588767 changelog updated 2022-02-11 12:36:05 +01:00
a083a320ae display address of current household 2022-02-11 12:34:18 +01:00
64fa7f2e00 Merge branch 'issue433_email_addPerson' into 'master'
AddPerson: email and altNames corrections and validation

See merge request Chill-Projet/chill-bundles!327
2022-02-11 11:33:23 +00:00
c7a830ee28 Merge branch 'master' into 'issue433_email_addPerson'
# Conflicts:
#   CHANGELOG.md
2022-02-11 11:33:09 +00:00
9aa51406fa Merge branch 'accent419' 2022-02-11 12:26:27 +01:00
344981cf99 Merge branch 'master' into issue433_email_addPerson 2022-02-11 12:16:49 +01:00
28c4d6b92f vue pick entity: fix i18n translations in create thirdparty modal 2022-02-11 12:16:01 +01:00
ef2ab5faf6 vue pick entity: improve picked condition when value is empty string 2022-02-11 12:12:12 +01:00
54dbf711f9 fix cs 2022-02-11 12:10:44 +01:00
60951231c4 Merge branch 'issue415_household_address' of gitlab.com:Chill-Projet/chill-bundles into issue415_household_address 2022-02-11 11:57:49 +01:00
ece30b63c3 in process... need to be sure which address to display exactly 2022-02-11 11:45:23 +01:00
94503505f7 changelog updated 2022-02-11 11:40:56 +01:00
48589f3368 scope placed within condition in edit form personDocument 2022-02-11 11:33:07 +01:00
1e04c178dc phpcsfixes 2022-02-11 11:28:32 +01:00
37ed703abe Create AccompanyingCourseDocumentRepository and use to add pagination 2022-02-11 11:26:21 +01:00
7ed20d2fc5 Add pagination in the findBy() 2022-02-11 11:25:53 +01:00
5fe2a8e737 Add sticky buttons for create 2022-02-11 11:25:27 +01:00
nobohan
861abe708b upd CHANGELOG 2022-02-11 10:55:37 +01:00
nobohan
f43e216e13 fix normalisation of AccompanyingCourse requestor api 2022-02-11 10:53:05 +01:00
5210e7985f deprecated service replaced and error removed from phpstan file 2022-02-11 10:46:46 +01:00
ddabdff206 comments in migration removed 2022-02-11 10:45:30 +01:00
5a692db30f Fix review remarks 2022-02-10 17:51:45 +01:00
659983fef1 delegates the acl for fetching person documents to an ACLAwareREpository 2022-02-10 00:37:24 +01:00
aca5a3c731 remove scope in PersonDocumentType when disabled 2022-02-10 00:12:55 +01:00
406cfc86cd add order in localisation entity list (api) 2022-02-09 23:44:38 +01:00
90da067fbd address: show full address in address text and adapt Vue 2022-02-09 22:21:48 +01:00
e7a6742964 OpenWopiLink, option title is maintened (replace button by options) 2022-02-09 18:04:56 +01:00
3ffeaf419a mimetype document mapping (temporary) 2022-02-09 17:39:43 +01:00
ce2f64acd0 wopi edit extension: button call collabora not in a modal, but in a page (twig way) 2022-02-09 17:10:12 +01:00
08f544f6ea add btn-wopilink button ; fix OpenWopiLink when options is null 2022-02-09 16:29:57 +01:00
4e862cc585 Changelog updated 2022-02-09 16:20:59 +01:00
05d2c3b2e8 Minor style adjustment within template
The content of the div aligned center
2022-02-09 16:19:42 +01:00
513207e510 PHP csfixes 2022-02-09 16:08:27 +01:00
dee6ed6707 Listing of mes parcours changed to only display confirmed or closed
draft parcours are displayed in mes parcours brouillon
2022-02-09 16:02:21 +01:00
d60095987c Changelog updated 2022-02-09 15:59:19 +01:00
b4f9be0d92 openingDate only displayed on parcours that are not in draft state 2022-02-09 15:59:10 +01:00
a2a0f6af44 mes parcours brouillon added to usermenu 2022-02-09 15:58:54 +01:00
7fa0353d8c Pagination added 2022-02-09 15:10:04 +01:00
9256c71715 scope put in condition in personDocument form - temporarily commented out 2022-02-09 14:38:20 +01:00
5520f3df50 condition added in show template for scope 2022-02-09 14:37:48 +01:00
a6c0d8b219 templates adjusted 2022-02-09 14:37:06 +01:00
f6e667700e move twig function in runtime 2022-02-09 13:19:58 +01:00
f94ef0798a Merge branch 'issue_414_documents_list' of gitlab.com:Chill-Projet/chill-bundles into issue_414_documents_list 2022-02-09 10:15:18 +01:00
7bd7476200 only add scope field conditionally in personDocumentType analogue to SingleTaskType 2022-02-09 10:15:09 +01:00
a2752765af created and updated traits added + template property added to Document entity 2022-02-09 09:54:21 +01:00
1e81256404 fix cs 2022-02-08 19:37:59 +01:00
6b571e87b4 fix typos 2022-02-08 19:37:11 +01:00
4314823a70 change order for accompanying period work list 2022-02-08 19:36:45 +01:00
dc08ced6c9 fix notification subject 2022-02-07 21:49:34 +01:00
9d660311f0 improve translation on working about houshold members 2022-02-07 20:54:06 +01:00
b3e65f0733 fix validation when comment content is empty 2022-02-07 20:53:44 +01:00
03007370bc fix "impossible to confirm a course"
In some case, the "confirm" button for the form AccompanyingCourse was
disable, due to wrong condition.

This commit also fixes the filtering for users, and allow to remove a
user associated with an accompanyingCourse.
2022-02-07 17:44:03 +01:00
f9b514c869 fix assertion of type.
https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/266
2022-02-07 11:22:35 +01:00
nobohan
91ba08dfa1 accompanying course: normalize the requestor entity 2022-02-04 10:34:32 +01:00
nobohan
06142797c3 AddPersons: add missing css class + upd CHANGELOG 2022-02-03 16:25:59 +01:00
nobohan
4185b660cc AddPersons: remove ul-li html tags from AddPersons 2022-02-03 16:17:05 +01:00
ad46776164 hide save and close button in modal header 2022-02-03 15:58:57 +01:00
2343a4a334 add links between document in workflow and course 2022-02-03 13:49:52 +01:00
3f83c323be fix creating workflow from document in accompanying course 2022-02-03 13:16:13 +01:00
9a9ed64298 fix on test after creating an activity without generating a document 2022-02-03 12:54:44 +01:00
nobohan
a0cadb46db upd CHANGELOG 2022-02-03 11:32:11 +01:00
nobohan
f139af8b6f accompanying course work: fix on-the-fly update of thirdParty 2022-02-03 11:26:31 +01:00
nobohan
1f0ef6e187 on-the-fly: close modal only after validation 2022-02-03 11:07:32 +01:00
nobohan
4afea55465 addperson: repair altname: add altname if known 2022-02-03 09:17:45 +01:00
nobohan
3d5723facb AddPerson: correct thirdparty PATCH url + add email and altnames in AddPerson and serializer 2022-02-03 08:59:56 +01:00
211ecf1c3e add cache to workflow counter 2022-02-02 23:19:59 +01:00
796b4ff76b Merge branch 'master' into issue431_worklow_notifications 2022-02-02 22:40:37 +01:00
9553e18d83 do not uppercase/lowercase ref in activity list 2022-02-02 22:17:24 +01:00
02d9a713de set requestor before acdtions 2022-02-02 22:16:15 +01:00
6cc4fa582e fix validation error with startDate 2022-02-02 22:15:53 +01:00
b8d43f36eb document: add macro mapping forkawesome icon from mime type 2022-02-02 19:39:17 +01:00
06b3d63133 pass options in twig extension filter 2022-02-02 18:28:59 +01:00
232e71f46c uniformize ui with sticky-form-button 2022-02-02 18:25:44 +01:00
0b5a0e65a8 ckeck document mime (TO BE CHECKED) 2022-02-02 16:53:13 +01:00
ef42227e6c data-doc-title is a not required props 2022-02-02 15:13:48 +01:00
2af3de4f61 chill_document_edit_button: create new twig extension 2022-02-02 14:57:25 +01:00
8bafa37fe1 dump removed 2022-02-02 10:32:46 +01:00
nobohan
a83b0326d7 upd CHANGELOG 2022-02-02 07:04:14 +01:00
53e03395f2 renaming date de fermeture en date de cloture in accompanying course 2022-02-01 22:36:51 +01:00
5fef4048a4 use renderbox in activity list recent 2022-02-01 22:36:26 +01:00
d8929a6222 Merge branch 'master' into homepage/rewrite 2022-02-01 21:26:27 +01:00
33af7f36a0 remove dump 2022-02-01 21:23:50 +01:00
3f2454cead disable multiselect search in City and Street search 2022-02-01 21:23:15 +01:00
0ac9f2df8c fix loading of file template 2022-02-01 21:20:11 +01:00
333224a9d5 update changelog 2022-02-01 19:19:50 +01:00
496e5f0259 homepage: fix counter appareance on MyCustoms vue tab 2022-02-01 19:13:25 +01:00
f2a62822a4 Merge remote-tracking branch 'origin/master' 2022-02-01 18:43:39 +01:00
7879a99197 Merge branch 'master' into issue425_errorMessage_closeParcours 2022-02-01 18:42:59 +01:00
72d8cc297f Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2022-02-01 18:41:07 +01:00
1a0674a20b using badges instead of eye for person resource list 2022-02-01 18:40:57 +01:00
572aa2c14e Merge remote-tracking branch 'origin/master' into issue426_household_composition 2022-02-01 18:29:02 +01:00
bc4b2c4e86 Merge branch 'master' into issue428_person_resource_ameliorations 2022-02-01 18:08:48 +01:00
a227e88e79 Merge branch 'master' into small_issues_421_424 2022-02-01 18:05:33 +01:00
d9e9f472fb Merge remote-tracking branch 'origin/small_issues_421_424' into small_issues_421_424 2022-02-01 18:02:25 +01:00
fcff4bf863 fix phpcs 2022-02-01 18:01:34 +01:00
79b82e09e1 add document from activity 2022-02-01 17:58:20 +01:00
nobohan
a78f29cffc on the fly component: add returnPath when viewing 2022-02-01 17:44:55 +01:00
e98b1b049b add context to MyEvaluations (action and course containers) 2022-02-01 17:28:30 +01:00
99efa51794 csfixes 2022-02-01 17:23:45 +01:00
76932e6e8c bugfix in notificationhandler. Not sure about the fix in the template 2022-02-01 17:23:12 +01:00
97d2e3d5b5 counter created for workflows for which user is a destuser and that are not finalized 2022-02-01 17:22:45 +01:00
a2063ab13b Merge branch 'master' into homepage/rewrite 2022-02-01 16:54:49 +01:00
2bb718f3ea fix popover header on vue ListWorkflow breadcrumb 2022-02-01 15:51:42 +01:00
c6373a075d refactor pickTemplate and adapt existing usage 2022-02-01 15:49:23 +01:00
46337ef25d in process... need to be sure which address to display exactly 2022-02-01 15:09:42 +01:00
2f5fecf414 notifications: fix comments counter 2022-02-01 14:56:50 +01:00
5d2cb8c712 homepage: add social action in evaluation serialisation 2022-02-01 14:14:55 +01:00
460502a1f2 mask unready comments counter 2022-02-01 14:13:08 +01:00
cdd7a16d18 homepage_widget counters pluralization 2022-02-01 14:10:19 +01:00
ec29758547 detect span.counter and allow to put styles on the number 2022-02-01 14:09:29 +01:00
c5cbdd97ff csfixes 2022-02-01 13:52:42 +01:00
05fc5a1829 changelog updated 2022-02-01 13:52:13 +01:00
c3de3c6c39 validation adjusted to greaterThanOrEqual and different message passed 2022-02-01 13:51:21 +01:00
1530e9d13a changelog updated 2022-02-01 12:37:09 +01:00
a986b4ae98 csfixes 2022-02-01 12:33:09 +01:00
0854b7fc6a delete action created in controller and template 2022-02-01 12:32:34 +01:00
730199cca4 Merge branch 'foldlist_in_entity' into homepage/rewrite 2022-02-01 12:23:19 +01:00
de1dddbb85 validation: no more workflow without dest 2022-02-01 12:19:51 +01:00
a612d7dd9f improve workflow decision form 2022-02-01 12:19:51 +01:00
50e722e637 fix workflow redirection in accompanying period work edit 2022-02-01 12:19:51 +01:00
e4d5e40a8b improve homepage_widget tables with datas 2022-02-01 12:07:16 +01:00
479ec25d99 Merge branch 'issue428_person_resource_ameliorations' of gitlab.com:Chill-Projet/chill-bundles into issue428_person_resource_ameliorations 2022-02-01 10:18:21 +01:00
930b6ff77e minor style changes 2022-02-01 10:17:08 +01:00
7d8bd089a2 double edit button removed 2022-02-01 10:09:48 +01:00
2c566bb21c Merge branch 'master' into 'issue428_person_resource_ameliorations'
# Conflicts:
#   CHANGELOG.md
2022-01-31 17:50:08 +00:00
a0bc6c7604 csfixes 2022-01-31 18:47:06 +01:00
a4457664d4 bugfix addPersons 2022-01-31 18:33:29 +01:00
b42ae76bdf Merge branch 'small_issues_421_424' of gitlab.com:Chill-Projet/chill-bundles into small_issues_421_424 2022-01-31 17:46:18 +01:00
2eb1ebd263 changelog updated with bugfix 2022-01-31 17:45:22 +01:00
b79885cd21 bugfix creation of person or thirdparty through onTheFly 2022-01-31 17:45:04 +01:00
69dd920fd6 php csfixes 2022-01-31 17:45:04 +01:00
0689ca366d changelog updated 2022-01-31 17:44:04 +01:00
af1c268784 commentfield for marital status is always visible 2022-01-31 17:43:50 +01:00
1162c436c1 demandeur not displayed in search list if anonymous 2022-01-31 17:43:50 +01:00
571a8a0edc Merge branch 'foldlist_in_entity' of gitlab.com:Chill-Projet/chill-bundles into foldlist_in_entity 2022-01-31 15:55:52 +01:00
bdbb83ab85 wip count comments 2022-01-31 15:53:57 +01:00
434d672d8a Merge branch 'master' into small_issues_421_424 2022-01-31 14:49:38 +01:00
e21ff588ae csfixes 2022-01-31 14:42:54 +01:00
0383571a9e changelog updated 2022-01-31 14:42:17 +01:00
f358e78734 onthefly button added to view person or thirdparty + badge changed if thirdparty is a contact 2022-01-31 14:39:40 +01:00
43ef31b93f Merge remote-tracking branch 'origin/master' into small_issues_421_424 2022-01-31 14:28:30 +01:00
15393d9da9 changelog updated with bugfix 2022-01-31 13:45:41 +01:00
695d22adba bugfix creation of person or thirdparty through onTheFly 2022-01-31 13:39:52 +01:00
bc1a63354c php csfixes 2022-01-31 13:11:25 +01:00
faebb0fe30 changelog updated 2022-01-31 13:08:55 +01:00
68d99050bc commentfield for marital status is always visible 2022-01-31 13:06:59 +01:00
4fa1bb4341 demandeur not displayed in search list if anonymous 2022-01-31 12:45:02 +01:00
739540af64 created and updated traits added + template property added to Document entity 2022-01-31 11:13:22 +01:00
158 changed files with 2781 additions and 1124 deletions

64
.env
View File

@@ -1,64 +0,0 @@
##
## Manually dump .env files in .env.local.php with
## `$ composer symfony:dump-env prod`
##
## Project environment
APP_ENV=dev
## Enable debug
APP_DEBUG=true
## Locale
LOCALE=fr
## Framework secret
APP_SECRET=ThisTokenIsNotSoSecretChangeIt
## Symfony/swiftmailer
MAILER_TRANSPORT=smtp
MAILER_HOST=smtp
MAILER_PORT=1025
MAILER_CRYPT=
MAILER_AUTH=
MAILER_USER=
MAILER_PASSWORD=
MAILER_URL=${MAILER_TRANSPORT}://${MAILER_HOST}:${MAILER_PORT}?encryption=${MAILER_CRYPT}&auth_mode=${MAILER_AUTH}&username=${MAILER_USER}&password=${MAILER_PASSWORD}
## Notifications
NOTIFICATION_HOST=localhost:8001
NOTIFICATION_FROM_EMAIL=admin@chill.social
NOTIFICATION_FROM_NAME=Chill
## Gelf
GELF_HOST=gelf
GELF_PORT=12201
## OVH OpenStack Storage User/Role
OS_USERNAME=
OS_PASSWORD=
OS_TENANT_ID=
OS_REGION_NAME=GRA
OS_AUTH_URL=https://auth.cloud.ovh.net/v2.0/
## OVH OpenStack Storage Container
ASYNC_UPLOAD_TEMP_URL_KEY=
ASYNC_UPLOAD_TEMP_URL_BASE_PATH=
ASYNC_UPLOAD_TEMP_URL_CONTAINER=
## Redis Cache
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
## Twilio
TWILIO_SID=~
TWILIO_SECRET=~
## DOCKER IMAGES REGISTRY
#IMAGE_PHP=
#IMAGE_NGINX=
## DOCKER IMAGES VERSION
#VERSION=test
VERSION=prod

View File

@@ -9,18 +9,52 @@ and this project adheres to
* date versioning for test releases
## Unreleased
* AddPersons: remove ul-li html tags from AddPersons (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/419)
<!-- 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
* fix normalisation of accompanying course requestor api (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/378)
* [person] add a returnPath when clicking on some Person or ThirdParty badge (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/427)
* [person] accompanying course work: fix on-the-fly update of thirdParty
* [on-the-fly] close modal only after validation
* [person] correct thirdparty PATCH url + add email and altnames in AddPerson and serializer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/433)
* change order for accompanying course work list
* [parcours]: Mes parcours brouillon added to user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/440)
* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
* [person]: style fix in parcours listing per person. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/432)
* [household]: display address of current household (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/415)
* ajoute un ordre dans les localisation (api)
* [pick entity]: fix translations in modal (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/419)
* [homepage_widget]: fix translation on emergency badge (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/440)
* [person]: create person and household added to button dropdown (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/454)
* display full address in address.text in normalization. Adapt AddressRenderBox
* [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459)
* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
* [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450)
## Test releases
### test release 2021-02-01
* 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
* [homepage widget] improve content tables, improve counter pluralization with style on number
* [notification lists] add comments counter information
* [workflows] fix popover header with previous transition
* [parcours]: validation + message for closing parcours adjusted.
* [household]: household composition double edit button replaced by a delete action (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/426)
[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]: Comment on marital status is possible even if marital status is not defined (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/421)
* [parcours]: In the list of person results the requestor is not displayed if defined as anonymous (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/424)
* [bugfix]: modal closes and newly created person/thirdparty is selected when multiple persons/thirdparties are created through the modal (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/429)
* [person_resource]: Onthefly button added to view person/thirdparty and badge differentiation for a contact-thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/428)
* [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal
* [AddAddress] disable multiselect search, and rely only on most pertinent Cities and Street computed backend
* [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.
### 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)
@@ -31,7 +65,6 @@ and this project adheres to
* [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
@@ -67,7 +100,7 @@ and this project adheres to
* [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]
* [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

View File

@@ -408,24 +408,6 @@ parameters:
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Form/CustomFieldsGroupType.php
-
message:
"""
#^Call to deprecated method getReachableScopes\\(\\) of class Chill\\\\MainBundle\\\\Security\\\\Authorization\\\\AuthorizationHelper\\:
Use getReachableCircles$#
"""
count: 1
path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
-
message:
"""
#^Parameter \\$translator of method Chill\\\\DocStoreBundle\\\\Controller\\\\DocumentPersonController\\:\\:__construct\\(\\) has typehint with deprecated interface Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\:
since Symfony 4\\.2, use Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface instead$#
"""
count: 1
path: src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
-
message:
"""
@@ -927,15 +909,6 @@ parameters:
count: 1
path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php
-
message:
"""
#^Parameter \\$trans of method Chill\\\\PersonBundle\\\\Controller\\\\AccompanyingCourseWorkController\\:\\:__construct\\(\\) has typehint with deprecated interface Symfony\\\\Component\\\\Translation\\\\TranslatorInterface\\:
since Symfony 4\\.2, use Symfony\\\\Contracts\\\\Translation\\\\TranslatorInterface instead$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php
-
message:
"""

View File

@@ -34,6 +34,7 @@ use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -200,12 +201,36 @@ final class ActivityController extends AbstractController
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,
])->handleRequest($request);
]);
if ($form->has('documents')) {
$form->add('gendocTemplateId', HiddenType::class, [
'mapped' => false,
'data' => null,
'attr' => [
// required for js
'data-template-id' => 'data-template-id',
],
]);
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($entity);
$this->entityManager->flush();
if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
return $this->redirectToRoute(
'chill_docgenerator_generate_from_template',
[
'template' => $form->get('gendocTemplateId')->getData(),
'entityClassName' => Activity::class,
'entityId' => $entity->getId(),
]
);
}
$this->addFlash('success', $this->get('translator')->trans('Success : activity updated!'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
@@ -393,12 +418,36 @@ final class ActivityController extends AbstractController
'role' => new Role('CHILL_ACTIVITY_CREATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,
])->handleRequest($request);
]);
if ($form->has('documents')) {
$form->add('gendocTemplateId', HiddenType::class, [
'mapped' => false,
'data' => null,
'attr' => [
// required for js
'data-template-id' => 'data-template-id',
],
]);
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($entity);
$this->entityManager->flush();
if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
return $this->redirectToRoute(
'chill_docgenerator_generate_from_template',
[
'template' => $form->get('gendocTemplateId')->getData(),
'entityClassName' => Activity::class,
'entityId' => $entity->getId(),
]
);
}
$this->addFlash('success', $this->get('translator')->trans('Success : activity created!'));
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);

View File

@@ -271,7 +271,7 @@ class ActivityType
public function checkSocialActionsVisibility(ExecutionContextInterface $context, $payload)
{
if ($this->socialIssuesVisible !== $this->socialActionsVisible) {
if (!($this->socialIssuesVisible === 2 && $this->socialActionsVisible === 1)) {
if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) {
$context
->buildViolation('The socialActionsVisible value is not compatible with the socialIssuesVisible value')
->atPath('socialActionsVisible')

View File

@@ -19,14 +19,18 @@
</ul>
</div>
<add-persons
buttonTitle="activity.add_persons"
modalTitle="activity.add_persons"
v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons"
ref="addPersons">
</add-persons>
<ul class="record_actions">
<li class="add-persons">
<add-persons
buttonTitle="activity.add_persons"
modalTitle="activity.add_persons"
v-bind:key="addPersons.key"
v-bind:options="addPersonsOptions"
@addNewPersons="addNewPersons"
ref="addPersons">
</add-persons>
</li>
</ul>
</teleport>
</template>

View File

@@ -2,11 +2,15 @@ import { createApp } from 'vue';
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { activityMessages } from './i18n'
import store from './store'
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
import App from './App.vue';
const i18n = _createI18n(activityMessages);
// app for activity
const hasSocialIssues = document.querySelector('#social-issues-acc') !== null;
const hasLocation = document.querySelector('#location') !== null;
const hasPerson = document.querySelector('#add-persons') !== null;
@@ -29,3 +33,54 @@ const app = createApp({
.use(i18n)
.component('app', App)
.mount('#activity');
// app for picking template
const i18nGendoc = _createI18n({});
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
fetchTemplates(el.dataset.entityClass).then(templates => {
const picker = {
template:
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
':entityClass="faked" @go-to-generate-document="generateDoc"></pick-template>',
components: {
PickTemplate,
},
data() {
return {
templates: templates,
entityId: el.dataset.entityId,
}
},
methods: {
generateDoc({event, link, template}) {
console.log('generateDoc');
console.log('link', link);
console.log('template', template);
let hiddenInput = document.querySelector("input[data-template-id]");
if (hiddenInput === null) {
console.error('hidden input not found');
return;
}
hiddenInput.value = template;
let form = document.querySelector('form[name="chill_activitybundle_activity"');
if (form === null) {
console.error('form not found');
return;
}
form.submit();
}
}
};
createApp(picker).use(i18nGendoc).mount(el);
})
});

View File

@@ -68,7 +68,7 @@
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
{{ activity.user|chill_entity_render_string|capitalize }}
{{ activity.user|chill_entity_render_box }}
</p>
</div>
</div>

View File

@@ -89,9 +89,9 @@
{%- if edit_form.documents is defined -%}
{{ form_row(edit_form.documents) }}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
{% 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 %}
{% if entity.person %}

View File

@@ -24,12 +24,10 @@
window.activity = {{ activity_json|json_encode|raw }};
</script>
{{ encore_entry_script_tags('vue_activity') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{% endblock %}

View File

@@ -39,11 +39,9 @@
window.activity = {{ activity_json|json_encode|raw }};
</script>
{{ encore_entry_script_tags('vue_activity') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('vue_activity') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{% endblock %}

View File

@@ -13,7 +13,7 @@
<p class="date-label">{{ activity.date|format_date('short') }}</p>
{%- endif -%}
<span class="like-h3">{{ activity.type.name | localize_translatable_string }}</span>
<span class="like-h3">{{ activity.type.name|localize_translatable_string }}</span>
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
@@ -41,7 +41,7 @@
{% if activity.user and t.userVisible %}
<li>
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
<b>{{ activity.user.usernameCanonical }}</b>
<b>{{ activity.user|chill_entity_render_box}}</b>
</li>
{% endif %}

View File

@@ -87,6 +87,7 @@
{%- if form.documents is defined -%}
{{ form_row(form.documents) }}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
{% endif %}
{%- if form.attendee is defined -%}

View File

@@ -17,10 +17,6 @@
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
<script type="text/javascript">
window.addEventListener('DOMContentLoaded', function (e) {
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
});
window.activity = {{ activity_json|json_encode|raw }};
{% if default_location is not null %}window.default_location_id = {{ default_location.id }}{% endif %};
</script>

View File

@@ -17,7 +17,6 @@ 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;
@@ -210,11 +209,8 @@ class ActivityContext implements
*/
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new StoredObject();
// TODO push document to remote
$entity->addDocument($storedObject);
$this->em->persist($doc);
$entity->addDocument($doc);
$this->em->persist($storedObject);
}
}

View File

@@ -167,7 +167,6 @@ class AsideActivityCategory
}
$this->parent = $parent;
dump($this);
return $this;
}

View File

@@ -97,8 +97,6 @@ class CalendarType extends AbstractType
return $res;
},
static function (?string $dateAsString): DateTimeImmutable {
dump($dateAsString);
return new DateTimeImmutable($dateAsString);
}
));

View File

@@ -0,0 +1,13 @@
const buildLink = function(templateId, entityId, entityClass) {
const
entityIdEncoded = encodeURI(entityId),
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
entityClassEncoded = encodeURI(entityClass),
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`
;
console.log('computed Url');
return url;
};
export {buildLink};

View File

@@ -20,8 +20,8 @@
<option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option>
</template>
</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-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button>
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>
<a v-else class="btn btn-update btn-sm change-icon" href="#" disabled ><i class="fa fa-fw fa-cog"></i></a>
</div>
</div>
</div>
@@ -39,24 +39,27 @@
<script>
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
export default {
name: "PickTemplate",
props: {
entityId: [String, Number],
entityClass: {
type: String,
required: true,
required: false,
},
templates: {
type: Array,
required: true,
},
// beforeMove execute "something" before
beforeMove: {
type: Function,
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
}
},
emits: ['goToGenerateDocument'],
data() {
return {
template: null,
@@ -74,66 +77,37 @@ export default {
return true;
},
getDescription() {
if (null === this.template) {
return '';
}
let desc = this.templates.find(t => t.id === this.template);
if (null === desc) {
return '';
}
return desc.description || '';
},
buildUrlGenerate() {
if (null === this.template) {
return '#';
}
return buildLink(this.template, this.entityId, this.entityClass);
}
},
methods: {
generateDocument() {
console.log('generateDocument');
console.log('beforeMove', this.beforeMove);
if (this.beforeMove != null) {
console.log('execute before move');
let r = this.beforeMove();
if (r instanceof Promise) {
r.then((obj) => this.goToGenerate(obj));
} else {
this.goToGenerate();
}
} else {
this.goToGenerate();
}
},
goToGenerate(obj) {
console.log('goToGenerate');
console.log('obj', obj);
console.log('entityId', this.entityId);
let
realId = this.entityId,
realEntityClass = this.entityClass
;
if (obj !== undefined) {
if (obj.entityId !== undefined) {
realId = obj.entityId;
}
if (obj.entityClass !== undefined) {
realEntityClass = obj.entityClass;
}
clickGenerate(event, link) {
if (!this.preventDefaultMoveToGenerate) {
window.location.assign(link);
}
console.log('realId', realId);
console.log('realEntityClass', realEntityClass);
const
entityId = encodeURI(realId),
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
fqdnEntityClass = encodeURI(realEntityClass),
url = `/fr/doc/gen/generate/from/${this.template}/for/${fqdnEntityClass}/${entityId}?returnPath=${returnPath}`
;
console.log('I will generate your doc at', url);
window.location.assign(url);
this.$emit('goToGenerateDocument', {event, link, template: this.template});
},
},
i18n: {
messages: {
fr: {
generate_document: 'Générer un document',
select_a_template: 'Choisir un gabarit',
select_a_template: 'Choisir un modèle',
choose_a_template: 'Choisir',
}
}

View File

@@ -13,7 +13,9 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Form\AccompanyingCourseDocumentType;
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use DateTime;
@@ -29,20 +31,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class DocumentAccompanyingCourseController extends AbstractController
{
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
protected AuthorizationHelper $authorizationHelper;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
protected EventDispatcherInterface $eventDispatcher;
/**
* @var TranslatorInterface
*/
protected $translator;
protected TranslatorInterface $translator;
private AccompanyingCourseDocumentRepository $courseRepository;
private PaginatorFactory $paginatorFactory;
/**
* DocumentAccompanyingCourseController constructor.
@@ -50,11 +47,15 @@ class DocumentAccompanyingCourseController extends AbstractController
public function __construct(
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper
AuthorizationHelper $authorizationHelper,
PaginatorFactory $paginatorFactory,
AccompanyingCourseDocumentRepository $courseRepository
) {
$this->translator = $translator;
$this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper;
$this->paginatorFactory = $paginatorFactory;
$this->courseRepository = $courseRepository;
}
/**
@@ -130,11 +131,15 @@ class DocumentAccompanyingCourseController extends AbstractController
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course);
$documents = $em
->getRepository('ChillDocStoreBundle:AccompanyingCourseDocument')
$total = $this->courseRepository->countByCourse($course);
$pagination = $this->paginatorFactory->create($total);
$documents = $this->courseRepository
->findBy(
['course' => $course],
['date' => 'DESC']
['date' => 'DESC'],
$pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber()
);
return $this->render(
@@ -142,6 +147,7 @@ class DocumentAccompanyingCourseController extends AbstractController
[
'documents' => $documents,
'accompanyingCourse' => $course,
'pagination' => $pagination,
]
);
}

View File

@@ -13,7 +13,8 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Form\PersonDocumentType;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
@@ -24,7 +25,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Class DocumentPersonController.
@@ -35,20 +36,15 @@ use Symfony\Component\Translation\TranslatorInterface;
*/
class DocumentPersonController extends AbstractController
{
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
protected AuthorizationHelper $authorizationHelper;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
protected EventDispatcherInterface $eventDispatcher;
/**
* @var TranslatorInterface
*/
protected $translator;
protected TranslatorInterface $translator;
private PaginatorFactory $paginatorFactory;
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
/**
* DocumentPersonController constructor.
@@ -56,11 +52,15 @@ class DocumentPersonController extends AbstractController
public function __construct(
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper
AuthorizationHelper $authorizationHelper,
PaginatorFactory $paginatorFactory,
PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository
) {
$this->translator = $translator;
$this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper;
$this->paginatorFactory = $paginatorFactory;
$this->personDocumentACLAwareRepository = $personDocumentACLAwareRepository;
}
/**
@@ -156,19 +156,15 @@ class DocumentPersonController extends AbstractController
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
$reachableScopes = $this->authorizationHelper
->getReachableScopes(
$this->getUser(),
PersonDocumentVoter::SEE,
$person->getCenter()
);
$total = $this->personDocumentACLAwareRepository->countByPerson($person);
$pagination = $this->paginatorFactory->create($total);
$documents = $em
->getRepository('ChillDocStoreBundle:PersonDocument')
->findBy(
['person' => $person, 'scope' => $reachableScopes],
['date' => 'DESC']
);
$documents = $this->personDocumentACLAwareRepository->findByPerson(
$person,
[],
$pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber()
);
$event = new PrivacyEvent($person, [
'element_class' => PersonDocument::class,
@@ -181,6 +177,7 @@ class DocumentPersonController extends AbstractController
[
'documents' => $documents,
'person' => $person,
'pagination' => $pagination,
]
);
}

View File

@@ -38,6 +38,7 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
$loader->load('services/menu.yaml');
$loader->load('services/fixtures.yaml');
$loader->load('services/form.yaml');
$loader->load('services/templating.yaml');
}
public function prepend(ContainerBuilder $container)

View File

@@ -11,7 +11,13 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Entity;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
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\HasScopeInterface;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
@@ -20,8 +26,12 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\MappedSuperclass
*/
class Document implements HasScopeInterface
class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* @ORM\ManyToOne(targetEntity="Chill\DocStoreBundle\Entity\DocumentCategory")
* @ORM\JoinColumns({
@@ -67,6 +77,11 @@ class Document implements HasScopeInterface
*/
private $scope;
/**
* @ORM\ManyToOne(targetEntity="Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate")
*/
private $template;
/**
* @ORM\Column(type="text")
* @Assert\Length(
@@ -82,9 +97,6 @@ class Document implements HasScopeInterface
*/
private $user;
/**
* @return DocumentCategory
*/
public function getCategory(): ?DocumentCategory
{
return $this->category;
@@ -115,11 +127,16 @@ class Document implements HasScopeInterface
*
* @return \Chill\MainBundle\Entity\Scope
*/
public function getScope()
public function getScope(): ?Scope
{
return $this->scope;
}
public function getTemplate(): ?DocGeneratorTemplate
{
return $this->template;
}
public function getTitle(): ?string
{
return $this->title;
@@ -165,6 +182,13 @@ class Document implements HasScopeInterface
return $this;
}
public function setTemplate(?DocGeneratorTemplate $template): self
{
$this->template = $template;
return $this;
}
public function setTitle(string $title): self
{
$this->title = $title;

View File

@@ -18,10 +18,12 @@ use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectManager;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -51,14 +53,25 @@ class PersonDocumentType extends AbstractType
*/
protected $user;
private ParameterBagInterface $parameterBag;
private ScopeResolverDispatcher $scopeResolverDispatcher;
public function __construct(
TranslatableStringHelper $translatableStringHelper
TranslatableStringHelper $translatableStringHelper,
ScopeResolverDispatcher $scopeResolverDispatcher,
ParameterBagInterface $parameterBag
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
$this->parameterBag = $parameterBag;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$document = $options['data'];
$isScopeConcerned = $this->scopeResolverDispatcher->isConcerned($document);
$builder
->add('title', TextType::class)
->add('description', ChillTextareaType::class, [
@@ -67,10 +80,6 @@ class PersonDocumentType extends AbstractType
->add('object', StoredObjectType::class, [
'error_bubbling' => true,
])
->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
])
->add('date', ChillDateType::class)
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
@@ -84,6 +93,13 @@ class PersonDocumentType extends AbstractType
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';
},
]);
if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
$builder->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
]);
}
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -0,0 +1,75 @@
<?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\Repository;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
class AccompanyingCourseDocumentRepository implements ObjectRepository
{
private EntityManagerInterface $em;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->repository = $em->getRepository(AccompanyingCourseDocument::class);
}
public function buildQueryByCourse(AccompanyingPeriod $course): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('d');
$qb
->where($qb->expr()->eq('d.course', ':course'))
->setParameter('course', $course);
return $qb;
}
public function countByCourse(AccompanyingPeriod $course): int
{
$qb = $this->buildQueryByCourse($course)->select('COUNT(d)');
return $qb->getQuery()->getSingleScalarResult();
}
public function find($id): ?AccompanyingCourseDocument
{
return $this->repository->find($id);
}
public function findAll(): array
{
return $this->repository->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria): ?AccompanyingCourseDocument
{
return $this->findOneBy($criteria);
}
public function getClassName()
{
return AccompanyingCourseDocument::class;
}
}

View File

@@ -0,0 +1,90 @@
<?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\Repository;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface
{
private AuthorizationHelperInterface $authorizationHelper;
private CenterResolverDispatcher $centerResolverDispatcher;
private EntityManagerInterface $em;
private Security $security;
public function __construct(EntityManagerInterface $em, AuthorizationHelperInterface $authorizationHelper, CenterResolverDispatcher $centerResolverDispatcher, Security $security)
{
$this->em = $em;
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->security = $security;
}
public function buildQueryByPerson(Person $person): QueryBuilder
{
$qb = $this->em->getRepository(PersonDocument::class)->createQueryBuilder('d');
$qb
->where($qb->expr()->eq('d.person', ':person'))
->setParameter('person', $person);
return $qb;
}
public function countByPerson(Person $person): int
{
$qb = $this->buildQueryByPerson($person)->select('COUNT(d)');
$this->addACL($qb, $person);
return $qb->getQuery()->getSingleScalarResult();
}
public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array
{
$qb = $this->buildQueryByPerson($person)->select('d');
$this->addACL($qb, $person);
foreach ($orderBy as [$field, $order]) {
$qb->addOrderBy($field, $order);
}
$qb->setFirstResult($offset)->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
private function addACL(QueryBuilder $qb, Person $person): void
{
$center = $this->centerResolverDispatcher->resolveCenter($person);
$reachableScopes = $this->authorizationHelper
->getReachableScopes(
$this->security->getUser(),
PersonDocumentVoter::SEE,
$center
);
$qb->andWhere($qb->expr()->in('d.scope', ':scopes'))
->setParameter('scopes', $reachableScopes);
}
}

View File

@@ -0,0 +1,21 @@
<?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\Repository;
use Chill\PersonBundle\Entity\Person;
interface PersonDocumentACLAwareRepositoryInterface
{
public function countByPerson(Person $person): int;
public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array;
}

View File

@@ -1,4 +1,5 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
<div class="flex-table accompanying_course_work-list">
<div class="item-bloc document-item bg-chill-llight-gray">
@@ -8,14 +9,15 @@
</div>
<div class="col-8">
<h3>{{ document.title }}</h3>
<small>{{ document.object.type }}</small>
{{ mm.mimeIcon(document.object.type) }}
{% if document.description is not empty %}
<blockquote class="chill-user-quote mt-2">
<blockquote class="chill-user-quote mt-4">
{{ document.description }}
</blockquote>
{% endif %}
</div>
</div>
</div>
@@ -32,30 +34,31 @@
{% if display_action is defined and display_action == true %}
<ul class="record_actions">
{% if document.course != null and is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', document.course) %}
<li>
<a href="{{ path('chill_person_accompanying_course_index', {'accompanying_period_id': document.course.id}) }}" class="btn btn-show change-icon">
<i class="fa fa-random"></i> {{ 'Course number'|trans }} {{ document.course.id }}
</a>
</li>
{% endif %}
<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>
{% if chill_document_is_editable(document.object) %}
{% if not freezed %}
{{ document.object|chill_document_edit_button({'title': document.title|e('html') }) }}
{% else %}
<a class="btn btn-wopilink disabled" href="#" title="{{ 'workflow.freezed document'|trans }}">
{{ 'Update document'|trans }}
</a>
{% endif %}
{% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %}
<li>
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': document.course.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
</li>
</ul>
{% endif %}
{% endif %}

View File

@@ -19,7 +19,7 @@
{{ form_row(form.description) }}
{{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }}
<ul class="record_actions">
<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 }}

View File

@@ -2,8 +2,6 @@
{% set activeRouteKey = '' %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% block title %}
{{ 'Documents' }}
{% endblock %}
@@ -11,64 +9,35 @@
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{% endblock %}
{% block content %}
<div class="col-md-10 col-xxl">
<h1>{{ 'Documents' }}</h1>
<table class="table table-bordered border-dark table-striped">
<thead>
<tr>
<th>{{ 'Title' | trans }}</th>
<th>{{ 'Category'|trans }}</th>
<th>{{ 'Actions' | trans }}</th>
</tr>
</thead>
<tbody>
{% if documents|length == 0 %}
<p class="chill-no-data-statement">{{ 'No documents'|trans }}</p>
{% else %}
<div class="flex-table chill-task-list">
{% for document in documents %}
<tr>
<td>{{ document.title }}</td>
<td>{% if document.category %}{{ document.category.name|localize_translatable_string }}{% endif %}</td>
<td>
<ul class="record_actions">
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
<li>
{{ m.download_button(document.object, document.title) }}
</li>
<li>
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
</li>
{% endif %}
</ul>
</td>
</tr>
{% else %}
<tr>
<td colspan="9" style="text-align:center;">
<span class="chill-no-data-statement">{{ 'Any document found'|trans }}</span>
</td>
</tr>
{% include 'ChillDocStoreBundle:List:list_item.html.twig' %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\AccompanyingPeriod" data-entity-id="{{ accompanyingCourse.id }}"></div>
{{ chill_pagination(pagination) }}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\AccompanyingPeriod" data-entity-id="{{ accompanyingCourse.id }}"></div>
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
<ul class="record_actions">
<ul class="record_actions sticky-form-buttons">
<li class="create">
<a href="{{ path('accompanying_course_document_new', {'course': accompanyingCourse.id}) }}" class="btn btn-create">
{{ 'Create'|trans }}
@@ -76,4 +45,6 @@
</li>
</ul>
{% endif %}
</div>
{% endblock %}

View File

@@ -3,6 +3,7 @@
{% set activeRouteKey = '' %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% block title %}
{# {{ 'Detail of document of %name%'|trans({ '%name%': accompanyingCourse|chill_entity_render_string } ) }} #}
@@ -18,16 +19,18 @@
{% block content %}
<div class="document-show">
<h1>{{ block('title') }}</h1>
<dl class="chill_view_data">
{{ mm.mimeIcon(document.object.type) }}
<dl class="chill_view_data mt-4">
<dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd>
{% if document.category is not null %}
<dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</dd>
{% endif %}
<dt>{{ 'Description' | trans }}</dt>
<dd>
{% if document.description is empty %}
@@ -39,7 +42,7 @@
{% 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">
@@ -49,11 +52,15 @@
<li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
</li>
{% endif %}
{% 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>
<a href="{{ path('accompanying_course_document_edit', {'id': document.id, 'course': accompanyingCourse.id}) }}"
class="btn btn-edit" title="{{ 'Edit attributes' | trans }}"></a>
</li>
{% endif %}
{% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %}
@@ -65,7 +72,7 @@
</ul>
</div>
{% endblock %}
{% block block_post_menu %}
<div class="post-menu pt-4"></div>
{% endblock %}
@@ -74,4 +81,4 @@
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,9 @@
{# Twig way
TODO: une route, un template avec un header CHILL et un iframe
#}
<a href="{{ chill_path_add_return_path('chill_wopi_file_edit', {'fileId': document.uuid}) }}"
class="btn btn-wopilink">
{{ 'online_edit_document'|trans }}
</a>

View File

@@ -0,0 +1,83 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
<div class="item-bloc">
<div class="item-row">
<div class="item-col">
<div class="denomination h2">
{{ document.title }}
</div>
<div>
{{ mm.mimeIcon(document.object.type) }}
</div>
<div>
<p>{{ document.category.name|localize_translatable_string }}</p>
</div>
{% if document.template is not null %}
<div>
<p>{{ document.template.name.fr }}</p>
</div>
{% endif %}
</div>
<div class="item-col">
<div class="container">
{% if document.date is not null %}
<div class="dates row" style="float: right;">
<span>{{ document.createdAt|format_date('short') }}</span>
</div>
{% endif %}
</div>
</div>
</div>
{% if document.description is not empty %}
<div class="item-row separator">
<blockquote class="chill-user-quote">
{{ document.description|chill_markdown_to_html }}
</blockquote>
</div>
{% endif %}
<div class="item-row separator">
<div class="item-col item-meta">
<div class="updatedBy">
{{ 'Created by'|trans }}:
<span class="user">{{ document.createdBy|chill_entity_render_string }}</span>
<span class="date">le {{ document.createdAt|format_date('long') }}</span>
</div>
</div>
</div>
<div>
{% if document.course is defined %}
<ul class="record_actions">
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
<li>
{{ m.download_button(document.object, document.title) }}
</li>
<li>
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
</li>
{% endif %}
</ul>
{% else %}
<ul class="record_actions">
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
<li>
{{ m.download_button(document.object, document.title) }}
</li>
<li>
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
</li>
{% endif %}
</ul>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,55 @@
{% macro mimeIcon(type) %}
{# mapping
forkawesome and mime type https://gist.github.com/colemanw/9c9a12aae16a4bfe2678de86b661d922
#}
{% set mapmime = {
'image': 'fa-file-image-o',
'audio': 'fa-file-audio-o',
'video': 'fa-file-video-o',
'application/pdf': 'fa-file-pdf-o',
'application/msword': 'fa-file-word-o',
'application/vnd.ms-word': 'fa-file-word-o',
'application/vnd.oasis.opendocument.text': 'fa-file-word-o',
'application/vnd.openxmlformats-officedocument.wordprocessingml': 'fa-file-word-o',
'application/vnd.ms-excel': 'fa-file-excel-o',
'application/vnd.openxmlformats-officedocument.spreadsheetml': 'fa-file-excel-o',
'application/vnd.oasis.opendocument.spreadsheet': 'fa-file-excel-o',
'application/vnd.ms-powerpoint': 'fa-file-powerpoint-o',
'application/vnd.openxmlformats-officedocument.presentationml': 'fa-file-powerpoint-o',
'application/vnd.oasis.opendocument.presentation': 'fa-file-powerpoint-o',
'text/plain': 'fa-file-text-o',
'text/html': 'fa-file-code-o',
'application/json': 'fa-file-code-o',
'application/gzip': 'fa-file-archive-o',
'application/zip': 'fa-file-archive-o',
} %}
{% set icon = 'fa-file-o' %}
{% for key,val in mapmime %}
{% if type starts with key %}
{% set icon = val %}
{% endif %}
{% endfor %}
{# TODO improve mapping
mime type and friendly name https://gist.github.com/rosskmurphy/3724501
#}
{% set maptype = {
'fa-file-word-o': 'office document/texte',
'fa-file-excel-o': 'office document/tableur',
'fa-file-powerpoint-o': 'office document/presentation',
} %}
{% set label = type %}
{% for key, val in maptype %}
{% if icon == key %}
{% set label = val %}
{% endif %}
{% endfor %}
<div class="metadata">
<i class="fa {{ icon }} fa-lg me-1"></i>
{{ label|capitalize }}
</div>
{% endmacro %}

View File

@@ -30,7 +30,9 @@
{{ form_row(form.title) }}
{{ form_row(form.date) }}
{{ form_row(form.category) }}
{{ form_row(form.scope) }}
{% if form.scope is defined %}
{{ form_row(form.scope) }}
{% endif %}
{{ form_row(form.description) }}
{{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }}

View File

@@ -30,54 +30,24 @@
{% endblock %}
{% block personcontent %}
<div class="col-md-10 col-xxl">
<h1>{{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}</h1>
<table class="table table-bordered border-dark table-striped">
<thead>
<tr>
<th>{{ 'Title' | trans }}</th>
<th>{{ 'Category'|trans }}</th>
<th>{{ 'Circle' | trans }}</th>
<th>{{ 'Actions' | trans }}</th>
</tr>
</thead>
<tbody>
{% if documents|length == 0 %}
<p class="chill-no-data-statement">{{ 'No documents'|trans }}</p>
{% else %}
<div class="flex-table chill-task-list">
{% for document in documents %}
<tr>
<td>{{ document.title }}</td>
<td>{{ document.category.name|localize_translatable_string }}</td>
<td>{{ document.scope.name|localize_translatable_string }}</td>
<td>
<ul class="record_actions">
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
<li>
{{ m.download_button(document.object, document.title) }}
</li>
<li>
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li>
{% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
</li>
{% endif %}
</ul>
</td>
</tr>
{% else %}
<tr>
<td colspan="9" style="text-align:center;">
<span class="chill-no-data-statement">{{ 'Any document found'|trans }}</span>
</td>
</tr>
{% include 'ChillDocStoreBundle:List:list_item.html.twig' %}
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{{ chill_pagination(pagination) }}
{% if is_granted('CHILL_PERSON_DOCUMENT_CREATE', person) %}
<ul class="record_actions">
<ul class="record_actions sticky-form-buttons">
<li class="create">
<a href="{{ path('person_document_new', {'person': person.id}) }}" class="btn btn-create">
{{ 'Create new document' | trans }}
@@ -85,4 +55,6 @@
</li>
</ul>
{% endif %}
</div>
{% endblock %}

View File

@@ -19,6 +19,7 @@
{% set activeRouteKey = '' %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %}
@@ -29,12 +30,16 @@
{% block personcontent %}
<h1>{{ 'Document %title%' | trans({ '%title%': document.title }) }}</h1>
{{ mm.mimeIcon(document.object.type) }}
<dl class="chill_view_data">
<dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd>
{% if document.scope is not null %}
<dt>{{ 'Scope' | trans }}</dt>
<dd>{{ document.scope.name | localize_translatable_string }}</dd>
{% endif %}
<dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</dd>
@@ -52,7 +57,7 @@
</dl>
<ul class="record_actions">
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ path('person_document_index', {'person': person.id}) }}" class="btn btn-cancel">
{{ 'Back to the list' | trans }}
@@ -63,6 +68,12 @@
{{ m.download_button(document.object, document.title) }}
</li>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
</li>
{% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('person_document_edit', {'id': document.id, 'person': person.id}) }}" class="btn btn-edit">

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\DocStoreBundle\Templating;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class WopiEditTwigExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('chill_document_edit_button', [WopiEditTwigExtensionRuntime::class, 'renderEditButton'], [
'needs_environment' => true,
'is_safe' => ['html'],
]),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('chill_document_is_editable', [WopiEditTwigExtensionRuntime::class, 'isEditable']),
];
}
}

View File

@@ -0,0 +1,55 @@
<?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\Templating;
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Twig\Environment;
use Twig\Extension\RuntimeExtensionInterface;
use function array_key_exists;
class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface
{
public const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig';
private DiscoveryInterface $discovery;
public function __construct(DiscoveryInterface $discovery)
{
$this->discovery = $discovery;
}
public function isEditable(StoredObject $document): bool
{
$mime_type = $this->discovery->discoverMimeType($document->getType());
if ([] === $mime_type) {
return false;
}
foreach ($mime_type as $item) {
if (array_key_exists('default', $item) && 'true' === $item['default']) {
return true;
}
}
return false;
}
public function renderEditButton(Environment $environment, StoredObject $document, ?array $options = null): string
{
return $environment->render(self::TEMPLATE, [
'document' => $document,
'options' => $options,
]);
}
}

View File

@@ -6,7 +6,6 @@ services:
autowire: true
autoconfigure: true
resource: "../Repository/"
tags: ["doctrine.repository_service"]
Chill\DocStoreBundle\Form\DocumentCategoryType:
class: Chill\DocStoreBundle\Form\DocumentCategoryType
@@ -16,8 +15,10 @@ services:
Chill\DocStoreBundle\Form\PersonDocumentType:
class: Chill\DocStoreBundle\Form\PersonDocumentType
arguments:
- "@chill.main.helper.translatable_string"
autowire: true
autoconfigure: true
# arguments:
# - "@chill.main.helper.translatable_string"
tags:
- { name: form.type, alias: chill_docstorebundle_form_document }

View File

@@ -0,0 +1,6 @@
services:
Chill\DocStoreBundle\Templating\:
resource: ../../Templating
autoconfigure: true
autowire: true

View File

@@ -0,0 +1,76 @@
<?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\Migrations\DocStore;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Implementations of create and update traits for Document entity + template property added.
*/
final class Version20220131093117 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_doc.person_document DROP CONSTRAINT FK_41DA53C5DA0FB8');
$this->addSql('ALTER TABLE chill_doc.person_document DROP CONSTRAINT FK_41DA53C3174800F');
$this->addSql('ALTER TABLE chill_doc.person_document DROP CONSTRAINT FK_41DA53C65FF1AEC');
$this->addSql('ALTER TABLE chill_doc.person_document DROP template_id');
$this->addSql('ALTER TABLE chill_doc.person_document DROP createdAt');
$this->addSql('ALTER TABLE chill_doc.person_document DROP updatedAt');
$this->addSql('ALTER TABLE chill_doc.person_document DROP createdBy_id');
$this->addSql('ALTER TABLE chill_doc.person_document DROP updatedBy_id');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP CONSTRAINT FK_A45098F65DA0FB8');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP CONSTRAINT FK_A45098F63174800F');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP CONSTRAINT FK_A45098F665FF1AEC');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP template_id');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP createdAt');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP updatedAt');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP createdBy_id');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document DROP updatedBy_id');
}
public function getDescription(): string
{
return 'Implementations of create and update traits for Document entity + template property added';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD template_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD createdBy_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD updatedBy_id INT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN chill_doc.accompanyingcourse_document.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_doc.accompanyingcourse_document.updatedAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD CONSTRAINT FK_A45098F65DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD CONSTRAINT FK_A45098F63174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.accompanyingcourse_document ADD CONSTRAINT FK_A45098F665FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_A45098F65DA0FB8 ON chill_doc.accompanyingcourse_document (template_id)');
$this->addSql('CREATE INDEX IDX_A45098F63174800F ON chill_doc.accompanyingcourse_document (createdBy_id)');
$this->addSql('CREATE INDEX IDX_A45098F665FF1AEC ON chill_doc.accompanyingcourse_document (updatedBy_id)');
$this->addSql('ALTER TABLE chill_doc.person_document ADD template_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.person_document ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.person_document ADD updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.person_document ADD createdBy_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.person_document ADD updatedBy_id INT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN chill_doc.person_document.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_doc.person_document.updatedAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C5DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_41DA53C5DA0FB8 ON chill_doc.person_document (template_id)');
$this->addSql('CREATE INDEX IDX_41DA53C3174800F ON chill_doc.person_document (createdBy_id)');
$this->addSql('CREATE INDEX IDX_41DA53C65FF1AEC ON chill_doc.person_document (updatedBy_id)');
}
}

View File

@@ -10,6 +10,7 @@ New document for %name%: Nouveau document pour %name%
Editing document for %name%: Modification d'un document pour %name%
Edit Document: Modification d'un document
Update document: Modifier le document
Edit attributes: Modifier les propriétés du document
Existing document: Document existant
No document to download: Aucun document à télécharger
'Choose a document category': Choisissez une catégorie de document
@@ -51,4 +52,7 @@ Id inside bundle: Identifiant
Document class: Classe de document
no records found:
Create new category: Créer une nouvelle catégorie
Back to the category list: Retour à la liste
Back to the category list: Retour à la liste
# WOPI EDIT
online_edit_document: Éditer en ligne

View File

@@ -12,6 +12,8 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -19,7 +21,7 @@ use Symfony\Component\HttpFoundation\Request;
*/
class LocationApiController extends ApiController
{
public function customizeQuery(string $action, Request $request, $query): void
protected function customizeQuery(string $action, Request $request, $query): void
{
$query
->leftJoin('e.locationType', 'lt')
@@ -31,4 +33,14 @@ class LocationApiController extends ApiController
)
);
}
/**
* @param QueryBuilder $query
* @param mixed $_format
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $query
->addOrderBy('e.name', 'ASC');
}
}

View File

@@ -236,6 +236,10 @@ class NotificationController extends AbstractController
'_fragment' => 'comment-' . $commentId,
]);
}
if ($editedCommentForm->isSubmitted() && !$editedCommentForm->isValid()) {
$this->addFlash('error', $this->translator->trans('This form contains errors'));
}
}
}
@@ -257,6 +261,10 @@ class NotificationController extends AbstractController
'id' => $notification->getId(),
]);
}
if ($appendCommentForm->isSubmitted() && !$appendCommentForm->isValid()) {
$this->addFlash('error', $this->translator->trans('This form contains errors'));
}
}
}

View File

@@ -19,6 +19,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\Validator\StepDestValid;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
@@ -166,6 +167,7 @@ class WorkflowController extends AbstractController
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$errors = [];
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
// possible transition
@@ -201,9 +203,18 @@ class WorkflowController extends AbstractController
$entityWorkflow->getCurrentStep()->addDestUser($user);
}
$this->entityManager->flush();
$errors = $this->validator->validate(
$entityWorkflow->getCurrentStep(),
new StepDestValid()
);
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
if (count($errors) === 0) {
$this->entityManager->flush();
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
}
return new Response((string) $errors, Response::HTTP_UNPROCESSABLE_ENTITY);
}
if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) {
@@ -235,6 +246,7 @@ class WorkflowController extends AbstractController
'handler_template_data' => $handler->getTemplateData($entityWorkflow),
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
'entity_workflow' => $entityWorkflow,
'transition_form_errors' => $errors,
//'comment_form' => $commentForm->createView(),
]
);

View File

@@ -18,6 +18,7 @@ use DateTimeInterface;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
@@ -28,6 +29,7 @@ class NotificationComment implements TrackCreationInterface, TrackUpdateInterfac
{
/**
* @ORM\Column(type="text")
* @Assert\NotBlank(message="notification.Comment content might not be blank")
*/
private string $content = '';
@@ -136,9 +138,9 @@ class NotificationComment implements TrackCreationInterface, TrackUpdateInterfac
$this->recentlyPersisted = true;
}
public function setContent(string $content): self
public function setContent(?string $content): self
{
$this->content = $content;
$this->content = (string) $content;
return $this;
}

View File

@@ -135,6 +135,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
if (!$this->steps->contains($step)) {
$this->steps[] = $step;
$step->setEntityWorkflow($this);
$this->stepsChainedCache = null;
}
return $this;
@@ -174,6 +175,20 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
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()) {
@@ -318,32 +333,26 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
public function isFinal(): bool
{
$steps = $this->getStepsChained();
if (1 === count($steps)) {
// the initial step cannot be finalized
return false;
foreach ($this->getStepsChained() as $step) {
if ($step->isFinal()) {
return true;
}
}
/** @var EntityWorkflowStep $last */
$last = end($steps);
return $last->isFinal();
return false;
}
public function isFreeze(): bool
{
$steps = $this->getStepsChained();
if (1 === count($steps)) {
// the initial step cannot be finalized
return false;
foreach ($this->getStepsChained() as $step) {
if ($step->isFreezeAfter()) {
return true;
}
}
/** @var EntityWorkflowStep $last */
$last = end($steps);
return $last->getPrevious()->isFreezeAfter();
return false;
}
public function isUserSubscribedToFinal(User $user): bool
@@ -420,7 +429,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
$newStep->setCurrentStep($step);
// copy the freeze
if ($this->getCurrentStep()->isFreezeAfter()) {
if ($this->isFreeze()) {
$newStep->setFreezeAfter(true);
}

View File

@@ -48,6 +48,8 @@ class WorkflowStepType extends AbstractType
$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']) {
@@ -68,40 +70,77 @@ class WorkflowStepType extends AbstractType
$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.Transition to apply',
'label' => 'workflow.Next step',
'mapped' => false,
'multiple' => false,
'expanded' => true,
'choices' => $choices,
'choice_label' => function (Transition $transition) use ($workflow) {
$meta = $workflow->getMetadataStore()->getTransitionMetadata($transition);
$meta = $workflow->getMetadataStore()->getTransitionMetadata($transition);
if (array_key_exists('label', $meta)) {
return $this->translatableStringHelper->localize($meta['label']);
}
if (array_key_exists('label', $meta)) {
return $this->translatableStringHelper->localize($meta['label']);
}
return $transition->getName();
},
return $transition->getName();
},
'choice_attr' => static function (Transition $transition) use ($workflow) {
$toFinal = true;
$toFinal = true;
$isForward = 'neutral';
foreach ($transition->getTos() as $to) {
$meta = $workflow->getMetadataStore()->getPlaceMetadata($to);
$metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
if (
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;
}
$toFinal = false;
}
}
return [
'data-is-transition' => 'data-is-transition',
'data-to-final' => $toFinal ? '1' : '0',
];
},
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',

View File

@@ -79,11 +79,8 @@ class NotificationMailer
continue;
}
$email = new Email();
$email
->subject($notification->getTitle());
if ($notification->isSystem()) {
$email = new Email();
$email
->text($notification->getMessage());
} else {
@@ -96,7 +93,9 @@ class NotificationMailer
]);
}
$email->to($addressee->getEmail());
$email
->subject($notification->getTitle())
->to($addressee->getEmail());
try {
$this->mailer->send($email);

View File

@@ -110,7 +110,8 @@ class EntityWorkflowRepository implements ObjectRepository
$qb->where(
$qb->expr()->andX(
$qb->expr()->isMemberOf(':user', 'step.destUser'),
$qb->expr()->isNull('step.transitionAfter')
$qb->expr()->isNull('step.transitionAfter'),
$qb->expr()->eq('step.isFinal', "'FALSE'")
)
);

View File

@@ -0,0 +1,79 @@
<?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\EntityWorkflowStep;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Doctrine\Persistence\ObjectRepository;
class EntityWorkflowStepRepository implements ObjectRepository
{
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(EntityWorkflowStep::class);
}
public function countUnreadByUser(User $user, ?array $orderBy = null, $limit = null, $offset = null): int
{
$qb = $this->buildQueryByUser($user)->select('count(e)');
return (int) $qb->getQuery()->getSingleScalarResult();
}
public function find($id): ?EntityWorkflowStep
{
return $this->repository->find($id);
}
public function findAll(): array
{
return $this->repository->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria): ?EntityWorkflowStep
{
return $this->repository->findOneBy($criteria);
}
public function getClassName()
{
return EntityWorkflowStep::class;
}
private function buildQueryByUser(User $user): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('e');
$qb->where(
$qb->expr()->andX(
$qb->expr()->isMemberOf(':user', 'e.destUser'),
$qb->expr()->isNull('e.transitionAt'),
$qb->expr()->eq('e.isFinal', ':bool'),
)
);
$qb->setParameter('user', $user);
$qb->setParameter('bool', false);
return $qb;
}
}

View File

@@ -25,6 +25,7 @@ import { chill } from './js/chill.js';
global.chill = chill;
require('./js/date.js');
require('./js/counter.js');
/// Load fonts
require('./fonts/OpenSans/OpenSans.scss')

View File

@@ -0,0 +1,35 @@
/**
*
* This script search for span.counter elements like
* <span class="counter">Il y a 4 notifications</span>
* and return
* <span class="counter">Il y a <span>4</span> notifications</span>
*
*/
const isNum = (v) => !isNaN(v);
const parseCounter = () => {
document.querySelectorAll('span.counter')
.forEach(el => {
let r = [];
el.innerText
.trim()
.split(' ')
.forEach(w => {
if (isNum(w)) {
r.push(`<span>${w}</span>`);
} else {
r.push(w);
}
})
;
el.innerHTML = r.join(' ');
})
;
};
window.addEventListener('DOMContentLoaded', function (e) {
parseCounter();
});
export { parseCounter };

View File

@@ -15,6 +15,7 @@ $chill-theme-buttons: (
"action": $chill-orange,
"edit": $chill-orange,
"update": $chill-orange,
"wopilink": $chill-orange,
"show": $chill-blue,
"view": $chill-blue,
"misc": $gray-300,
@@ -54,6 +55,7 @@ $chill-theme-buttons: (
&.btn-action,
&.btn-edit,
&.btn-tpchild,
&.btn-wopilink,
&.btn-update {
&, &:hover {
color: $light;
@@ -66,6 +68,7 @@ $chill-theme-buttons: (
&.btn-create::before,
&.btn-edit::before,
&.btn-update::before,
&.btn-wopilink::before,
&.btn-show::before,
&.btn-view::before,
&.btn-save::before,
@@ -98,6 +101,7 @@ $chill-theme-buttons: (
&.btn-create::before { content: "\f067"; } // fa-plus
&.btn-edit::before,
&.btn-update::before { content: "\f040"; } // fa-pencil
&.btn-wopilink::before { content: "\f1dd"; } // fa-paragraph
&.btn-show::before,
&.btn-view::before { content: "\f06e"; } // fa-eye
&.btn-save::before { content: "\f0c7"; } // fa-floppy-o

View File

@@ -82,7 +82,7 @@ div#notification-fold {
// Counter
div.notification-counter {
span {
span.counter {
&:not(:first-child) {
&::before {
content: '/ ';
@@ -90,3 +90,11 @@ div.notification-counter {
}
}
}
span.counter {
& > span {
font-weight: bold;
background-color: $chill-ll-gray;
padding: 0 0.4rem;
border-radius: 50%;
}
}

View File

@@ -1,19 +1,19 @@
/**
* Create a control to show or hide values
*
*
* Possible options are:
*
*
* - froms: an Element, an Array of Element, or a NodeList. A
* listener will be attached to **all** input of those elements
* 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.
* 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.
* - 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
* - event_name: the name of the event to listen to. `'change'` by default.
*
*
* @param object options
*/
var ShowHide = function(options) {
@@ -26,8 +26,10 @@ var ShowHide = function(options) {
container_content = [],
debug = 'debug' in options ? options.debug : false,
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) {
if (debug) {
console.log('debug is activated on this show-hide', this);
@@ -39,15 +41,14 @@ var ShowHide = function(options) {
contents.push(el);
}
container_content.push(contents);
// console.log('container content', container_content);
}
// attach the listener on each input
for (let f of froms.values()) {
let
inputs = f.querySelectorAll('input'),
let
inputs = f.querySelectorAll('input'),
selects = f.querySelectorAll('select');
for (let input of inputs.values()) {
if (debug) {
console.log('attaching event to input', input);
@@ -67,10 +68,10 @@ var ShowHide = function(options) {
}
// first launch of the show/hide
onChange(event);
onChange(event);
};
var onChange = function (event) {
var result = test(froms, event), me;
@@ -89,45 +90,53 @@ var ShowHide = function(options) {
} else {
throw "the result of test is not a boolean";
}
};
var forceHide = function() {
if (debug) {
console.log('force hide');
}
for (let contents of container_content.values()) {
for (let el of contents.values()) {
el.remove();
if (toggle_callback !== null) {
toggle_callback(container, 'hide');
} else {
for (let contents of container_content.values()) {
for (let el of contents.values()) {
el.remove();
}
}
}
is_shown = false;
};
var forceShow = function() {
if (debug) {
console.log('show');
}
for (let i of container_content.keys()) {
var contents = container_content[i];
for (let el of contents.values()) {
container[i].appendChild(el);
if (toggle_callback !== null) {
toggle_callback(container, 'show');
} else {
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;
};
var forceCompute = function(event) {
onChange(event);
};
if (load_event !== null) {
window.addEventListener('load', bootstrap);
} else {
bootstrap(null);
}
return {
forceHide: forceHide,
forceShow: forceShow,

View File

@@ -17,7 +17,12 @@ function loadDynamicPicker(element) {
isMultiple = parseInt(el.dataset.multiple) === 1,
uniqId = el.dataset.uniqid,
input = element.querySelector('[data-input-uniqid="'+ el.dataset.uniqid +'"]'),
picked = (isMultiple) ? (JSON.parse(input.value)) : ((input.value === '[]') ? (null) : ([JSON.parse(input.value)]));
picked = isMultiple ?
JSON.parse(input.value) : (
(input.value === '[]' || input.value === '') ?
null : [ JSON.parse(input.value) ]
)
;
if (!isMultiple) {
if (input.value === '[]'){

View File

@@ -4,20 +4,34 @@ import {_createI18n} from "ChillMainAssets/vuejs/_js/i18n";
const i18n = _createI18n({});
//TODO move to chillDocStore or ChillWopi
/*
tags to load module:
<span data-module="wopi-link"
data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.uuid}) }}"
data-doc-type="{{ document.type|e('html_attr') }}"
data-options="{{ options|json_encode }}"
></span>
*/
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>',
template: '<open-wopi-link :wopiUrl="wopiUrl" :type="type" :options="options"></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) : {}
options: el.dataset.options !== 'null' ? JSON.parse(el.dataset.options) : {}
}
}
})
@@ -26,4 +40,4 @@ window.addEventListener('DOMContentLoaded', function (e) {
;
})
;
});
});

View File

@@ -2,29 +2,89 @@ import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js';
window.addEventListener('DOMContentLoaded', function() {
let
finalizeAfterContainer = document.querySelector('#finalizeAfter'),
divTransitions = document.querySelector('#transitions'),
futureDestUsersContainer = document.querySelector('#futureDestUsers')
;
if (null === finalizeAfterContainer) {
return;
}
new ShowHide({
load_event: null,
froms: [finalizeAfterContainer],
container: [futureDestUsersContainer],
test: function(containers, arg2, arg3) {
for (let container of containers) {
for (let input of container.querySelectorAll('input')) {
if (!input.checked) {
return true;
} else {
return false;
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;
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;
}
}
},
});
});
}
// validate form
let form = document.querySelector('form[name="workflow_step"]');
if (form === null) {
console.error('form to validate not found');
}
form.addEventListener('submit', function (event) {
const datas = new FormData(event.target);
if (datas.has('workflow_step[future_dest_users]')) {
const dests = JSON.parse(datas.get('workflow_step[future_dest_users]'));
if (dests === null || (dests instanceof Array && dests.length === 0)) {
event.preventDefault();
console.log('no users!');
window.alert('Indiquez un utilisateur pour traiter la prochaine étape.');
}
}
});
});

View File

@@ -10,6 +10,7 @@
:deselect-label="$t('create_address')"
:selected-label="$t('multiselect.selected_label')"
@search-change="listenInputSearch"
:internal-search="false"
ref="addressSelector"
@select="selectAddress"
@remove="remove"

View File

@@ -18,6 +18,7 @@
:selected-label="$t('multiselect.selected_label')"
:taggable="true"
:multiple="false"
:internal-search="false"
@tag="addPostcode"
:tagPlaceholder="$t('create_postal_code')"
:loading="isLoading"

View File

@@ -3,16 +3,38 @@
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">id</th>
<th scope="col">Ouvert le</th>
<th scope="col">Usagers concernés</th>
<th scope="col">{{ $t('opening_date') }}</th>
<th scope="col">{{ $t('social_issues') }}</th>
<th scope="col">{{ $t('concerned_persons') }}</th>
<th scope="col"></th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
<td>{{ c.id}}</td>
<td>{{ $d(c.openingDate.datetime, 'long') }}</td>
<td>{{ c.participations.length }}</td>
<td>{{ $d(c.openingDate.datetime, 'short') }}</td>
<td>
<span v-for="i in c.socialIssues"
class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{ i.title.fr }}
</span>
</span>
</td>
<td>
<span v-for="p in c.participations" class="me-1" :key="p.person.id">
<on-the-fly
:type="p.person.type"
:id="p.person.id"
:buttonText="p.person.textAge"
:displayBadge="'true' === 'true'"
action="show">
</on-the-fly>
</span>
</td>
<td>
<span v-if="c.emergency" class="badge rounded-pill bg-danger me-1">{{ $t('emergency') }}</span>
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{ $t('confidential') }}</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(c)">
{{ $t('show_entity', { entity: $t('the_course') }) }}
@@ -26,11 +48,13 @@
<script>
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
export default {
name: "MyAccompanyingCourses",
components: {
TabTable
TabTable,
OnTheFly,
},
computed: {
...mapState([
@@ -56,5 +80,7 @@ export default {
</script>
<style scoped>
span.badge.rounded-pill.bg-danger {
text-transform: uppercase;
}
</style>

View File

@@ -6,22 +6,34 @@
<div class="custom1">
<ul class="list-unstyled">
<li v-if="counter.notifications > 0">
<b>{{ counter.notifications }}</b> {{ $t('counter.unread_notifications') }}
<i18n-t keypath="counter.unread_notifications" tag="span" :class="counterClass" :plural="counter.notifications">
<template v-slot:n><span>{{ counter.notifications }}</span></template>
</i18n-t>
</li>
<li v-if="counter.accompanyingCourses > 0">
<b>{{ counter.accompanyingCourses }}</b> {{ $t('counter.assignated_courses') }}
<i18n-t keypath="counter.assignated_courses" tag="span" :class="counterClass" :plural="counter.accompanyingCourses">
<template v-slot:n><span>{{ counter.accompanyingCourses }}</span></template>
</i18n-t>
</li>
<li v-if="counter.works > 0">
<b>{{ counter.works }}</b> {{ $t('counter.assignated_actions') }}
<i18n-t keypath="counter.assignated_actions" tag="span" :class="counterClass" :plural="counter.works">
<template v-slot:n><span>{{ counter.works }}</span></template>
</i18n-t>
</li>
<li v-if="counter.evaluations > 0">
<b>{{ counter.evaluations }}</b> {{ $t('counter.assignated_evaluations') }}
<i18n-t keypath="counter.assignated_evaluations" tag="span" :class="counterClass" :plural="counter.evaluations">
<template v-slot:n><span>{{ counter.evaluations }}</span></template>
</i18n-t>
</li>
<li v-if="counter.tasksAlert > 0">
<b>{{ counter.tasksAlert }}</b> {{ $t('counter.alert_tasks') }}
<i18n-t keypath="counter.alert_tasks" tag="span" :class="counterClass" :plural="counter.tasksAlert">
<template v-slot:n><span>{{ counter.tasksAlert }}</span></template>
</i18n-t>
</li>
<li v-if="counter.tasksWarning > 0">
<b>{{ counter.tasksWarning }}</b> {{ $t('counter.warning_tasks') }}
<i18n-t keypath="counter.warning_tasks" tag="span" :class="counterClass" :plural="counter.tasksWarning">
<template v-slot:n><span>{{ counter.tasksWarning }}</span></template>
</i18n-t>
</li>
</ul>
</div>
@@ -54,6 +66,13 @@ import Masonry from 'masonry-layout/masonry';
export default {
name: "MyCustoms",
data() {
return {
counterClass: {
counter: true //hack to pass class 'counter' in i18n-t
}
}
},
computed: {
...mapGetters(['counter']),
noResults() {
@@ -67,11 +86,16 @@ export default {
}
</script>
<style scoped>
<style lang="scss" scoped>
div.custom4,
div.custom3,
div.custom2 {
font-style: italic;
color: var(--bs-chill-gray);
}
span.counter {
& > span {
background-color: unset;
}
}
</style>

View File

@@ -1,32 +1,71 @@
<template>
<div class="accompanying_course_work">
<div class="alert alert-light">{{ $t('my_evaluations.description') }}</div>
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">id</th>
<th scope="col">{{ $t('max_date') }}</th>
<th scope="col">{{ $t('evaluation') }}</th>
<th scope="col">{{ $t('SocialAction') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
<td>{{ e.id}}</td>
<td>{{ $d(e.maxDate.datetime, 'short') }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(e)">
{{ $t('show_entity', { entity: $t('the_evaluation') }) }}
</a>
{{ e.evaluation.title.fr }}
</td>
<td>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{ e.accompanyingPeriodWork.socialAction.issue.text }}
</span>
</span>
<h4 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ e.accompanyingPeriodWork.socialAction.text }}
</span>
</h4>
<span v-for="person in e.accompanyingPeriodWork.persons" class="me-1" :key="person.id">
<on-the-fly
:type="person.type"
:id="person.id"
:buttonText="person.textAge"
:displayBadge="'true' === 'true'"
action="show">
</on-the-fly>
</span>
</td>
<td>
<div class="btn-group-vertical" role="group" aria-label="Actions">
<a class="btn btn-sm btn-show" :href="getUrl(e)">
{{ $t('show_entity', { entity: $t('the_evaluation') }) }}
</a>
<a class="btn btn-sm btn-update" :href="getUrl(e.accompanyingPeriodWork)">
{{ $t('show_entity', { entity: $t('the_action') }) }}
</a>
<a class="btn btn-sm btn-show" :href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod)">
{{ $t('show_entity', { entity: $t('the_course') }) }}
</a>
</div>
</td>
</tr>
</template>
</tab-table>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
export default {
name: "MyEvaluations",
components: {
TabTable
TabTable,
OnTheFly,
},
computed: {
...mapState([
@@ -45,8 +84,17 @@ export default {
},
methods: {
getUrl(e) {
let anchor = '#evaluations';
return `/fr/person/accompanying-period/work/${e.id}/edit${anchor}`
switch (e.type) {
case 'accompanying_period_work_evaluation':
let anchor = '#evaluations';
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork.id}/edit${anchor}`;
case 'accompanying_period_work':
return `/fr/person/accompanying-period/work/${e.id}/edit`
case 'accompanying_period':
return `/fr/parcours/${e.id}`
default:
throw 'entity type unknown';
}
}
},
}

View File

@@ -17,7 +17,8 @@
<a :href="getNotificationUrl(n)">{{ n.title }}</a>
</span>
</td>
<td>{{ n.sender.text }}</td>
<td v-if="n.sender != null">{{ n.sender.text }}</td>
<td v-else>{{ $t('automatic_notification')}}</td>
<td>
<a class="btn btn-sm btn-show"
:href="getEntityUrl(n)">
@@ -65,6 +66,8 @@ export default {
return appMessages.fr.the_activity;
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
return appMessages.fr.the_course;
case 'Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow':
return appMessages.fr.workflow;
default:
throw 'notification type unknown';
}
@@ -75,6 +78,8 @@ export default {
return `/fr/activity/${n.relatedEntityId}/show`
case 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod':
return `/fr/parcours/${n.relatedEntityId}`
case 'Chill\\MainBundle\\Entity\\Workflow\\EntityWorkflow':
return `/fr/main/workflow/${n.relatedEntityId}/show`
default:
throw 'notification type unknown';
}

View File

@@ -1,15 +1,21 @@
<template>
<div class="alert alert-light">{{ $t('my_tasks.description_alert') }}</div>
<span v-if="noResultsWarning" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<div class="alert alert-light">{{ $t('my_tasks.description_warning') }}</div>
<span v-if="noResultsAlert" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">id</th>
<th scope="col">{{ $t('warning_date') }}</th>
<th scope="col">{{ $t('max_date') }}</th>
<th scope="col">{{ $t('task') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(t, i) in tasks.warning" :key="`task-warning-${i}`">
<td>{{ t.id}}</td>
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
<td>{{ $d(t.warningDate.datetime, 'short') }}</td>
<td>
<span class="outdated">{{ $d(t.endDate.datetime, 'short') }}</span>
</td>
<td>{{ t.title }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t('show_entity', { entity: $t('the_task') }) }}
@@ -19,16 +25,22 @@
</template>
</tab-table>
<div class="alert alert-light">{{ $t('my_tasks.description_warning') }}</div>
<span v-if="noResultsAlert" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<div class="alert alert-light">{{ $t('my_tasks.description_alert') }}</div>
<span v-if="noResultsWarning" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">id</th>
<th scope="col">{{ $t('warning_date') }}</th>
<th scope="col">{{ $t('max_date') }}</th>
<th scope="col">{{ $t('task') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(t, i) in tasks.alert" :key="`task-alert-${i}`">
<td>{{ t.id}}</td>
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
<td>
<span class="outdated">{{ $d(t.warningDate.datetime, 'short') }}</span>
</td>
<td>{{ $d(t.endDate.datetime, 'short') }}</td>
<td>{{ t.title }}</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(t)">
{{ $t('show_entity', { entity: $t('the_task') }) }}
@@ -81,5 +93,8 @@ export default {
</script>
<style scoped>
span.outdated {
font-weight: bold;
color: var(--bs-warning);
}
</style>

View File

@@ -6,12 +6,18 @@
<template v-slot:thead>
<th scope="col">{{ $t('StartDate') }}</th>
<th scope="col">{{ $t('SocialAction') }}</th>
<th scope="col">{{ $t('concerned_persons') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(w, i) in works.results" :key="`works-${i}`">
<td>{{ $d(w.startDate.datetime, 'short') }}</td>
<td>
<span class="chill-entity entity-social-issue">
<span class="badge bg-chill-l-gray text-dark">
{{ w.socialAction.issue.text }}
</span>
</span>
<h4 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
@@ -20,7 +26,18 @@
</h4>
</td>
<td>
<div class="btn-group" role="group" aria-label="Actions">
<span v-for="person in w.persons" class="me-1" :key="person.id">
<on-the-fly
:type="person.type"
:id="person.id"
:buttonText="person.textAge"
:displayBadge="'true' === 'true'"
action="show">
</on-the-fly>
</span>
</td>
<td>
<div class="btn-group-vertical" role="group" aria-label="Actions">
<a class="btn btn-sm btn-update" :href="getUrl(w)">
{{ $t('show_entity', { entity: $t('the_action') }) }}
</a>
@@ -38,11 +55,13 @@
<script>
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
export default {
name: "MyWorks",
components: {
TabTable
TabTable,
OnTheFly,
},
computed: {
...mapState([

View File

@@ -1,6 +1,6 @@
<template>
<span v-if="isCounterAvailable"
class="badge rounded-pill bg-danger counter">
class="badge rounded-pill bg-danger">
{{ count }}
</span>
</template>

View File

@@ -22,8 +22,15 @@ const appMessages = {
tab: "Mes notifications",
description: "Liste des notifications reçues et non lues.",
},
opening_date: "Date d'ouverture",
social_issues: "Problématiques sociales",
concerned_persons: "Usagers concernés",
max_date: "Date d'échéance",
warning_date: "Date de rappel",
evaluation: "Évaluation",
task: "Tâche",
Date: "Date",
From: "De",
From: "Expéditeur",
Subject: "Objet",
Entity: "Associé à",
show_entity: "Voir {entity}",
@@ -32,18 +39,22 @@ const appMessages = {
the_action: "l'action",
the_evaluation: "l'évaluation",
the_task: "la tâche",
workflow: "le workflow",
StartDate: "Date d'ouverture",
SocialAction: "Action d'accompagnement",
no_data: "Aucun résultats",
no_dashboard: "Pas de tableaux de bord",
counter: {
unread_notifications: "notifications non lues",
assignated_courses: "parcours récents assignés",
assignated_actions: "actions assignées",
assignated_evaluations: "évaluations assignées",
alert_tasks: "tâches en rappel",
warning_tasks: "tâches à échéances",
}
unread_notifications: "{n} notification non lue | {n} notifications non lues",
assignated_courses: "{n} parcours récent assigné | {n} parcours récents assignés",
assignated_actions: "{n} action assignée | {n} actions assignées",
assignated_evaluations: "{n} évaluation assignée | {n} évaluations assignées",
alert_tasks: "{n} tâche en rappel | {n} tâches en rappel",
warning_tasks: "{n} tâche à échéance | {n} tâches à échéance",
},
emergency: "Urgent",
confidential: "Confidentiel",
automatic_notification: "Notification automatique"
}
};

View File

@@ -62,27 +62,27 @@ const store = createStore({
},
mutations: {
addWorks(state, works) {
console.log('addWorks', works);
//console.log('addWorks', works);
state.works = works;
},
addEvaluations(state, evaluations) {
console.log('addEvaluations', evaluations);
//console.log('addEvaluations', evaluations);
state.evaluations = evaluations;
},
addTasksWarning(state, tasks) {
console.log('addTasksWarning', tasks);
//console.log('addTasksWarning', tasks);
state.tasks.warning = tasks;
},
addTasksAlert(state, tasks) {
console.log('addTasksAlert', tasks);
//console.log('addTasksAlert', tasks);
state.tasks.alert = tasks;
},
addCourses(state, courses) {
console.log('addCourses', courses);
//console.log('addCourses', courses);
state.accompanyingCourses = courses;
},
addNotifications(state, notifications) {
console.log('addNotifications', notifications);
//console.log('addNotifications', notifications);
state.notifications = notifications;
},
setLoading(state, bool) {

View File

@@ -90,7 +90,7 @@ export default {
OnTheFlyThirdparty,
OnTheFlyCreate
},
props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'isDead', 'parent', 'canCloseModal'],
props: ['type', 'id', 'action', 'buttonText', 'displayBadge', 'isDead', 'parent'],
emits: ['saveFormOnTheFly'],
data() {
return {
@@ -160,17 +160,10 @@ export default {
},
badgeType() {
return 'entity-' + this.type + ' badge-' + this.type;
}
},
watch: {
canCloseModal: {
handler: function(val, oldVal) {
if (val) {
this.closeModal();
}
},
deep: true
}
},
getReturnPath() {
return `?returnPath=${window.location.pathname}${window.location.search}${window.location.hash}`;
},
},
methods: {
closeModal() {
@@ -217,9 +210,9 @@ export default {
buildLocation(id, type) {
if (type === 'person') {
// TODO i18n
return `/fr/person/${id}/general`;
return encodeURI(`/fr/person/${id}/general${this.getReturnPath}`);
} else if (type === 'thirdparty') {
return `/fr/3party/3party/${id}/view`;
return encodeURI(`/fr/3party/3party/${id}/view${this.getReturnPath}`);
}
}
}

View File

@@ -5,8 +5,8 @@
</li>
</ul>
<ul class="record_actions">
<li>
<AddPersons
<li class="add-persons">
<add-persons
:options="addPersonsOptions"
:key="uniqid"
:buttonTitle="translatedListOfTypes"
@@ -14,7 +14,7 @@
ref="addPersons"
@addNewPersons="addNewEntity"
>
</AddPersons>
</add-persons>
</li>
</ul>
</template>

View File

@@ -1,4 +1,7 @@
import { personMessages } from 'ChillPersonAssets/vuejs/_js/i18n';
import { thirdpartyMessages } from 'ChillThirdPartyAssets/vuejs/_js/i18n';
import { addressMessages } from 'ChillMainAssets/vuejs/Address/i18n';
import { ontheflyMessages } from 'ChillMainAssets/vuejs/OnTheFly/i18n';
const appMessages = {
fr: {
@@ -12,6 +15,6 @@ const appMessages = {
}
}
Object.assign(appMessages.fr, personMessages.fr);
Object.assign(appMessages.fr, personMessages.fr, thirdpartyMessages.fr, addressMessages.fr, ontheflyMessages.fr );
export { appMessages };

View File

@@ -42,57 +42,11 @@
class="street">
{{ address.text }}
</p>
<p v-if="address.postcode"
class="postcode">
{{ address.postcode.code }} {{ address.postcode.name }}
</p>
<p v-if="address.country"
class="country">
{{ address.country.name.fr }}
</p>
</div>
</div>
</component>
<!-- <div v-if="isMultiline === true" class="address-more">
<div v-if="address.floor">
<span class="floor">
<b>{{ $t('floor') }}</b>: {{ address.floor }}
</span>
</div>
<div v-if="address.corridor">
<span class="corridor">
<b>{{ $t('corridor') }}</b>: {{ address.corridor }}
</span>
</div>
<div v-if="address.steps">
<span class="steps">
<b>{{ $t('steps') }}</b>: {{ address.steps }}
</span>
</div>
<div v-if="address.flat">
<span class="flat">
<b>{{ $t('flat') }}</b>: {{ address.flat }}
</span>
</div>
<div v-if="address.buildingName">
<span class="buildingName">
<b>{{ $t('buildingName') }}</b>: {{ address.buildingName }}
</span>
</div>
<div v-if="address.extra">
<span class="extra">
<b>{{ $t('extra') }}</b>: {{ address.extra }}
</span>
</div>
<div v-if="address.distribution">
<span class="distribution">
<b>{{ $t('distribution') }}</b>: {{ address.distribution }}
</span>
</div>
</div> -->
<div v-if="useDatePane === true" class="address-more">
<div v-if="address.validFrom">
<span class="validFrom">

View File

@@ -93,8 +93,9 @@ export default {
},
getPopTitle(step) {
if (step.transitionPrevious != null) {
//console.log(step.transitionPrevious.text);
let freezed = step.isFreezed ? `<i class="fa fa-snowflake-o fa-sm me-1"></i>` : ``;
return `${freezed}${step.currentStep.text}`;
return `${freezed}${step.transitionPrevious.text}`;
}
},
getPopContent(step) {

View File

@@ -1,5 +1,5 @@
<template>
<button v-if="hasWorkflow"
class="btn btn-primary"
@click="openModal">
@@ -7,36 +7,39 @@
<template v-if="countWorkflows > 1">{{ $t('workflows') }}</template>
<template v-else>{{ $t('workflow') }}</template>
</button>
<pick-workflow v-else-if="allowCreate"
:relatedEntityClass="this.relatedEntityClass"
:relatedEntityId="this.relatedEntityId"
:workflowsAvailables="workflowsAvailables"
@go-to-generate-workflow="goToGenerateWorkflow"
></pick-workflow>
<teleport to="body">
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="modal.showModal = false">
<template v-slot:header>
<h2 class="modal-title">{{ $t('workflow_list') }}</h2>
</template>
<template v-slot:body>
<list-workflow-vue
:workflows="workflows"
></list-workflow-vue>
</template>
<template v-slot:footer>
<pick-workflow v-if="allowCreate"
:relatedEntityClass="this.relatedEntityClass"
:relatedEntityId="this.relatedEntityId"
:workflowsAvailables="workflowsAvailables"
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
@go-to-generate-workflow="this.goToGenerateWorkflow"
></pick-workflow>
</template>
</modal>
</teleport>
</template>
@@ -53,6 +56,7 @@ export default {
PickWorkflow,
ListWorkflowVue
},
emits: ['goToGenerateWorkflow'],
props: {
workflows: {
type: Array,
@@ -73,9 +77,14 @@ export default {
workflowsAvailables: {
type: Array,
required: true,
}
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
},
data() {
data() {
return {
modal: {
showModal: false,
@@ -95,6 +104,10 @@ export default {
openModal() {
this.modal.showModal = true;
},
goToGenerateWorkflow(data) {
console.log('go to generate workflow intercepted');
this.$emit('goToGenerateWorkflow', data);
}
},
i18n: {
messages: {
@@ -108,4 +121,4 @@ export default {
}
</script>
<style scoped></style>
<style scoped></style>

View File

@@ -6,7 +6,7 @@
</button>
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
<li v-for="w in workflowsAvailables" :key="w.name">
<a class="dropdown-item" :href="makeLink(w.name)" @click="goToGenerateWorkflow($event, w.name)">{{ w.text }}</a>
<a class="dropdown-item" :href="makeLink(w.name)" @click.prevent="goToGenerateWorkflow($event, w.name)">{{ w.text }}</a>
</li>
</ul>
</div>
@@ -31,7 +31,12 @@ export default {
workflowsAvailables: {
type: Array,
required: true,
}
},
preventDefaultMoveToGenerate: {
type: Boolean,
required: false,
default: false,
},
},
emits: ['goToGenerateWorkflow'],
methods: {
@@ -39,6 +44,13 @@ export default {
return buildLinkCreate(workflowName, this.relatedEntityClass, this.relatedEntityId);
},
goToGenerateWorkflow(event, workflowName) {
console.log('goToGenerateWorkflow', event, workflowName);
if (!this.$props.preventDefaultMoveToGenerate) {
console.log('to go generate');
window.location.assign(this.makeLink(workflowName));
}
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName)});
}
}

View File

@@ -1,12 +1,14 @@
<template>
<a v-if="isOpenDocument"
class="btn change-icon" :class="[isChangeClass ? button.changeClass : 'btn-edit']"
class="btn" :class="[
isChangeIcon ? 'change-icon' : '',
isChangeClass ? options.changeClass : 'btn-wopilink' ]"
@click="openModal">
<i class="fa me-2" :class="[isChangeIcon ? button.changeIcon : 'fa-pencil']"></i>
<i v-if="isChangeIcon" class="fa me-2" :class="options.changeIcon"></i>
<span v-if="!noText">
{{ $t('Update_document') }}
{{ $t('online_edit_document') }}
</span>
</a>
@@ -20,12 +22,14 @@
<template v-slot:header>
<img class="logo" :src="logo" height="45"/>
<span class="ms-auto me-3">
{{ this.title }}
<span v-if="options.title">{{ options.title }}</span>
</span>
<!--
<a class="btn btn-outline-light">
<i class="fa fa-save fa-fw"></i>
{{ $t('save_and_quit') }}
</a>
-->
</template>
<template v-slot:body>
@@ -71,15 +75,11 @@ export default {
type: String,
required: true
},
title: {
type: String,
required: true
},
type: {
type: String,
required: true
},
button: {
options: {
type: Object,
required: false
}
@@ -175,20 +175,20 @@ export default {
return false;
},
noText() {
if (typeof this.button.noText !== 'undefined') {
return this.button.noText === true;
if (typeof this.options.noText !== 'undefined') {
return this.options.noText === true;
}
return false;
},
isChangeIcon() {
if (typeof this.button.changeIcon !== 'undefined') {
return (!(this.button.changeIcon === null || this.button.changeIcon === ''))
if (typeof this.options.changeIcon !== 'undefined') {
return (!(this.options.changeIcon === null || this.options.changeIcon === ''))
}
return false;
},
isChangeClass() {
if (typeof this.button.changeClass !== 'undefined') {
return (!(this.button.changeClass === null || this.button.changeClass === ''))
if (typeof this.options.changeClass !== 'undefined') {
return (!(this.options.changeClass === null || this.options.changeClass === ''))
}
return false;
}
@@ -205,7 +205,7 @@ export default {
i18n: {
messages: {
fr: {
Update_document: "Modifier le document",
online_edit_document: "Éditer en ligne",
save_and_quit: "Enregistrer et quitter",
loading: "Chargement de l'éditeur en ligne",
invalid_title: "Format incompatible",

View File

@@ -91,11 +91,11 @@ export const multiSelectMessages = {
multiselect: {
placeholder: 'Choisir',
tag_placeholder: 'Créer un nouvel élément',
select_label: 'Appuyer sur "Entrée" pour sélectionner',
deselect_label: 'Appuyer sur "Entrée" pour désélectionner',
select_label: '"Entrée" ou cliquez pour sélectionner',
deselect_label: '"Entrée" ou cliquez pour désélectionner',
select_group_label: 'Appuyer sur "Entrée" pour sélectionner ce groupe',
deselect_group_label: 'Appuyer sur "Entrée" pour désélectionner ce groupe',
selected_label: 'Sélectionné'
}
}
};
};

View File

@@ -15,11 +15,11 @@
<div class="notification-comment-list my-5">
<h2 class="chill-blue">{{ 'notification.comments_list'|trans }}</h2>
{% if notification.comments|length > 0 %}
<div class="flex-table">
{% for comment in notification.comments %}
{% if editedCommentForm is null or editedCommentId != comment.id %}
{{ m.show_comment(comment, {
'recordAction': _self.recordAction(comment)
@@ -28,10 +28,11 @@
<div class="item-bloc">
<div class="item-row row">
<a id="comment-{{ comment.id }}"></a>
{{ form_start(editedCommentForm) }}
{{ form_errors(editedCommentForm) }}
{{ form_widget(editedCommentForm.content) }}
{{ form_errors(editedCommentForm.content) }}
<input type="hidden" name="form" value="edit" />
<ul class="record_actions">
<li class="cancel">
@@ -46,24 +47,25 @@
</li>
</ul>
{{ form_end(editedCommentForm) }}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% else %}
<span class="chill-no-data-statement">{{ 'No comments'|trans }}</span>
{% endif %}
{% if appendCommentForm is not null %}
<div class="new-comment my-5">
<h2 class="chill-blue mb-4">{{ 'Write a new comment'|trans }}</h2>
{{ form_start(appendCommentForm) }}
{{ form_errors(appendCommentForm) }}
{{ form_widget(appendCommentForm.content) }}
{{ form_errors(appendCommentForm.content) }}
<input type="hidden" name="form" value="append" />
<ul class="record_actions">
<li>
@@ -71,7 +73,7 @@
</li>
</ul>
{{ form_end(appendCommentForm) }}
</div>
{% endif %}
</div>
</div>

View File

@@ -72,10 +72,13 @@
<div class="item-row separator">
<div class="item-col item-meta">
{# TODO twig extension to count comments #}
<div class="comment-counter visually-hidden">
<span>x commentaires</span>
</div>
{% if c.notification.comments|length > 0 %}
<div class="comment-counter">
<span class="counter">
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
</span>
</div>
{% endif %}
</div>
<div class="item-col">

View File

@@ -1,11 +1,11 @@
<div class="notification-counter">
{% if counter.total > 0 %}
<span>
<span class="counter">
{{ 'notification.counter total notifications'|trans({'total': counter.total }) }}
</span>
{% endif %}
{% if counter.unread > 0 %}
<span>
<span class="counter">
{{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }}
</span>
{% endif %}

View File

@@ -3,7 +3,58 @@
{% if transition_form is not null %}
{{ form_start(transition_form) }}
{{ form_row(transition_form.transition) }}
{% set step = entity_workflow.currentStepChained %}
{% set labels = workflow_metadata(entity_workflow, 'label', step.currentStep) %}
{% set label = labels is null ? step.currentStep : labels|localize_translatable_string %}
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-sm-12">
{{ 'workflow.Current step'|trans }}&nbsp;:
<span class="badge bg-primary">{{ label }}</span>
</div>
</div>
{% if step.previous is not null %}
{% if step.previous.comment is not empty %}
<div class="row">
<div class="col-sm-12">
<blockquote class="chill-user-quote">
{{ step.previous.comment|chill_markdown_to_html }}
</blockquote>
</div>
</div>
{% endif %}
<div class="row">
<div class="col-sm-12">
{{ 'By'|trans }}
{{ step.previous.transitionBy|chill_entity_render_box }},
{{ step.previous.transitionAt|format_datetime('short', 'short') }}
</div>
</div>
{% else %}
<div class="row">
<div class="col-sm-4">{{ 'workflow.Created by'|trans }}</div>
<div class="col-sm-8">{{ step.entityWorkflow.createdBy|chill_entity_render_box }}</div>
</div>
<div class="row">
<div class="col-sm-4">{{ 'Le'|trans }}</div>
<div class="col-sm-8">{{ step.entityWorkflow.createdAt|format_datetime('short', 'short') }}</div>
</div>
{% endif %}
</div>
</div>
<div id="transitionFilter">
{% if transition_form.transitionFilter is defined %}
{{ form_row(transition_form.transitionFilter) }}
{% endif %}
</div>
<div id="transitions">
{{ form_row(transition_form.transition) }}
</div>
{% if transition_form.freezeAfter is defined %}
{{ form_row(transition_form.freezeAfter) }}

View File

@@ -69,6 +69,18 @@
</blockquote>
</div>
{% endif %}
{% if loop.last and step.destUser|length > 0 %}
<div class="item-row separator">
<div>
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }}&nbsp;: </b></p>
<ul>
{% for u in step.destUser %}
<li>{{ u|chill_entity_render_box }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
{% endfor %}

View File

@@ -2,7 +2,7 @@
<h2>
{{ 'workflow_'|trans }}
</h2>
{% include handler.templateTitle(l.entity_workflow) with handler.templateTitleData(entity_workflow)|merge({
{% include handler.templateTitle(entity_workflow) with handler.templateTitleData(entity_workflow)|merge({
'description': true,
'breadcrumb': true,
'add_classes': 'ms-3 h3'

View File

@@ -6,7 +6,6 @@
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{{ encore_entry_script_tags('mod_entity_workflow_subscribe') }}

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Routing\MenuBuilder;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Counter\NotificationByUserCounter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Workflow\Counter\WorkflowByUserCounter;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -25,12 +26,16 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
private TranslatorInterface $translator;
private WorkflowByUserCounter $workflowByUserCounter;
public function __construct(
NotificationByUserCounter $notificationByUserCounter,
WorkflowByUserCounter $workflowByUserCounter,
Security $security,
TranslatorInterface $translator
) {
$this->notificationByUserCounter = $notificationByUserCounter;
$this->workflowByUserCounter = $workflowByUserCounter;
$this->security = $security;
$this->translator = $translator;
}
@@ -69,9 +74,11 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'counter' => $nbNotifications,
]);
$workflowCount = $this->workflowByUserCounter->getCountUnreadByUser($user);
$menu
->addChild(
$this->translator->trans('workflow.My workflows'),
$this->translator->trans('workflow.My workflows with counter', ['wc' => $workflowCount]),
['route' => 'chill_main_workflow_list_dest']
)
->setExtras([

View File

@@ -64,7 +64,7 @@ class AddressNormalizer implements ContextAwareNormalizerInterface, NormalizerAw
if ($address instanceof Address) {
$data = [
'address_id' => $address->getId(),
'text' => $address->isNoAddress() ? null : $this->addressRender->renderStreetLine($address, []),
'text' => $this->addressRender->renderString($address, []),
'street' => $address->getStreet(),
'streetNumber' => $address->getStreetNumber(),
'postcode' => [

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Serializer\Normalizer;
use LogicException;
use Symfony\Component\Serializer\Exception\NotNormalizableValueException;
use Symfony\Component\Serializer\Exception\RuntimeException;
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
@@ -44,7 +45,7 @@ class DiscriminatedObjectDenormalizer implements ContextAwareDenormalizerInterfa
if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) {
try {
return $this->denormalizer->denormalize($data, $localType, $format, $context);
} catch (RuntimeException $e) {
} catch (RuntimeException|NotNormalizableValueException $e) {
$lastException = $e;
}
}

View File

@@ -44,7 +44,6 @@ class NotificationNormalizer implements NormalizerAwareInterface, NormalizerInte
*/
public function normalize($object, ?string $format = null, array $context = [])
{
dump($object);
$entity = $this->entityManager
->getRepository($object->getRelatedEntityClass())
->find($object->getRelatedEntityId());

View File

@@ -124,7 +124,7 @@ class AddressRender implements ChillEntityRenderInterface
*/
public function renderString($addr, array $options): string
{
return implode(' - ', $this->renderLines($addr));
return implode(' ', $this->renderLines($addr));
}
public function supports($entity, array $options): bool

View File

@@ -24,30 +24,30 @@ final class EntityWorkflowTest extends TestCase
{
$entityWorkflow = new EntityWorkflow();
$entityWorkflow->getCurrentStep()->setFinalizeAfter(true);
$entityWorkflow->setStep('final');
$entityWorkflow->getCurrentStep()->setIsFinal(true);
$this->assertTrue($entityWorkflow->isFinalize());
$this->assertTrue($entityWorkflow->isFinal());
}
public function testIsFinalizeWith4Steps()
{
$entityWorkflow = new EntityWorkflow();
$this->assertFalse($entityWorkflow->isFinalize());
$this->assertFalse($entityWorkflow->isFinal());
$entityWorkflow->setStep('two');
$this->assertFalse($entityWorkflow->isFinalize());
$this->assertFalse($entityWorkflow->isFinal());
$entityWorkflow->setStep('previous_final');
$this->assertFalse($entityWorkflow->isFinalize());
$this->assertFalse($entityWorkflow->isFinal());
$entityWorkflow->getCurrentStep()->setFinalizeAfter(true);
$entityWorkflow->getCurrentStep()->setIsFinal(true);
$entityWorkflow->setStep('final');
$this->assertTrue($entityWorkflow->isFinalize());
$this->assertTrue($entityWorkflow->isFinal());
}
public function testIsFreeze()
@@ -64,11 +64,8 @@ final class EntityWorkflowTest extends TestCase
$this->assertFalse($entityWorkflow->isFreeze());
$entityWorkflow->getCurrentStep()->setFreezeAfter(true);
$this->assertFalse($entityWorkflow->isFreeze());
$entityWorkflow->setStep('freezed');
$entityWorkflow->getCurrentStep()->setFreezeAfter(true);
$this->assertTrue($entityWorkflow->isFreeze());

View File

@@ -48,7 +48,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setBuildingName('Résidence "Les Bleuets"');
yield [$addr, 'Résidence "Les Bleuets" - Rue ABC, 5 - 012345 Locality - Belgium'];
yield [$addr, 'Résidence "Les Bleuets" Rue ABC, 5 012345 Locality Belgium'];
}
public function addressDataProviderBEWithSteps(): Iterator
@@ -68,7 +68,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setSteps('4');
yield [$addr, 'esc 4 - Rue ABC, 5 - 012345 Locality - Belgium'];
yield [$addr, 'esc 4 Rue ABC, 5 012345 Locality Belgium'];
}
public function addressDataProviderFRWithBuilding(): Iterator
@@ -88,7 +88,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setBuildingName('Résidence "Les Bleuets"');
yield [$addr, 'Résidence "Les Bleuets" - 5, Rue ABC - 012345 Locality - France'];
yield [$addr, 'Résidence "Les Bleuets" 5, Rue ABC 012345 Locality France'];
}
public function addressDataProviderFRWithSteps(): Iterator
@@ -108,7 +108,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setSteps('4');
yield [$addr, 'esc 4 - 5, Rue ABC - 012345 Locality - France'];
yield [$addr, 'esc 4 5, Rue ABC 012345 Locality France'];
}
public function complexAddressDataProviderBE(): Iterator
@@ -132,7 +132,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setCorridor('3');
$addr->setSteps('4');
yield [$addr, 'Résidence "Les Bleuets" - appart 1 - ét 2 - coul 3 - esc 4 - Rue ABC, 5 - 012345 Locality - Belgium'];
yield [$addr, 'Résidence "Les Bleuets" - appart 1 - ét 2 - coul 3 - esc 4 Rue ABC, 5 012345 Locality Belgium'];
}
public function complexAddressDataProviderFR(): Iterator
@@ -158,7 +158,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setExtra('A droite de la porte');
$addr->setDistribution('CEDEX');
yield [$addr, 'appart 1 - ét 2 - coul 3 - esc 4 - Résidence "Les Bleuets" - 5, Rue ABC - A droite de la porte - 012345 Locality CEDEX - France'];
yield [$addr, 'appart 1 - ét 2 - coul 3 - esc 4 Résidence "Les Bleuets" 5, Rue ABC A droite de la porte 012345 Locality CEDEX France'];
}
public function noFullAddressDataProviderBE(): Iterator
@@ -175,7 +175,7 @@ final class AddressRenderTest extends KernelTestCase
$addr->setPostcode($postCode)
->setIsNoAddress(true);
yield [$addr, '012345 Locality - Belgium'];
yield [$addr, '012345 Locality Belgium'];
}
public function simpleAddressDataProviderBE(): Iterator
@@ -193,7 +193,7 @@ final class AddressRenderTest extends KernelTestCase
->setStreetNumber('5')
->setPostcode($postCode);
yield [$addr, 'Rue ABC, 5 - 012345 Locality - Belgium'];
yield [$addr, 'Rue ABC, 5 012345 Locality Belgium'];
}
public function simpleAddressDataProviderFR(): Iterator
@@ -211,7 +211,7 @@ final class AddressRenderTest extends KernelTestCase
->setStreetNumber('5')
->setPostcode($postCode);
yield [$addr, '5, Rue ABC - 012345 Locality - France'];
yield [$addr, '5, Rue ABC 012345 Locality France'];
}
/**

View File

@@ -0,0 +1,98 @@
<?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\Workflow\Counter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepRepository;
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Workflow\Event\Event;
final class WorkflowByUserCounter implements NotificationCounterInterface, EventSubscriberInterface
{
private CacheItemPoolInterface $cacheItemPool;
private EntityWorkflowStepRepository $workflowStepRepository;
public function __construct(EntityWorkflowStepRepository $workflowStepRepository, CacheItemPoolInterface $cacheItemPool)
{
$this->workflowStepRepository = $workflowStepRepository;
$this->cacheItemPool = $cacheItemPool;
}
public function addNotification(UserInterface $u): int
{
if (!$u instanceof User) {
return 0;
}
$key = self::generateCacheKeyWorkflowByUser($u);
$item = $this->cacheItemPool->getItem($key);
if ($item->isHit()) {
return $item->get();
}
$nb = $this->getCountUnreadByUser($u);
$item->set($nb)
->expiresAfter(60 * 15);
$this->cacheItemPool->save($item);
return $nb;
}
public static function generateCacheKeyWorkflowByUser(User $user): string
{
return 'chill_main_workflow_by_u_' . $user->getId();
}
public function getCountUnreadByUser(User $user): int
{
return $this->workflowStepRepository->countUnreadByUser($user);
}
public static function getSubscribedEvents()
{
return [
'workflow.leave' => 'resetWorkflowCache',
];
}
public function resetWorkflowCache(Event $event): void
{
if (!$event->getSubject() instanceof EntityWorkflow) {
return;
}
/** @var EntityWorkflow $entityWorkflow */
$entityWorkflow = $event->getSubject();
$step = $entityWorkflow->getCurrentStep();
if (null === $step) {
return;
}
$keys = [];
foreach ($step->getDestUser() as $user) {
$keys[] = self::generateCacheKeyWorkflowByUser($user);
}
if ([] !== $keys) {
$this->cacheItemPool->deleteItems($keys);
}
}
}

View File

@@ -72,17 +72,18 @@ class MetadataExtractor
foreach ($transitions as $transition) {
if ($transition->getName() === $transitionName) {
break;
$metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
return [
'name' => $transition->getName(),
'text' => array_key_exists('label', $metadata) ?
$this->translatableStringHelper->localize($metadata['label']) : $transition->getName(),
'isForward' => $metadata['isForward'] ?? null,
];
}
}
$metadata = $workflow->getMetadataStore()->getTransitionMetadata($transition);
return [
'name' => $transition->getName(),
'text' => array_key_exists('label', $metadata) ?
$this->translatableStringHelper->localize($metadata['label']) : $transition->getName(),
'isForward' => $metadata['isForward'] ?? null,
];
return [];
}
public function buildArrayPresentationForWorkflow(WorkflowInterface $workflow): array

View File

@@ -23,6 +23,12 @@ class WorkflowNotificationHandler implements NotificationHandlerInterface
private EntityWorkflowRepository $entityWorkflowRepository;
public function __construct(EntityWorkflowRepository $entityWorkflowRepository, EntityWorkflowManager $entityWorkflowManager)
{
$this->entityWorkflowRepository = $entityWorkflowRepository;
$this->entityWorkflowManager = $entityWorkflowManager;
}
public function getTemplate(Notification $notification, array $options = []): string
{
return '@ChillMain/Workflow/_notification_include.html.twig';

View File

@@ -0,0 +1,26 @@
<?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\Workflow\Validator;
use Symfony\Component\Validator\Constraint;
class StepDestValid extends Constraint
{
public string $messageDestNotAllowed = 'workflow.As the step is final, no dest are allowed';
public string $messageRequireDest = 'workflow.As the step is not final, dest are required';
public function getTargets()
{
return [self::CLASS_CONSTRAINT];
}
}

View File

@@ -0,0 +1,49 @@
<?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\Workflow\Validator;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use function count;
class StepDestValidValidator extends ConstraintValidator
{
/**
* @param EntityWorkflowStep $value
* @param Constraint|StepDestValid $constraint
*
* @return void
*/
public function validate($value, Constraint $constraint)
{
if (!$constraint instanceof StepDestValid) {
throw new UnexpectedTypeException($constraint, StepDestValid::class);
}
if (!$value instanceof EntityWorkflowStep) {
throw new UnexpectedValueException($value, EntityWorkflowStep::class);
}
if ($value->isFinal() && 0 < count($value->getDestUser())) {
$this->context->buildViolation($constraint->messageDestNotAllowed)
->addViolation();
}
if (!$value->isFinal() && 0 === count($value->getDestUser())) {
$this->context->buildViolation($constraint->messageRequireDest)
->addViolation();
}
}
}

View File

@@ -29,3 +29,19 @@ notification:
few {# non-lues}
other {# non-lues}
}
counter comments: >-
{nb, plural,
=0 {Aucun commentaire}
one {# commentaire}
few {# commentaires}
other {# commentaires}
}
workflow:
My workflows with counter: >-
{wc, plural,
=0 {Mes workflows}
one {Une workflow}
few {# workflows}
other {# workflows}
}

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