Compare commits

...

139 Commits

Author SHA1 Message Date
c6c0b7ce2f age displayed between parenthesis in twig renderbox. Validation not yet correct 2021-12-13 20:08:16 +01:00
3973cdd3f6 update changelog [ci-skip] 2021-12-13 16:19:32 +01:00
13a1ab380e fix import social work metadata: use child social issue if any 2021-12-13 15:03:30 +01:00
041655d26e update changelog [ci-skip] 2021-12-13 15:03:30 +01:00
6e412117d3 Merge branch 'issue324_civility_order' into 'master'
main: add order field to civility

See merge request Chill-Projet/chill-bundles!265
2021-12-13 14:00:55 +00:00
cc849e279d fix column name 2021-12-13 14:38:33 +01:00
de262bbc28 civility::order: replace int by a float 2021-12-13 14:12:48 +01:00
2bafdd9e62 Merge branch 'fix/236-update-social-work-metadata-importer' into 'master'
Update Social Work Metadata importer

Closes champs-libres/departement-de-la-vendee/chill#236

See merge request Chill-Projet/chill-bundles!190
2021-12-13 12:49:03 +00:00
d01eaa8065 various fixes 2021-12-13 13:48:20 +01:00
nobohan
4a465b8f78 upd CHANGELOG 2021-12-13 12:39:09 +01:00
nobohan
eaf7ae6f2f php cs fixer 2021-12-13 12:38:18 +01:00
nobohan
b5178c3be3 main: add order field to civility 2021-12-13 12:35:17 +01:00
fdae6c106a Merge remote-tracking branch 'origin/master' into fix/236-update-social-work-metadata-importer 2021-12-13 11:01:19 +01:00
1fff9ef535 stylefix 2021-12-13 10:10:05 +01:00
20c4d07668 fix of aside activity category creation 2021-12-13 10:05:51 +01:00
6e1340be27 Merge branch 'master' into 292_activity_acl 2021-12-13 09:21:32 +01:00
118b208f68 Merge branch 'master' into 292_activity_acl 2021-12-13 09:13:43 +01:00
e21f12d96c Merge remote-tracking branch 'origin/master' 2021-12-12 21:05:35 +01:00
4b213dc290 remove dump [ci-skip] 2021-12-12 21:05:12 +01:00
55991a6082 Merge branch 'issue309_format_address' into 'master'
main: change address format for France, in Address render box and normalizer

See merge request Chill-Projet/chill-bundles!259
2021-12-12 15:05:54 +00:00
juminet
898f25c0e4 main: change address format for France, in Address render box and normalizer 2021-12-12 15:05:54 +00:00
b3929321b6 Merge branch 'badge_by_entity_type' into 'master'
Badge-thirdparty

See merge request Chill-Projet/chill-bundles!258
2021-12-12 14:51:43 +00:00
c049a0b78b Badge-thirdparty 2021-12-12 14:51:42 +00:00
10aa897899 Merge branch 'docgen/improve-normalizer' into 'master'
Docgen/improve normalizer

See merge request Chill-Projet/chill-bundles!257
2021-12-12 14:43:05 +00:00
31f5d97912 Docgen/improve normalizer 2021-12-12 14:43:05 +00:00
c6cda437c4 Merge branch 'issue324_person' into 'master'
fix some person issues

See merge request Chill-Projet/chill-bundles!254
2021-12-12 14:39:59 +00:00
juminet
7e045e964a fix some person issues 2021-12-12 14:39:59 +00:00
133ac67794 Merge branch 'issue320_bug_redirect_createPeriod' into 'master'
Issue320 bug redirect create period

See merge request Chill-Projet/chill-bundles!249
2021-12-12 14:12:44 +00:00
8fb4efc16e Issue320 bug redirect create period 2021-12-12 14:12:44 +00:00
1823a1b031 Merge branch 'issue318_internal_addIssue_fromAction' into 'master'
Adding an unrelated to issue to an accompanyingCourseWork

See merge request Chill-Projet/chill-bundles!246
2021-12-12 13:55:23 +00:00
683d055088 Adding an unrelated to issue to an accompanyingCourseWork 2021-12-12 13:55:22 +00:00
839fb4a211 Squashed commit of the following:
commit 977863c2dd56d5c835f2a710cad7f7d3ba42da68
Merge: 5c37b419d 3eb7ffed1
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 14:45:21 2021 +0100

    Merge remote-tracking branch 'origin/master' into docgen/improve-normalizer

commit 5c37b419ddf0b32b9950c33042396bba1860da84
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 14:37:05 2021 +0100

    fix normalization for user and type in null value

commit 4469d46cdb19051fedec86bbb84e2351e6fcb72e
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 13:24:23 2021 +0100

    add civility to person

commit 6cf92fbbde8f4d9f2f4763ec4ee88216257040f7
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 13:19:36 2021 +0100

    fix person normalization: add a isNull on not null person

commit ed6087ff8fd47b80ea5e9526756fe5d032d478e3
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 13:08:46 2021 +0100

    fix stan and cs issues

commit 8429c334c33b3545835cbde034fccaa529c134a7
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 13:08:36 2021 +0100

    fix id type

commit 39ae00d172a9f29320a97abb8518b2ea48d89d9b
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sun Dec 12 12:52:41 2021 +0100

    fix test and fix null or not-null value have same keys

commit 312fcc44c07affa7aa60f6c5fce58f9d1c564cc3
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sat Dec 11 03:27:30 2021 +0100

    improve normalization wip

commit f91a29635827005fc58617dc1c9d210091372be5
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sat Dec 11 01:15:32 2021 +0100

    improve normalization

commit 56060e5e6a2191ef441039fdc91a01fb4653a553
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Sat Dec 11 00:41:09 2021 +0100

    handle changelog with translatable string

commit 9004686a13
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Fri Dec 10 01:10:55 2021 +0100

    improve docgen wip

commit e266fa0e5d
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Dec 9 21:50:56 2021 +0100

    show errors from relatorio driver

commit 75ba56fa09
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Dec 9 21:14:12 2021 +0100

    add verification tool for admin

commit 12d6829b98
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Dec 9 14:17:42 2021 +0100

    fix type with phonenumber helper

commit 7b5e96771f
Merge: 8a9024de1 8a4748dc2
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Dec 9 14:11:14 2021 +0100

    Merge remote-tracking branch 'origin/master' into docgen/improve-normalizer

commit 8a9024de13
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Dec 9 13:51:36 2021 +0100

    add docgen:normalization for relation

commit 24a404964b
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Thu Dec 9 12:44:41 2021 +0100

    docgen normalization for relation

commit 5d24bd4d11
Merge: 70ab23214 455b225f4
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 21:08:30 2021 +0100

    Merge branch 'master' into docgen/improve-normalizer

commit 70ab232149
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 13:58:49 2021 +0100

    improve docgen, trnslations, admin

commit 027c01fc58
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 12:23:24 2021 +0100

    fix css block

commit fdc5127c74
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:57:16 2021 +0100

    fix some error in test (wip)

commit b8d48f04ae
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:47:50 2021 +0100

    fix tests (wip)

commit f1b1771d6b
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:35:00 2021 +0100

    fix tests (wip)

commit 62dabbe1e7
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:14:46 2021 +0100

    fix code style

commit 4101392190
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:13:49 2021 +0100

    fix tests and type hinting

commit 3f1bed0b1c
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:05:41 2021 +0100

    fix tests (wip)

commit 79fbdcdee4
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 11:05:29 2021 +0100

    type hint User class

commit 3d8d79323e
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 10:56:30 2021 +0100

    remove error messages

commit 32178e22fe
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 10:51:30 2021 +0100

    fix tests (wip)

commit 60a8c20896
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 10:29:54 2021 +0100

    update app

commit 9d8011da61
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 10:06:35 2021 +0100

    fix loading origin

commit 789eeadb40
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 09:52:51 2021 +0100

    fix loading fixtures for doc generator template

commit f206fdb08c
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 09:52:35 2021 +0100

    fix code style

commit 9d5409d8d9
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 09:52:06 2021 +0100

    fix casting

commit e297d82533
Author: Julien Fastré <julien.fastre@champs-libres.coop>
Date:   Wed Dec 8 09:26:13 2021 +0100

    fixes on tests [WIP]
2021-12-12 14:46:37 +01:00
nobohan
3eb7ffed1a quick fix: use role as string in 2 functions arguments 2021-12-10 14:04:41 +01:00
d0c5d0423f update changelog 2021-12-10 12:58:07 +01:00
f2117ea1cd improve activity new/edit twig template 2021-12-10 12:51:18 +01:00
c7ee2cc600 add entity 'user' to pills badge-entity 2021-12-10 12:38:19 +01:00
57c4f2ee61 improve activity show twig template 2021-12-10 12:12:17 +01:00
95caa998e3 concernedGroups display badges as clickable onthefly modal 2021-12-10 12:09:50 +01:00
97165c8389 activity: set ACL (issue 292)
https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/292
2021-12-10 10:35:37 +01:00
1efdade382 thirdparty: no need to burst the code 2021-12-10 10:35:37 +01:00
cf219198ca update changelog 2021-12-10 10:32:50 +01:00
5a2e6c0969 AddPerson suggestions: all line make checkbox clickable 2021-12-09 17:18:06 +01:00
84f772b42b hop 2021-12-09 16:50:07 +01:00
62b8b3e6b7 add thirdparty comment in acccourse resources 2021-12-09 16:25:45 +01:00
5fdbba9ef5 thirdparty: improve render for children/parent dependencies in renderbox vue/twig 2021-12-09 16:17:04 +01:00
5b321dfb81 list-suggest: add classes to change list disposition 2021-12-09 14:34:34 +01:00
10f276e48c list-suggest add-items hover effect 2021-12-09 11:18:14 +01:00
8a4748dc2d position of toast changed to bottom-right 2021-12-09 10:38:04 +01:00
f5ae5443ab badge-entity: mask 'tiers' when displayLong (??) 2021-12-09 09:40:52 +01:00
9272687ec4 adapt in twig thirdparty renderbox 2021-12-08 17:45:07 +01:00
6100176c9f try badgeEntity with displayLong option 2021-12-08 17:29:31 +01:00
56ab304af1 try badge with hover 2021-12-08 16:12:47 +01:00
b888211ef9 proposition for person/thirparty rounded-pill badge appearance 2021-12-08 12:19:03 +01:00
0617014e91 some small style improvements in thirdparty section 2021-12-08 12:19:03 +01:00
ce171ec747 household-members-editor: lightly improve ux (TO CHECK) 2021-12-08 12:19:02 +01:00
455b225f49 Merge branch 'tests/fixes-2021-12-07' into 'master'
fixes on tests

See merge request Chill-Projet/chill-bundles!256
2021-12-08 10:56:39 +00:00
fa5001265f fixes on tests 2021-12-08 10:56:39 +00:00
56dd825f39 Merge remote-tracking branch 'origin/master' 2021-12-07 18:23:13 +01:00
4e4add3cc1 check for ancestors when validating the presence of accompanying
period's social issues
2021-12-07 18:17:22 +01:00
b684bdfa3b Merge branch 'issue279_accompanying_period_validation' into 'master'
accompanying period validation - on social issues suppression

See merge request Chill-Projet/chill-bundles!250
2021-12-07 17:16:14 +00:00
juminet
b683630136 accompanying period validation - on social issues suppression 2021-12-07 17:16:13 +00:00
e6c60e66fc fix validator for taking ancestors into account 2021-12-07 18:05:05 +01:00
b1b9acc686 Merge branch 'bugfix/activity-fix-location-visibility' into issue279_accompanying_period_validation 2021-12-07 17:57:02 +01:00
f6408af543 Merge branch 'master' into issue279_accompanying_period_validation 2021-12-07 17:55:00 +01:00
5b4388eae6 update changelog [ci-skip] 2021-12-07 16:42:34 +01:00
41ecb2351e Merge branch 'master' into bugfix/activity-fix-location-visibility 2021-12-07 16:41:59 +01:00
6cc0f1f98b update changelog [ci-skip] 2021-12-07 16:41:35 +01:00
aa68ae0b25 [ActivityType] remove placeLabel/visible, replace by
locationLabel/visible
2021-12-07 16:38:43 +01:00
ebd817f61e fix cs 2021-12-06 17:27:57 +01:00
25147704ad fix uppercase in person form with strict types 2021-12-06 17:24:58 +01:00
2788149db4 fix error when using twilio format 2021-12-06 17:24:35 +01:00
fabe32371f fix menu doc generator 2021-12-06 17:19:22 +01:00
b120e47786 add translations 2021-12-06 16:17:30 +01:00
5a61f05faa Merge remote-tracking branch 'origin/master' into features/docgen-widget-generate-template 2021-12-06 15:46:19 +01:00
7c2a283437 Merge branch 'page_accourse_lists' into 'master'
Cohérence graphique des listes d'activités et d'actions dans le contexte d'un parcours

See merge request Chill-Projet/chill-bundles!251
2021-12-06 14:45:44 +00:00
2d1e8b2acd Cohérence graphique des listes d'activités et d'actions dans le contexte d'un parcours 2021-12-06 14:45:44 +00:00
afb7d4a160 try to fix [wip] 2021-12-06 15:37:20 +01:00
96e5e4a7b2 Merge remote-tracking branch 'origin/master' into issue279_accompanying_period_validation 2021-12-06 15:08:56 +01:00
8e6e92ae89 Merge branch 'issue128_remove_titulaire_field' into 'master'
titulaire field removed from household edit form

See merge request Chill-Projet/chill-bundles!248
2021-12-06 14:07:21 +00:00
07f53e6758 titulaire field removed from household edit form 2021-12-06 14:07:21 +00:00
d2c61a26ea Merge branch '306_visgraph_corrections' into 'master'
306 fix visgraph issues

See merge request Chill-Projet/chill-bundles!245
2021-12-06 14:01:51 +00:00
bbc5e94033 306 fix visgraph issues 2021-12-06 14:01:50 +00:00
8e8a8c1e70 Merge branch 'issue280_actionToCourse' into 'master'
Eventlistener to create accompanyingPeriodWork

See merge request Chill-Projet/chill-bundles!244
2021-12-06 13:57:10 +00:00
f10d762e59 Eventlistener to create accompanyingPeriodWork 2021-12-06 13:57:10 +00:00
9924c3a3b7 Merge branch 'issue305_internal_fixbug_posting_dates' into 'master'
Fix bug posting birth- and deathdate

See merge request Chill-Projet/chill-bundles!243
2021-12-06 13:04:19 +00:00
cea7160d5a Fix bug posting birth- and deathdate 2021-12-06 13:04:19 +00:00
5669f62fdc Merge branch 'issue316-addresses-search-by-postal-code' into 'master'
Issue316 addresses search by postal code

See merge request Chill-Projet/chill-bundles!239
2021-12-06 12:56:57 +00:00
938720be52 Issue316 addresses search by postal code 2021-12-06 12:56:57 +00:00
c4998f4ac1 normalization for docgen:read: add groups and so on (wip) 2021-12-06 13:18:59 +01:00
nobohan
15c7c6ccde fix code syntax using php stan 2021-12-06 10:06:55 +01:00
nobohan
3c3d63e286 upd CHANGELOG 2021-12-06 10:04:25 +01:00
nobohan
74682549c8 person: fix accompanying period validation 2021-12-06 10:00:40 +01:00
51fd81c661 code style 2021-12-04 01:14:37 +01:00
063ceb2c53 various improvements 2021-12-04 01:13:55 +01:00
6c1a946608 generate context for evaluations 2021-12-03 23:31:19 +01:00
29134f0f11 continue context for evaluations 2021-12-03 20:24:31 +01:00
2d319fcc42 adapting vuejs form for accompanyingPeriodWork: include template for
specific evaluation
2021-12-03 20:09:31 +01:00
nobohan
2e4356c3c9 person: add custom class validator for accompanying period 2021-12-03 18:10:52 +01:00
51bbcab878 Merge branch 'cherry-pick-a86ba6fa' into 'master'
fix code style

See merge request Chill-Projet/chill-bundles!253
2021-12-03 15:57:57 +00:00
5645cb0cdd Merge branch 'cherry-pick-43702ded' into 'master'
fix pagination problems

See merge request Chill-Projet/chill-bundles!252
2021-12-03 15:57:36 +00:00
02c93389d8 fix code style
(cherry picked from commit a86ba6faf5)
2021-12-03 14:20:01 +00:00
a86ba6faf5 fix code style 2021-12-03 15:18:45 +01:00
e1d4868f51 Merge remote-tracking branch 'origin/master' into features/docgen-widget-generate-template 2021-12-03 15:17:50 +01:00
9244bb2f8d fix pagination problems
(cherry picked from commit 43702ded77)
2021-12-03 14:10:07 +00:00
43702ded77 fix pagination problems 2021-12-03 15:06:06 +01:00
nobohan
611f0a9ca6 activity: fix type hinting in activity form - fix 2021-12-03 14:38:55 +01:00
nobohan
d2704ad7e6 activity: fix type hinting in activity form 2021-12-03 14:34:21 +01:00
ff751b7f30 remove validation about UserCircleConsistency 2021-12-03 13:25:39 +00:00
23eff9f6d4 add basic context for AccompanyingPeriodWorkEvalution WIP 2021-12-03 13:50:35 +01:00
e053529afb add docgen context for AccompanyingPeriodWork 2021-12-03 12:21:36 +01:00
be626079d0 move logic of context to different interfaces 2021-12-02 18:18:11 +01:00
af6efdd0ba generate document with relatorio: config and driver 2021-12-02 18:02:19 +01:00
2245f83631 generate document with relatorio: config and driver 2021-12-02 17:00:09 +01:00
475b40e896 add form to doc generation and custom form to admin template configuration 2021-12-01 23:00:02 +01:00
7719d2b073 full generation for accompanying period 2021-12-01 15:43:34 +01:00
9d0e1a82e7 add deps to base64 2021-12-01 15:41:59 +01:00
9f43c99acc handle finding returnType on property type on parent classes 2021-12-01 15:39:44 +01:00
de4e83b3fb Generate a context for docgen, on accompanying period 2021-11-30 23:23:02 +01:00
3404d3669c create contextmanager and refactor docgeneratorTemplateType 2021-11-30 18:35:05 +01:00
0710d6572b Merge remote-tracking branch 'origin/docgenerator_oct_nov_21' into features/docgen-widget-generate-template 2021-11-30 18:31:33 +01:00
cf442461d6 Merge remote-tracking branch 'origin/master' into features/docgen-widget-generate-template 2021-11-30 16:38:36 +01:00
Pol Dellaiera
6e521642a4 fix: Typed property RedisConnectionFactory::$redis must not be accessed before initialization. 2021-11-30 15:22:32 +01:00
marcu
0e88d7c549 Using HttpClientInterface instead of file_get_contents 2021-11-30 14:53:50 +01:00
marcu
2993dab3f7 Debug using KernelInterface instead HttpKernelInterface 2021-11-30 14:53:50 +01:00
marcu
d026be3041 Create new layer + convert in good crs 2021-11-30 14:53:50 +01:00
marcu
59a0b692ac Fix load fixtures (LoadDocGeneratorTemplates) 2021-11-30 14:53:50 +01:00
marcu
9fa2e80d81 Debug : redeclaration of a varaible 2021-11-30 14:53:50 +01:00
Marc Ducobu
4e56075fdc Removing unused deps 2021-11-30 14:53:48 +01:00
Marc Ducobu
2b01179967 DocGeneratorTemplate use StoredObject for storign the template 2021-11-30 14:53:05 +01:00
Marc Ducobu
847e610362 Good values to point to a doc file 2021-11-30 14:53:05 +01:00
Marc Ducobu
a7af7e7874 php-decrypt data crypted using js 2021-11-30 14:53:01 +01:00
Marc Ducobu
1bf595334e Adding AccompanyingPeriodWorkEvaluation in the choices of DocGenerator config form (admin) 2021-11-30 14:51:43 +01:00
Marc Ducobu
a3333c3302 Fix LoadDocGeneratorTemplate (using docstore obj) 2021-11-30 14:51:43 +01:00
Marc Ducobu
7fe2172f05 DocGeneratorTemplate use StoredObject for storign the template 2021-11-30 14:51:43 +01:00
bbf6b1412f repair admin docgen WIP 2021-11-29 10:42:43 +01:00
9f868bff4f create module for generating document in twig pages 2021-11-28 21:24:18 +01:00
1c18ba20fc docgen: improve listing for templates 2021-11-27 01:23:34 +01:00
Pol Dellaiera
90b256daaf fix: Remove extra comma. 2021-11-03 10:14:10 +01:00
Pol Dellaiera
226c3eedef Update Social Work Metadata importer based on change request. 2021-11-02 13:27:51 +01:00
239 changed files with 8287 additions and 2910 deletions

View File

@@ -11,11 +11,50 @@ and this project adheres to
## Unreleased
<!-- write down unreleased development here -->
* [person search] fix bug when using birthdate after and birthdate before
* [person search] increase pertinence when lastname begins with search pattern
## Test releases
### test release 2021-12-11
* [main] add order field to civility
* [main] change address format in case the country is France, in Address render box and address normalizer
* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76)
* [activity] fix visibility for location
* [origin] fix origin: use correctly the translatable strings
* /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');`
* [person] redirect bug fixed.
* [action] add an unrelated issue within action creation.
* [origin] fix origin: use correctly the translatable strings
* /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');`
* [main] change order of civilities in civility fixtures (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
* [person] set min attr in the minimum of children field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
* [person] add marital status date in person view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
* [person] show number of children + allow set number of children to null (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
* [person] show acceptSMS option (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
* [person] add death information in person render box in twig and vue render boxes (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262)
* [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
### test release 2021-12-06
* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
* [person search] fix bug when using birthdate after and birthdate before
* [person search] increase pertinence when lastname begins with search pattern
* [activity/actions] Améliore la cohérence du design entre
* la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes)
* la page liste des actions
* la page liste des activités (contexte personne / contexte parcours)
* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322)
* [activity] create work if a work with same social action is not associated to the activity
* [visgraph] improve and fix bugs on vis-network relationship graph
* [bugfix] posting of birth- and deathdate through api fixed.
* [suggestions] improve suggestions lists
* [badge-entity] design coherency between badge-person and 3 kinds of badge-thirdparty
* [AddPersons] suggestions row are clickable, not only checkbox
* [activity] improve show/new/edit templates, fix SEE and SEE_DETAILS acl
* [activity][calendar] concerned groups items are clickable with on-the-fly modal, create specific badge for TMS column
### Test release 2021-11-19 - bis
* [household] do not allow to create two addresses on the same date
@@ -23,6 +62,7 @@ and this project adheres to
* [activity] layout for issues / actions
* [activity][bugfix] in edit mode, the form will now load the social action list
### Test release 2021-11-29
* [person] suggest entities (person | thirdparty) when creating/editing the accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/119)
@@ -50,6 +90,9 @@ and this project adheres to
* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
## Test releases
### Test release 2021-11-22
* [activity] delete admin_user_show in twig template because this route is not defined and should be defined
@@ -164,7 +207,7 @@ and this project adheres to
* fast creation buttons
* add ordering for types
* [AccompanyingCourse Resume page] badge-title for AccompanyingCourseWork and for Activities;
* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities;
* Improve badges behaviour with small screens;
* [ThirdParty]:

View File

@@ -409,3 +409,4 @@ Créer un titre enlevable (avec une croix rouge cliquable, l'ancre a est vide)
<a></a>
</div>
```
Les classes `cols` ou `inline` peuvent être ajoutées à côté de `list-suggest` pour modifier la disposition de la liste.

View File

@@ -24,6 +24,7 @@
"phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5",
"spomky-labs/base64url": "^2.0",
"symfony/asset": "4.*",
"symfony/browser-kit": "^5.2",
"symfony/css-selector": "^5.2",

View File

@@ -10,16 +10,6 @@ parameters:
count: 1
path: src/Bundle/ChillCustomFieldsBundle/Controller/CustomFieldsGroupController.php
-
message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#"
count: 1
path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorController.php
-
message: "#^Instantiated class PhpOffice\\\\PhpWord\\\\TemplateProcessor not found\\.$#"
count: 1
path: src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php
-
message: "#^Variable \\$participation might not be defined\\.$#"
count: 3

View File

@@ -525,24 +525,6 @@ parameters:
count: 1
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
-
message:
"""
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 2
path: src/Bundle/ChillEventBundle/Search/EventSearch.php
-
message:
"""
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillEventBundle/Timeline/TimelineEventProvider.php
-
message:
"""
@@ -1311,16 +1293,6 @@ parameters:
count: 1
path: src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php
-
message:
"""
#^Instantiation of deprecated class Symfony\\\\Component\\\\Security\\\\Core\\\\Role\\\\Role\\:
since Symfony 4\\.3, to be removed in 5\\.0\\. Use strings as roles instead\\.$#
"""
count: 1
path: src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php
-
message:
"""

View File

@@ -41,6 +41,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$loader->load('services/form.yaml');
$loader->load('services/templating.yaml');
$loader->load('services/accompanyingPeriodConsistency.yaml');
$loader->load('services/doctrine.entitylistener.yaml');
}
public function prepend(ContainerBuilder $container)

View File

@@ -47,7 +47,8 @@ use Symfony\Component\Serializer\Annotation\SerializedName;
* })
* @ActivityValidator\ActivityValidity
*
* @UserCircleConsistency(
* TODO see if necessary
* UserCircleConsistency(
* "CHILL_ACTIVITY_SEE_DETAILS",
* getUserFunction="getUser",
* path="scope")

View File

@@ -163,16 +163,6 @@ class ActivityType
*/
private int $personVisible = self::FIELD_REQUIRED;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $placeLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $placeVisible = self::FIELD_OPTIONAL;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
@@ -406,16 +396,6 @@ class ActivityType
return $this->personVisible;
}
public function getPlaceLabel(): string
{
return $this->placeLabel;
}
public function getPlaceVisible(): int
{
return $this->placeVisible;
}
public function getReasonsLabel(): string
{
return $this->reasonsLabel;
@@ -688,20 +668,6 @@ class ActivityType
return $this;
}
public function setPlaceLabel(string $placeLabel): self
{
$this->placeLabel = $placeLabel;
return $this;
}
public function setPlaceVisible(int $placeVisible): self
{
$this->placeVisible = $placeVisible;
return $this;
}
public function setReasonsLabel(string $reasonsLabel): self
{
$this->reasonsLabel = $reasonsLabel;

View File

@@ -0,0 +1,77 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\ActivityBundle\EntityListener;
use Chill\ActivityBundle\Entity\Activity;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use function in_array;
class ActivityEntityListener
{
private EntityManagerInterface $em;
private AccompanyingPeriodWorkRepository $workRepository;
public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository)
{
$this->em = $em;
$this->workRepository = $workRepository;
}
public function persistActionToCourse(Activity $activity)
{
if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$period = $activity->getAccompanyingPeriod();
$accompanyingCourseWorks = $this->workRepository->findByAccompanyingPeriod($period);
$periodActions = [];
$now = new DateTimeImmutable();
foreach ($accompanyingCourseWorks as $key => $work) {
// take only the actions which are still opened
if ($work->getEndDate() === null || $work->getEndDate() > ($activity->getDate() ?? $now)) {
$periodActions[$key] = spl_object_hash($work->getSocialAction());
}
}
$associatedPersons = $activity->getPersonsAssociated();
$associatedThirdparties = $activity->getThirdParties();
foreach ($activity->getSocialActions() as $action) {
if (in_array(spl_object_hash($action), $periodActions, true)) {
continue;
}
$newAction = new AccompanyingPeriodWork();
$newAction->setSocialAction($action);
$period->addWork($newAction);
$date = DateTimeImmutable::createFromMutable($activity->getDate());
$newAction->setStartDate($date);
foreach ($associatedPersons as $person) {
$newAction->addPerson($person);
}
foreach ($associatedThirdparties as $thirdparty) {
$newAction->setHandlingThierparty($thirdparty);
}
$this->em->persist($newAction);
$this->em->flush();
}
}
}
}

View File

@@ -260,6 +260,10 @@ class ActivityType extends AbstractType
return implode(',', $personIds);
},
function (?string $personsAsString): array {
if (null === $personsAsString) {
return [];
}
return array_map(
fn (string $id): ?Person => $this->om->getRepository(Person::class)->findOneBy(['id' => (int) $id]),
explode(',', $personsAsString)
@@ -282,6 +286,10 @@ class ActivityType extends AbstractType
return implode(',', $thirdpartyIds);
},
function (?string $thirdpartyAsString): array {
if (null === $thirdpartyAsString) {
return [];
}
return array_map(
fn (string $id): ?ThirdParty => $this->om->getRepository(ThirdParty::class)->findOneBy(['id' => (int) $id]),
explode(',', $thirdpartyAsString)
@@ -315,6 +323,10 @@ class ActivityType extends AbstractType
return implode(',', $userIds);
},
function (?string $usersAsString): array {
if (null === $usersAsString) {
return [];
}
return array_map(
fn (string $id): ?User => $this->om->getRepository(User::class)->findOneBy(['id' => (int) $id]),
explode(',', $usersAsString)
@@ -332,7 +344,7 @@ class ActivityType extends AbstractType
return '';
}
return $location->getId();
return (string) $location->getId();
},
function (?string $id): ?Location {
return $this->om->getRepository(Location::class)->findOneBy(['id' => (int) $id]);
@@ -379,7 +391,7 @@ class ActivityType extends AbstractType
$timezoneUTC = new DateTimeZone('GMT');
/** @var DateTime $data */
$data = $formEvent->getData() === null ?
DateTime::createFromFormat('U', 300) :
DateTime::createFromFormat('U', '300') :
$formEvent->getData();
$seconds = $data->getTimezone()->getOffset($data);
$data->setTimeZone($timezoneUTC);

View File

@@ -55,7 +55,7 @@ class ActivityTypeType extends AbstractType
]);
$fields = [
'persons', 'user', 'date', 'place', 'persons',
'persons', 'user', 'date', 'location', 'persons',
'thirdParties', 'durationTime', 'travelTime', 'attendee',
'reasons', 'comment', 'sentReceived', 'documents',
'emergency', 'socialIssues', 'socialActions', 'users',

View File

@@ -31,6 +31,8 @@ class ActivityRepository extends ServiceEntityRepository
}
/**
* @deprecated use @see{ActivityACLAwareRepositoryInterface::findByAccompanyingPeriod}
*
* @return Activity[]
*/
public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array

View File

@@ -24,14 +24,16 @@ div.new-activity-select-type {
}
//// ACTIVITY LIST PAGE
// precise badge-title specific details
// precise dashboard specific details
p.date-label {
display: inline-block;
margin: 0 0.5em 0 0;
font-weight: 700;
font-size: 18pt;
}
div.dashboard,
h2.badge-title {
div.duration {
font-size: smaller;
padding-left: 1em;
margin-top: 1em;
}
ul.list-content {
font-size: 70%;
list-style-type: none;
@@ -39,16 +41,13 @@ h2.badge-title {
margin: 0;
li {
margin-bottom: 0.2em;
// exception: change bg color for action badges above badge-title
// exception: change bg color for action badges above dashboard
.bg-light {
background-color: $chill-light-gray !important;
}
}
}
}
div.main {
padding: 1em;
}
//// ACTIVITY SHOW AND FORM PAGES
// Exceptions for flex-bloc in concerned-groups

View File

@@ -11,7 +11,7 @@
</persons-bloc>
</div>
<div v-if="getContext === 'accompanyingCourse' && suggestedEntities.length > 0">
<ul class="list-suggest add-items">
<ul class="list-suggest add-items inline">
<li v-for="p in suggestedEntities" @click="addSuggestedEntity(p)">
<span>{{ p.text }}</span>
</li>

View File

@@ -6,17 +6,17 @@ const debug = process.env.NODE_ENV !== 'production';
//console.log('window.activity', window.activity);
const addIdToValue = (string, id) => {
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
};
const removeIdFromValue = (string, id) => {
let array = string.split(',');
array = array.filter(el => el !== id.toString());
let str = array.join();
return str;
let array = string.split(',');
array = array.filter(el => el !== id.toString());
let str = array.join();
return str;
};
const store = createStore({
@@ -50,9 +50,9 @@ const store = createStore({
return state.activity.activityType.personsVisible === 0
? []
: state.activity.accompanyingPeriod.participations
.filter((p) => p.endDate === null)
.map((p) => p.person)
.filter((p) => !existingPersonIds.includes(p.id));
.filter((p) => p.endDate === null)
.map((p) => p.person)
.filter((p) => !existingPersonIds.includes(p.id));
},
suggestedRequestor(state) {
if (state.activity.accompanyingPeriod.requestor === null) {
@@ -78,8 +78,8 @@ const store = createStore({
return state.activity.activityType.usersVisible === 0
? []
: [state.activity.accompanyingPeriod.user].filter(
(u) => u !== null && !existingUserIds.includes(u.id)
);
(u) => u !== null && !existingUserIds.includes(u.id)
);
},
suggestedResources(state) {
const resources = state.activity.accompanyingPeriod.resources;

View File

@@ -1,110 +0,0 @@
<h2 class="badge-title">
<span class="title_label">
{% if activity.date %}
<h3>{{ activity.date|format_date('short') }}</h3>
{% endif %}
<div class="duration">
{% if activity.durationTime and t.durationTimeVisible %}
<p>
<abbr class="fa fa-fw fa-hourglass-end" title="{{ 'Duration Time'|trans }}"></abbr>
{{ activity.durationTime|date('H:i') }}
</p>
{% endif %}
{% if activity.travelTime and t.travelTimeVisible %}
<p>
<abbr class="fa fa-fw fa-car" title="{{ 'Travel time'|trans }}"></abbr>
{{ activity.travelTime|date('H:i') }}
</p>
{% endif %}
</div>
</span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
<ul class="small_in_title mt-3">
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<li>
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
<b>{{ activity.sentReceived|capitalize|trans }}</b>
</li>
{% endif %}
{% if activity.location and t.locationVisible %}
<li>
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
<b>
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
{{ activity.location.name }}
</b>
</li>
{% endif %}
{% if activity.user and t.userVisible %}
<li>
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
<b>{{ activity.user.usernameCanonical }}</b>
</li>
{% endif %}
<li class="associated-persons">
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
{% for p in activity.personsAssociated %}
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
{% endfor %}
</li>
</ul>
<ul class="list-content my-3">
{%- if t.reasonsVisible -%}
{%- if activity.reasons is not empty -%}
<li class="reasons">
{% for r in activity.reasons %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialIssuesVisible %}
{%- if activity.socialIssues is not empty -%}
<li class="social-issues">
{% for r in activity.socialIssues %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialActionsVisible -%}
{%- if activity.socialActions is not empty -%}
<li class="social-actions">
{% for r in activity.socialActions %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
</ul>
</span>
</h2>
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
<div class="mt-3">
<a class="btn btn-sm btn-outline-primary"
title="{{ 'Period number %number%'|trans({'%number%': activity.accompanyingPeriod.id}) }}"
href="{{ chill_path_add_return_path(
"chill_person_accompanying_course_index",
{ 'accompanying_period_id': activity.accompanyingPeriod.id }
) }}"><i class="fa fa-random"></i>
</a>
</div>
{% endif %}

View File

@@ -3,6 +3,14 @@
{{ path(pathname, parms) }}
{% endmacro %}
{% macro insert_onthefly(type, entity) %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: type, id: entity.id },
buttonText: entity|chill_entity_render_string
} %}
{% endmacro %}
{% macro computeWidth(nbBlocks) %}
{{ 'flex-basis: ' ~ (100 / nbBlocks)|round(1) ~ '%;' }}
{% endmacro %}
@@ -13,6 +21,7 @@
{% set blocks = blocks|merge([{
'title': 'Others persons'|trans,
'items': entity.persons,
'type': 'person',
'path' : 'chill_person_view',
'key' : 'person_id'
}]) %}
@@ -20,11 +29,13 @@
{% set blocks = blocks|merge([{
'title': 'Persons in accompanying course'|trans,
'items': entity.personsAssociated,
'type': 'person',
'path' : 'chill_person_view',
'key' : 'person_id'
},{
'title': 'Third persons'|trans,
'items': entity.personsNotAssociated,
'type': 'person',
'path' : 'chill_person_view',
'key' : 'person_id',
}]) %}
@@ -34,6 +45,7 @@
{% set blocks = blocks|merge([{
'title': 'Third parties'|trans,
'items': entity.thirdParties,
'type': 'thirdparty',
'path' : 'chill_crud_3party_3party_view',
'key' : 'id',
}]) %}
@@ -42,6 +54,7 @@
{% set blocks = blocks|merge([{
'title': 'Users concerned'|trans,
'items': entity.users,
'type': 'user',
'key' : 'id',
}]) %}
{% endif %}
@@ -59,22 +72,12 @@
<ul class="list-content">
{% for item in bloc.items %}
<li>
{% if bloc.path is defined %}
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{% if bloc.type == 'user' %}
<span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
</span>
</a>
{% else %}
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
</span>
{{ _self.insert_onthefly(bloc.type, item) }}
{% endif %}
</li>
{% endfor %}
@@ -96,20 +99,12 @@
<ul class="list-content">
{% for item in bloc.items %}
<li>
{% if bloc.path is defined %}
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
<span class="{% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{% if bloc.type == 'user' %}
<span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
</span>
</a>
{% else %}
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{{ _self.insert_onthefly(bloc.type, item) }}
{% endif %}
</li>
{% endfor %}
@@ -126,24 +121,18 @@
<div class="wl-row">
{% if bloc.items|length > 0 %}
<div class="wl-col title">
<h4>{{ bloc.title }}</h4>
<h3>{{ bloc.title }}</h3>
</div>
<div class="wl-col list">
{% for item in bloc.items %}
<span class="wl-item {% if (badge_person is defined and badge_person == true) %}badge-person{% else %}badge bg-primary{% endif %}">
{% if bloc.path is defined %}
<a href="{{ _self.href(bloc.path, bloc.key, item.id) }}">
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
</a>
<span class="wl-item">
{% if bloc.type == 'user' %}
<span class="badge-user">
{{ item|chill_entity_render_box({'render': 'raw', 'addAltNames': false }) }}
</span>
{% else %}
{{ item|chill_entity_render_box({
'render': 'raw',
'addAltNames': false
}) }}
{{ _self.insert_onthefly(bloc.type, item) }}
{% endif %}
</span>

View File

@@ -1,5 +1,12 @@
<h1>{{ "Update activity"|trans }}</h1>
<h2 class="chill-green mb-4">{{ entity.type.name|localize_translatable_string }}</h2>
<h1>
{{ "Update activity"|trans }}
</h1>
<h2 class="badge-title mb-5">
<span class="title_label"></span>
<span class="title_action">
{{ entity.type.name | localize_translatable_string }}
</span>
</h2>
{{ form_start(edit_form) }}
{{ form_errors(edit_form) }}
@@ -37,7 +44,7 @@
{% endif %}
{%- if edit_form.persons is defined or edit_form.thirdParties is defined or edit_form.users is defined -%}
<h2 class="chill-red">{{ 'Concerned groups'|trans }}</h2>
<h2 class="chill-blue">{{ 'Concerned groups'|trans }}</h2>
{%- if edit_form.persons is defined -%}
{{ form_widget(edit_form.persons) }}
@@ -53,7 +60,7 @@
{% endif %}
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
{%- if edit_form.date is defined -%}
{{ form_row(edit_form.date) }}
@@ -73,7 +80,6 @@
{% endif %}
{%- if edit_form.comment is defined -%}
{# TODO .. public and private #}
{{ form_row(edit_form.comment) }}
{% endif %}
@@ -97,16 +103,16 @@
{% set accompanying_course_id = accompanyingCourse.id %}
{% endif %}
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ path('chill_activity_activity_show', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-cancel">
{{ 'Cancel'|trans }}
</a>
</li>
<li>
<button class="btn btn-update" type="submit">{{ 'Save'|trans }}</button>
</li>
</ul>
{{ form_end(edit_form) }}
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ path('chill_activity_activity_show', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-cancel">
{{ 'Cancel'|trans }}
</a>
</li>
<li>
<button class="btn btn-update" type="submit">{{ 'Save'|trans }}</button>
</li>
</ul>
{{ form_end(edit_form) }}
{# {{ form(delete_form) }} #}

View File

@@ -10,49 +10,195 @@
{% for activity in activities %}
{% set t = activity.type %}
<div class="item-bloc">
<div class="item-row">
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
<div class="wrap-list">
<div class="wl-row">
<div class="wl-col title">
{% if activity.date %}
<p class="date-label">
{{ activity.date|format_date('short') }}
</p>
{% endif %}
</div>
<div class="wl-col list">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ activity.type.name | localize_translatable_string }}
{% if activity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</h2>
</div>
</div>
</div>
</div>
{% if activity.comment.comment is not empty
or activity.persons|length > 0
or activity.thirdParties|length > 0
or activity.users|length > 0
%}
<div class="main">
{% if activity.comment.comment is not empty %}
{{ activity.comment|chill_entity_render_box({
'disable_markdown': false,
'limit_lines': 3,
'metadata': false,
}) }}
{% endif %}
<div class="item-row column separator">
<div class="wrap-list">
{% if activity.location and t.locationVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'location'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
{{ activity.location.name }}
</p>
</div>
</div>
{% endif %}
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Sent received'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
{{ activity.sentReceived|capitalize|trans }}
</p>
</div>
</div>
{% endif %}
{% if activity.user and t.userVisible %}
<div class="wl-row">
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
<div class="wl-col list">
<p class="wl-item">
{{ activity.user.usernameCanonical|chill_entity_render_string|capitalize }}
</p>
</div>
</div>
{% endif %}
</div>
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
'context': context,
'with_display': 'row',
'with_display': 'wrap-list',
'entity': activity,
'badge_person': true
} %}
<div class="wrap-list">
{%- if activity.reasons is not empty and t.reasonsVisible -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Reasons'|trans }}</h3>
</div>
<div class="wl-col list">
{% for r in activity.reasons %}
<p class="wl-item reasons">
{{ r|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
{% endif %}
{%- if activity.socialIssues is not empty and t.socialIssuesVisible -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Social issues'|trans }}</h3>
</div>
<div class="wl-col list">
{% for r in activity.socialIssues %}
<p class="wl-item social-issues">
{{ r|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
{% endif %}
{%- if activity.socialActions is not empty and t.socialActionsVisible -%}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Social actions'|trans }}</h3>
</div>
<div class="wl-col list">
{% for r in activity.socialActions %}
<p class="wl-item social-actions">
{{ r|chill_entity_render_box }}
</p>
{% endfor %}
</div>
</div>
{% endif %}
{# Only if ACL SEE_DETAILS AND/OR only on template SHOW ??
durationTime
travelTime
comment
{% if activity.comment.comment is not empty and is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
<div class="wl-row">
<div class="wl-col title">
<h3>{{ 'Comment'|trans }}</h3>
</div>
<div class="wl-col list">
{{ activity.comment|chill_entity_render_box({
'disable_markdown': false,
'limit_lines': 3,
'metadata': false
}) }}
</div>
</div>
{% endif %}
documents
attendee
#}
</div>
</div>
{% endif %}
<div class="item-row separator">
<div class="updatedBy"></div>
<ul class="record_actions">
{% if context == 'person' and activity.accompanyingPeriod is not empty %}
{#
Disable person_id in following links, for redirect to accompanyingCourse context
#}
{% set person_id = null %}
{% set accompanying_course_id = activity.accompanyingPeriod.id %}
<li>
<a href="{{ chill_path_add_return_path('chill_activity_activity_list',{
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-primary"
title="{{ 'See activity in accompanying course context'|trans }}">
<i class="fa fa-random fa-fw"></i>
{{ 'Period number %number%'|trans({'%number%': accompanying_course_id}) }}
</a>
</li>
{% endif %}
<li>
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-sm btn-show "></a>
<a href="{{ path('chill_activity_activity_show', {'id': activity.id,
'person_id': person_id,
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-show"
title="{{ 'Show'|trans }}"></a>
</li>
{% if no_action is not defined or no_action == false %}
<li>
<a href="{{ path('chill_activity_activity_edit', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="btn btn-sm btn-update "></a>
</li>
<li>
<a href="{{ path('chill_activity_activity_delete', { 'id': activity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}"
class="btn btn-sm btn-delete "></a>
</li>
{% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
<li>
<a href="{{ path('chill_activity_activity_edit', {'id': activity.id,
'person_id': person_id,
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-update"
title="{{ 'Edit'|trans }}"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACTIVITY_DELETE', activity) %}
<li>
<a href="{{ path('chill_activity_activity_delete', {'id': activity.id,
'person_id': person_id,
'accompanying_period_id': accompanying_course_id
}) }}"
class="btn btn-delete"
title="{{ 'Delete'|trans }}"></a>
</li>
{% endif %}
{% endif %}
</ul>
</div>

View File

@@ -23,8 +23,8 @@
{% if is_granted('CHILL_ACTIVITY_CREATE', accompanyingCourse) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
{{ 'Create'|trans }}
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"
class="btn btn-create">{{ 'Create'|trans }}
</a>
</li>
</ul>

View File

@@ -3,10 +3,88 @@
{% set t = activity.type %}
<a href="{{ path('chill_activity_activity_show', { 'id': activity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}"
class="badge-link" title="{{ 'Show the activity'|trans }}">
class="dashboard-link" title="{{ 'Show the activity'|trans }}">
{% include '@ChillActivity/Activity/activity-badge-title.html.twig' %}
<div class="dashboard">
<span class="title_label"></span>
<span class="title_action">
{%- if activity.date -%}
<p class="date-label">{{ activity.date|format_date('short') }}</p>
{%- endif -%}
<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>
{% endif %}
<ul class="small_in_title mt-3">
{% if activity.sentReceived is not empty and t.sentReceivedVisible %}
<li>
<span class="item-key">{{ 'Sent received'|trans ~ ' : ' }}</span>
<b>{{ activity.sentReceived|capitalize|trans }}</b>
</li>
{% endif %}
{% if activity.location and t.locationVisible %}
<li>
<span class="item-key">{{ 'location'|trans ~ ': ' }}</span>
<b>
<span>{{ activity.location.locationType.title|localize_translatable_string }}</span>
{{ activity.location.name }}
</b>
</li>
{% endif %}
{% if activity.user and t.userVisible %}
<li>
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
<b>{{ activity.user.usernameCanonical }}</b>
</li>
{% endif %}
<li class="associated-persons">
<span class="item-key">{{ 'Participants'|trans ~ ' : ' }}</span>
{% for p in activity.personsAssociated %}
<span class="badge-person">{{ p|chill_entity_render_box }}</span>
{% endfor %}
</li>
</ul>
<ul class="list-content my-3">
{%- if t.reasonsVisible -%}
{%- if activity.reasons is not empty -%}
<li class="reasons">
{% for r in activity.reasons %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialIssuesVisible %}
{%- if activity.socialIssues is not empty -%}
<li class="social-issues">
{% for r in activity.socialIssues %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
{%- if t.socialActionsVisible -%}
{%- if activity.socialActions is not empty -%}
<li class="social-actions">
{% for r in activity.socialActions %}
{{ r|chill_entity_render_box }}
{% endfor %}
</li>
{%- endif -%}
{% endif %}
</ul>
</span>
</div>
</a>
{% endfor %}
</div>

View File

@@ -1,10 +1,16 @@
<h1>{{ "Activity creation"|trans ~ ' :' }}</h1>
<h2 class="chill-green mb-4">{{ entity.type.name|localize_translatable_string }}</h2>
<h1>
{{ "Activity creation"|trans }}
</h1>
<h2 class="badge-title mb-5">
<span class="title_label"></span>
<span class="title_action">
{{ entity.type.name | localize_translatable_string }}
</span>
</h2>
{{ form_start(form) }}
{{ form_errors(form) }}
{%- if form.emergency is defined -%}
{{ form_row(form.emergency) }}
{% endif %}
@@ -39,7 +45,7 @@
{%- if form.persons is defined or form.thirdParties is defined or form.users is defined -%}
<h2 class="chill-red">{{ 'Concerned groups'|trans }}</h2>
<h2 class="chill-blue">{{ 'Concerned groups'|trans }}</h2>
{%- if form.persons is defined -%}
{{ form_widget(form.persons) }}
@@ -55,7 +61,7 @@
{% endif %}
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
{%- if form.date is defined -%}
{{ form_row(form.date) }}
@@ -103,7 +109,7 @@
</a>
</li>
<li>
<button class="btn btn-create" type="submit">
<button class="btn btn-save" type="submit">
{{ 'Create'|trans }}
</button>
</li>

View File

@@ -1,128 +1,185 @@
{%- set t = entity.type -%}
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<h1>
{{ "Activity"|trans }}
{%- if t.emergencyVisible and entity.emergency -%}
<span class="badge bg-secondary">
{{- 'Emergency'|trans -}}
</span>
{%- endif -%}
</h1>
<h1>{{ "Activity"|trans }}</h1>
<dl class="chill_view_data">
<div class="flex-table">
<div class="item-bloc">
<dt class="inline">{{ 'by'|trans|capitalize }}</dt>
<dd>{{ entity.user }}</dd>
<div class="item-row">
<div class="wrap-list">
<div class="wl-row">
<div class="wl-col title">
{% if entity.date %}
<p class="date-label">
{{ entity.date|format_date('short') }}
</p>
{% endif %}
</div>
<div class="wl-col list">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ entity.type.name | localize_translatable_string }}
{% if entity.emergency %}
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
{% endif %}
</span>
</h2>
</div>
</div>
</div>
</div>
<dt class="inline">{{ 'Type'|trans }}</dt>
<dd>{{ entity.type.name | localize_translatable_string }}</dd>
<div class="item-row separator">
<dl class="chill_view_data">
<dt class="inline">{{ 'Referrer'|trans|capitalize }}</dt>
<dd>{{ entity.user }}</dd>
{%- if entity.scope -%}
<dt class="inline">{{ 'Scope'|trans }}</dt>
<dd><span class="scope">{{ entity.scope.name|localize_translatable_string }}</span></dd>
{% endif %}
{%- if entity.scope -%}
<dt class="inline">{{ 'Scope'|trans }}</dt>
<dd>
<span class="scope">{{ entity.scope.name|localize_translatable_string }}</span>
</dd>
{% endif %}
{% if t.socialIssuesVisible %}
<dt class="inline">{{ 'Social issues'|trans }}</dt>
<dd>
{% if entity.socialIssues|length == 0 %}
<p class="chill-no-data-statement">{{ 'No social issues associated'|trans }}</p>
{% else %}
{% for si in entity.socialIssues %}{{ si|chill_entity_render_box }}{% endfor %}
{% endif %}
</dd>
{% endif %}
{% if t.socialIssuesVisible %}
<dt class="inline">{{ 'Social issues'|trans }}</dt>
<dd>
{% if entity.socialIssues|length == 0 %}
<p class="chill-no-data-statement">{{ 'No social issues associated'|trans }}</p>
{% else %}
{% for si in entity.socialIssues %}{{ si|chill_entity_render_box }}{% endfor %}
{% endif %}
</dd>
{% endif %}
{% if t.socialActionsVisible %}
<dt class="inline">{{ 'Social actions'|trans }}</dt>
<dd>
{% if entity.socialActions|length == 0 %}
<p class="chill-no-data-statement">{{ 'No social actions associated'|trans }}</p>
{% else %}
{% for sa in entity.socialActions %}{{ sa|chill_entity_render_box }}{% endfor %}
{% endif %}
</dd>
{% endif %}
{% if t.socialActionsVisible %}
<dt class="inline">{{ 'Social actions'|trans }}</dt>
<dd>
{% if entity.socialActions|length == 0 %}
<span class="chill-no-data-statement">{{ 'No social actions associated'|trans }}</span>
{% else %}
{% for sa in entity.socialActions %}{{ sa|chill_entity_render_box }}{% endfor %}
{% endif %}
</dd>
{% endif %}
{% if t.reasonsVisible %}
<dt class="inline">{{ 'Reasons'|trans }}</dt>
{%- if entity.reasons is empty -%}
<dd><span class="chill-no-data-statement">{{ 'No reason associated'|trans }}</span></dd>
{%- else -%}
<dd>{% for r in entity.reasons %}{{ r|chill_entity_render_box }} {% endfor %}</dd>
{%- endif -%}
{% endif %}
{% if t.reasonsVisible %}
<dt class="inline">{{ 'Reasons'|trans }}</dt>
<dd>
{%- if entity.reasons is empty -%}
<span class="chill-no-data-statement">{{ 'No reason associated'|trans }}</span>
{%- else -%}
{% for r in entity.reasons %}{{ r|chill_entity_render_box }}{% endfor %}
{%- endif -%}
</dd>
{% endif %}
</dl>
</div>
<h2 class="chill-red">{{ 'Concerned groups'|trans }}</h2>
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': context, 'with_display': 'bloc', 'badge_person': 'true' } %}
</div>
</div>
<h2 class="chill-red">{{ 'Activity data'|trans }}</h2>
<h2 class="chill-blue">{{ 'Concerned groups'|trans }}</h2>
<dt class="inline">{{ 'Date'|trans }}</dt>
<dd>{{ entity.date|format_date('long') }}</dd>
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
'context': context,
'with_display': 'bloc',
'badge_person': 'true'
} %}
{% if t.locationVisible %}
<dt class="inline">{{ 'Activity location'|trans }}</dt>
<dd>
{% if entity.location is not null %}
<p>
{{ entity.location.name }}
<span> ({{ entity.location.locationType.title|localize_translatable_string }})</span>
</p>
{{ entity.location.address|chill_entity_render_box }}
{% else %}
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
<div class="flex-table">
<div class="item-bloc">
<dl class="chill_view_data">
<dt class="inline">{{ 'Date'|trans }}</dt>
<dd>{{ entity.date|format_date('long') }}</dd>
{% if t.locationVisible %}
<dt class="inline">{{ 'Activity location'|trans }}</dt>
<dd>
{% if entity.location is not null %}
<p>
<span>{{ entity.location.locationType.title|localize_translatable_string }}</span>
{{ entity.location.name }}
</p>
<div class="ms-3">{{ entity.location.address|chill_entity_render_box }}</div>
{% else %}
<span class="chill-no-data-statement">{{ 'No address given'|trans }}</span>
{% endif %}
</dd>
{% endif %}
</dd>
{% endif %}
{% if t.durationTimeVisible %}
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
<dd>{% if entity.durationTime is not null %}
{{ entity.durationTime|date('H:i') }}
{% else %}
{{ 'None'|trans|capitalize }}
{% endif %}
</dd>
{% endif %}
{% if t.durationTimeVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
<dd>
{% if entity.durationTime is not null %}
{{ entity.durationTime|date('H:i') }}
{% else %}
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
{% endif %}
</dd>
{% endif %}
{% if t.travelTimeVisible %}
<dt class="inline">{{ 'Travel time'|trans }}</dt>
<dd>{% if entity.travelTime is not null %}
{{ entity.travelTime|date('H:i') }}
{% else %}
{{ 'None'|trans|capitalize }}
{% endif %}
</dd>
{% endif %}
{% if t.travelTimeVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
<dt class="inline">{{ 'Travel time'|trans }}</dt>
<dd>
{% if entity.travelTime is not null %}
{{ entity.travelTime|date('H:i') }}
{% else %}
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
{% endif %}
</dd>
{% endif %}
{% if t.commentVisible %}
<dt class="inline">{{ 'activity.comment'|trans }}</dt>
{%- if entity.comment.empty -%}
<dd><span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span></dd>
{%- else -%}
<dd>{{ entity.comment|chill_entity_render_box }}</dd>
{%- endif -%}
{% endif %}
{% if t.commentVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
<dt class="inline">{{ 'activity.comment'|trans }}</dt>
<dd>
{%- if entity.comment.empty -%}
<span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span>
{%- else -%}
{{ entity.comment|chill_entity_render_box }}
{%- endif -%}
</dd>
{% endif %}
{% if t.documentsVisible and entity.documents|length > 0 %}
<dt>{{ 'Documents'|trans }}</dt>
<dd>
<ul>
{% for d in entity.documents %}
<li>{{ m.download_button(d) }}</li>
{% endfor %}
</ul>
</dd>
{% endif %}
{% if t.documentsVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
<dt class="inline">{{ 'Documents'|trans }}</dt>
<dd>
{% if entity.documents|length > 0 %}
<ul>
{% for d in entity.documents %}
<li>{{ m.download_button(d) }}</li>
{% endfor %}
</ul>
{% else %}
<span class="chill-no-data-statement">{{ 'Any document found'|trans }}</span>
{% endif %}
</dd>
{% endif %}
{% if t.attendeeVisible %}
<dt class="inline">{{ 'Attendee'|trans }}</dt>
<dd>{% if entity.attendee is not null %}{% if entity.attendee %}{{ 'present'|trans|capitalize }} {% else %} {{ 'not present'|trans|capitalize }}{% endif %}{% else %}{{ 'None'|trans|capitalize }}{% endif %}</dd>
{% endif %}
{% if t.attendeeVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
<dt class="inline">{{ 'Attendee'|trans }}</dt>
<dd>
{% if entity.attendee is not null %}
{% if entity.attendee %}
{{ 'present'|trans|capitalize }}
{% else %}
{{ 'not present'|trans|capitalize }}
{% endif %}
{% else %}
<span class="chill-no-data-statement">{{ 'None'|trans|capitalize }}</span>
{% endif %}
</dd>
{% endif %}
</dl>
</dl>
</div>
</div>
{% set person_id = null %}
{% if person %}
@@ -140,23 +197,24 @@
{{ 'Back to the list'|trans }}
</a>
</li>
{% if is_granted('CHILL_ACTIVITY_UPDATE', entity) %}
<li>
<a class="btn btn-update" href="{{ path('chill_activity_activity_edit', { 'id': entity.id, 'person_id': person_id, 'accompanying_period_id': accompanying_course_id }) }}">
{{ 'Edit'|trans }}
</a>
</li>
{# TODO
{% endif %}
{% if is_granted('CHILL_ACTIVITY_DELETE', entity) %}
#}
<li>
<a href="{{ path('chill_activity_activity_delete', { 'id': entity.id, 'person_id' : person_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete">
{{ 'Delete'|trans }}
</a>
</li>
{#
{% endif %}
#}
</ul>
<script>
import ShowPane from "../../../../ChillMainBundle/Resources/public/vuejs/Address/components/ShowPane";
export default {
components: {ShowPane}
}
</script>

View File

@@ -8,8 +8,6 @@
{% block content -%}
<div class="activity-show">
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'accompanyingCourse'} %}
</div>
{% endblock content %}

View File

@@ -8,8 +8,6 @@
{% block personcontent -%}
<div class="activity-show">
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %}
{% include 'ChillActivityBundle:Activity:show.html.twig' with {'context': 'person'} %}
</div>
{% endblock personcontent %}

View File

@@ -92,6 +92,8 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
public function getRoles(): array
{
return [
self::SEE,
self::SEE_DETAILS,
self::CREATE_PERSON,
self::CREATE_ACCOMPANYING_COURSE,
self::UPDATE,

View File

@@ -0,0 +1,15 @@
services:
Chill\ActivityBundle\EntityListener\ActivityEntityListener:
autowire: true
autoconfigure: true
tags:
-
name: 'doctrine.orm.entity_listener'
event: 'postPersist'
entity: 'Chill\ActivityBundle\Entity\Activity'
method: 'persistActionToCourse'
-
name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'Chill\ActivityBundle\Entity\Activity'
method: 'persistActionToCourse'

View File

@@ -0,0 +1,34 @@
<?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\Activity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20211207152023 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->throwIrreversibleMigrationException('placevisible and placelabel could not be created');
}
public function getDescription(): string
{
return 'DROP place visible and place label, which are replaced by location';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE activitytype DROP placevisible');
$this->addSql('ALTER TABLE activitytype DROP placelabel');
}
}

View File

@@ -146,8 +146,8 @@ User visible: Visibilité du champ Utilisateur
User label: Libellé du champ Utilisateur
Date visible: Visibilité du champ Date
Date label: Libellé du champ Date
Place visible: Visibilité du champ Lieu
Place label: Libellé du champ Lieu
Location visible: Visibilité du champ Lieu
Location label: Libellé du champ Lieu
Third parties visible: Visibilité du champ Tiers
Third parties label: Libellé du champ Tiers
Duration time visible: Visibilité du champ Durée
@@ -222,3 +222,5 @@ Aggregate by activity type: Aggréger par type d'activité
Aggregate by activity reason: Aggréger par sujet de l'activité
Last activities: Les dernières activités
See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement

View File

@@ -162,8 +162,12 @@ class AsideActivityCategory
public function setParent(?self $parent): self
{
// cache the old result for changing it during validaiton
$this->oldParent = $this->parent;
if ($this->parent) {
$this->oldParent = $this->parent;
}
$this->parent = $parent;
dump($this);
return $this;
}

View File

@@ -137,7 +137,7 @@ final class AsideActivityFormType extends AbstractType
$timezoneUTC = new DateTimeZone('GMT');
/** @var DateTimeImmutable $data */
$data = $formEvent->getData() === null ?
DateTime::createFromFormat('U', 300) :
DateTime::createFromFormat('U', '300') :
$formEvent->getData();
$seconds = $data->getTimezone()->getOffset($data);
$data->setTimeZone($timezoneUTC);

View File

@@ -11,8 +11,17 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillDocGeneratorBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->registerForAutoconfiguration(DocGeneratorContextInterface::class)
->addTag('chill_docgen.context');
}
}

View File

@@ -0,0 +1,58 @@
<?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\DocGeneratorBundle\Context;
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
class ContextManager
{
/**
* @var DocGeneratorContextInterface[]|iterable
*/
private iterable $contexts;
public function __construct(iterable $contexts)
{
$this->contexts = $contexts;
}
/**
* @throw ContextNotFoundException when the context is not found
*/
public function getContextByDocGeneratorTemplate(DocGeneratorTemplate $docGeneratorTemplate): DocGeneratorContextInterface
{
foreach ($this->contexts as $key => $context) {
if ($docGeneratorTemplate->getContext() === $key) {
return $context;
}
}
throw new ContextNotFoundException($docGeneratorTemplate->getContext());
}
public function getContextByKey(string $searchedKey): DocGeneratorContextInterface
{
foreach ($this->contexts as $key => $context) {
if ($searchedKey === $key) {
return $context;
}
}
throw new ContextNotFoundException($searchedKey);
}
public function getContexts(): array
{
return iterator_to_array($this->contexts);
}
}

View File

@@ -11,8 +11,11 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
/**
* Interface for context for for document generation.
* Interface for context for document generation.
*/
interface DocGeneratorContextInterface
{
@@ -21,22 +24,15 @@ interface DocGeneratorContextInterface
*
* @param mixed $entity
*/
public function getData($entity): array;
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array;
/**
* Generate the form that display.
*
* @param mixed $entity
*/
public function getForm($entity);
public function getDescription(): string;
/**
* has form.
*/
public function hasForm(): bool;
public function getEntityClass(): string;
/**
* True of false which entity supports.
*/
public function supports(string $entityClass): bool;
public static function getKey(): string;
public function getName(): string;
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void;
}

View File

@@ -0,0 +1,25 @@
<?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\DocGeneratorBundle\Context;
use Symfony\Component\Form\FormBuilderInterface;
interface DocGeneratorContextWithAdminFormInterface extends DocGeneratorContextInterface
{
public function adminFormReverseTransform(array $data): array;
public function adminFormTransform(array $data): array;
public function buildAdminForm(FormBuilderInterface $builder): void;
public function hasAdminForm(): bool;
}

View File

@@ -0,0 +1,34 @@
<?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\DocGeneratorBundle\Context;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Symfony\Component\Form\FormBuilderInterface;
interface DocGeneratorContextWithPublicFormInterface extends DocGeneratorContextInterface
{
/**
* Generate the form that display.
*
* @param mixed $entity
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void;
public function getFormData(DocGeneratorTemplate $template, $entity): array;
/**
* has form.
*
* @param mixed $entity
*/
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
}

View File

@@ -0,0 +1,22 @@
<?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\DocGeneratorBundle\Context\Exception;
use RuntimeException;
class ContextNotFoundException extends RuntimeException
{
public function __construct($contextName)
{
parent::__construct(sprintf('the context with name %s is not found', $contextName));
}
}

View File

@@ -0,0 +1,25 @@
<?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\DocGeneratorBundle\Context\Exception;
use LogicException;
use function get_class;
use function gettype;
use function is_object;
class UnexpectedTypeException extends LogicException
{
public function __construct($value, string $expectedType)
{
parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value)));
}
}

View File

@@ -1,109 +0,0 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Context;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Exception;
use function count;
use function get_class;
/**
* Context that display a form to select a member of a houseHold.
*/
class HouseholdMemberSelectionContext implements DocGeneratorContextInterface
{
/**
* Get the data that will be injected to the generated document.
*
* @param mixed $entity
*/
public function getData($entity): array
{
$datas = [
'setValues' => [],
'cloneRowAndSetValues' => [],
];
$persons = $entity->getAccompanyingPeriodWork()->getPersons();
if (count($persons) > 0) {
$firstPerson = $persons[0];
$datas['setValues'][] = [
'firstPersonFirstName' => $firstPerson->getFirstName(),
'firstPersonLastName' => $firstPerson->getLastName(), ];
}
if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) {
$values = [];
foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) {
$i = 1;
$values[] = [
'personRowId' => $i,
'personFirstName' => $person->getFirstName(),
'personLastName' => $person->getLastName(),
];
}
$datas['cloneRowAndSetValues'][] = [
'personRowId', $values, ];
}
return $datas;
}
/**
* Generate the form that display.
*
* @param mixed $entity
*/
public function getForm($entity)
{
throw new Exception('No implemented yet', 1);
$choices = [];
if (get_class($entity) === AccompanyingPeriodWorkEvaluation::class) {
foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) {
$choices[$person->getId()] = $person->getName();
}
}
$builder->add('members', ChoiceType::class, [
'choices' => $choices,
'placeholder' => 'Choose a person',
'label' => 'Person to add',
]);
return $builder;
}
/**
* has form.
*/
public function hasForm(): bool
{
return true;
}
/**
* True of false which entity supports.
*/
public function supports(string $entityClass): bool
{
return
(AccompanyingPeriod::class === $entityClass)
|| (SocialAction::class === $entityClass);
}
}

View File

@@ -11,8 +11,77 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller;
use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AdminDocGeneratorTemplateController extends CRUDController
{
private ContextManager $contextManager;
public function __construct(ContextManager $contextManager)
{
$this->contextManager = $contextManager;
}
public function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = [])
{
switch ($action) {
case 'new':
$context = $this->contextManager->getContextByKey($request->get('context'));
// no break
case 'edit':
$context = $this->contextManager->getContextByDocGeneratorTemplate($entity);
return array_merge(
$defaultTemplateParameters,
['context' => $context]
);
case 'index':
return array_merge(
$defaultTemplateParameters,
['contextManager' => $this->contextManager]
);
default:
return parent::generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters); // TODO: Change the autogenerated stub
}
}
public function new(Request $request): Response
{
if (!$request->query->has('context')) {
return $this->redirectToRoute('chill_docgen_admin_template_pick-context');
}
return parent::new($request);
}
/**
* @Route("{_locale}/admin/docgen/template/pick-context", name="chill_docgen_admin_template_pick-context")
*/
public function pickContext(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
return $this->render('ChillDocGeneratorBundle:Admin/DocGeneratorTemplate:pick-context.html.twig', [
'contexts' => $this->contextManager->getContexts(),
]);
}
protected function createEntity(string $action, Request $request): object
{
/** @var DocGeneratorTemplate $entity */
$entity = parent::createEntity($action, $request);
$key = $request->query->get('context');
$context = $this->contextManager->getContextByKey($key);
$entity->setContext($key)->setEntity($context->getEntityClass());
return $entity;
}
}

View File

@@ -1,76 +0,0 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlOpenstackGenerator;
use PhpOffice\PhpWord\TemplateProcessor;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class DocGeneratorController.
*/
class DocGeneratorController extends AbstractController
{
/**
* @Route(
* "{_locale}/doc/gen/test",
* name="chill_docgenerator_test"
* )
*/
public function getDoc(Request $request, TempUrlOpenstackGenerator $tempUrlGenerator): Response
{
$p = $tempUrlGenerator->generate(
'GET',
'FORMULAIRE_AEB.docx',
$request->query->has('expires_delay') ? $request->query->getInt('expires_delay', 0) : null
);
$tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE');
file_put_contents($tmpfname, file_get_contents($p->url));
$templateProcessor = new TemplateProcessor($tmpfname);
$templateProcessor->setValues(['firstname' => 'John', 'lastname' => 'Doe']);
$tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED');
$templateProcessor->saveAs($tmpfname2);
unlink($tmpfname);
$fileContent = fopen($tmpfname2, 'rb'); // the generated file content
$response = new Response(fread($fileContent, filesize($tmpfname2)));
$disposition = HeaderUtils::makeDisposition(
HeaderUtils::DISPOSITION_ATTACHMENT,
'foo.docx'
);
$response->headers->set('Content-Disposition', $disposition);
unlink($tmpfname2);
return $response;
}
/**
* @Route(
* "{_locale}/doc/gen/test",
* name="chill_docgenerator_test"
* )
*/
public function testAction(): Response
{
return (new Response())->setContent('Test');
}
}

View File

@@ -11,28 +11,100 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller;
use Base64Url\Base64Url;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext;
use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
use PhpOffice\PhpWord\TemplateProcessor;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
// TODO à mettre dans services
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Annotation\Route;
class DocGeneratorTemplateController extends AbstractController
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Contracts\HttpClient\HttpClientInterface;
final class DocGeneratorTemplateController extends AbstractController
{
private HttpClientInterface $client;
private ContextManager $contextManager;
private DocGeneratorTemplateRepository $docGeneratorTemplateRepository;
private DriverInterface $driver;
private KernelInterface $kernel;
private LoggerInterface $logger;
private PaginatorFactory $paginatorFactory;
private TempUrlGeneratorInterface $tempUrlGenerator;
public function __construct(
ContextManager $contextManager,
DocGeneratorTemplateRepository $docGeneratorTemplateRepository,
DriverInterface $driver,
LoggerInterface $logger,
PaginatorFactory $paginatorFactory,
TempUrlGeneratorInterface $tempUrlGenerator,
KernelInterface $kernel,
HttpClientInterface $client
) {
$this->contextManager = $contextManager;
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
$this->driver = $driver;
$this->logger = $logger;
$this->paginatorFactory = $paginatorFactory;
$this->tempUrlGenerator = $tempUrlGenerator;
$this->kernel = $kernel;
$this->client = $client;
}
/**
* @Route(
* "{_locale}/admin/doc/gen/generate/test/from/{template}/for/{entityClassName}/{entityId}",
* name="chill_docgenerator_test_generate_from_template"
* )
*/
public function adminTestGenerateDocFromTemplateAction(
DocGeneratorTemplate $template,
string $entityClassName,
int $entityId,
Request $request
): Response {
return $this->generateDocFromTemplate(
$template,
$entityClassName,
$entityId,
$request,
true
);
}
/**
* @Route(
* "{_locale}/doc/gen/generate/from/{template}/for/{entityClassName}/{entityId}",
@@ -40,80 +112,235 @@ class DocGeneratorTemplateController extends AbstractController
* )
*/
public function generateDocFromTemplateAction(
TempUrlGeneratorInterface $tempUrlGenerator,
DocGeneratorTemplate $template,
string $entityClassName,
int $entityId,
Request $request
): Response {
$getUrlGen = $tempUrlGenerator->generate(
'GET',
$template->getFile()
return $this->generateDocFromTemplate(
$template,
$entityClassName,
$entityId,
$request,
false
);
}
/**
* @Route(
* "/api/1.0/docgen/templates/by-entity/{entityClassName}",
* name="chill_docgenerator_templates_for_entity_api"
* )
*/
public function listTemplateApiAction(string $entityClassName): Response
{
$nb = $this->docGeneratorTemplateRepository->countByEntity($entityClassName);
$paginator = $this->paginatorFactory->create($nb);
$entities = $this->docGeneratorTemplateRepository->findByEntity(
$entityClassName,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage()
);
$tmpfname = tempnam(sys_get_temp_dir(), 'DOC_TEMPLATE');
file_put_contents($tmpfname, file_get_contents($getUrlGen->url));
return $this->json(
new Collection($entities, $paginator),
Response::HTTP_OK,
[],
[AbstractNormalizer::GROUPS => ['read']]
);
}
$entity = $this->getDoctrine()->getRepository($entityClassName)->find($entityId);
/**
* @Route(
* "{_locale}/admin/doc/gen/generate/test/redirect",
* name="chill_docgenerator_test_generate_redirect"
* )
*
* @return void
*/
public function redirectToTestGenerate(Request $request): RedirectResponse
{
$template = $request->query->getInt('template');
if ($template->getContext() === HouseholdMemberSelectionContext::class) {
$context = new HouseholdMemberSelectionContext();
$datas = $context->getData($entity);
if (null === $template) {
throw new BadRequestHttpException('template parameter is missing');
}
$entityClassName = $request->query->get('entityClassName');
if (null === $entityClassName) {
throw new BadRequestHttpException('entityClassName is missing');
}
$entityId = $request->query->get('entityId');
if (null === $entityId) {
throw new BadRequestHttpException('entityId is missing');
}
return $this->redirectToRoute(
'chill_docgenerator_test_generate_from_template',
['template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId,
'returnPath' => $request->query->get('returnPath', '/'), ]
);
}
private function generateDocFromTemplate(
DocGeneratorTemplate $template,
string $entityClassName,
int $entityId,
Request $request,
bool $isTest
): Response {
try {
$context = $this->contextManager->getContextByDocGeneratorTemplate($template);
} catch (ContextNotFoundException $e) {
throw new NotFoundHttpException($e->getMessage(), $e);
}
$entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId);
if (null === $entity) {
throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found");
}
$contextGenerationData = [];
if ($context instanceof DocGeneratorContextWithPublicFormInterface
&& $context->hasPublicForm($template, $entity) || $isTest) {
if ($context instanceof DocGeneratorContextWithPublicFormInterface) {
$builder = $this->createFormBuilder(
array_merge(
$context->getFormData($template, $entity),
$isTest ? ['test_file' => null] : []
)
);
} else {
$builder = $this->createFormBuilder(
['test_file' => null]
);
}
$context->buildPublicForm($builder, $template, $entity);
if ($isTest) {
$builder->add('test_file', FileType::class, [
'label' => 'Template file',
'required' => false,
]);
}
$form = $builder->getForm()->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$contextGenerationData = $form->getData();
} elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) {
$templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig';
$templateOptions = ['entity' => $entity, 'form' => $form->createView(),
'template' => $template, 'context' => $context, ];
return $this->render($templatePath, $templateOptions);
}
}
if ($isTest && null !== $contextGenerationData['test_file']) {
/** @var File $file */
$file = $contextGenerationData['test_file'];
$templateResource = fopen($file->getPathname(), 'rb');
} else {
throw new Exception('Not implemented', 1);
$getUrlGen = $this->tempUrlGenerator->generate(
'GET',
$template->getFile()->getFilename()
);
$data = $this->client->request('GET', $getUrlGen->url);
$iv = $template->getFile()->getIv(); // iv as an Array
$ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt)
$method = 'AES-256-CBC';
$key = $template->getFile()->getKeyInfos()['k'];
$keyGoodFormat = Base64Url::decode($key);
$dataDecrypted = openssl_decrypt($data->getContent(), $method, $keyGoodFormat, 1, $ivGoodFormat);
if (false === $dataDecrypted) {
throw new Exception('Error during Decrypt ', 1);
}
if (false === $templateResource = fopen('php://memory', 'r+b')) {
$this->logger->error('Could not write data to memory');
throw new HttpException(500);
}
fwrite($templateResource, $dataDecrypted);
rewind($templateResource);
}
$templateProcessor = new TemplateProcessor($tmpfname);
$datas = $context->getData($template, $entity, $contextGenerationData);
foreach ($datas['setValues'] as $setValuesConf) {
$templateProcessor->setValues($setValuesConf);
try {
$generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename());
} catch (TemplateException $e) {
$msg = implode("\n", $e->getErrors());
return new Response($msg, 400, [
'Content-Type' => 'text/plain',
]);
}
foreach ($datas['cloneRowAndSetValues'] as $cloneRowAndSetValues) {
$templateProcessor->cloneRowAndSetValues($cloneRowAndSetValues[0], $cloneRowAndSetValues[1]);
fclose($templateResource);
if ($isTest) {
return new StreamedResponse(
static function () use ($generatedResource) {
fpassthru($generatedResource);
fclose($generatedResource);
},
Response::HTTP_OK,
[
'Content-Transfer-Encoding', 'binary',
'Content-Type' => 'application/vnd.oasis.opendocument.text',
'Content-Disposition' => sprintf('attachment; filename="%s.odt"', 'generated'),
'Content-Length' => fstat($generatedResource)['size'],
],
);
}
$tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED');
$templateProcessor->saveAs($tmpfname2);
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt';
unlink($tmpfname);
$fileContent = fopen($tmpfname2, 'rb'); // the generated file content
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . '.docx';
$getUrlGen = $tempUrlGenerator->generate(
$getUrlGen = $this->tempUrlGenerator->generate(
'PUT',
$genDocName
);
unlink($tmpfname2);
$client = new Client();
try {
$putResponse = $client->request('PUT', $getUrlGen->url, [
'body' => $fileContent,
'body' => $generatedResource,
]);
if ($putResponse->getStatusCode() === 201) {
$em = $this->getDoctrine()->getManager();
$storedObject = new StoredObject();
$storedObject
// currently, only docx is supported
->setType('application/vnd.openxmlformats-officedocument.wordprocessingml.document')
->setType($template->getFile()->getType())
->setFilename($genDocName);
$em->persist($storedObject);
// Only for evaluation
if ($entity instanceof AccompanyingPeriodWorkEvaluation) {
$doc = new AccompanyingPeriodWorkEvaluationDocument();
$doc
->setStoredObject($storedObject)
->setTemplate($template);
$entity->addDocument($doc);
$em->persist($doc);
try {
$context->storeGenerated($template, $storedObject, $entity, $contextGenerationData);
} catch (Exception $e) {
$this->logger->error('Could not store the associated document to entity', [
'entityClassName' => $entityClassName,
'entityId' => $entityId,
'contextKey' => $context->getName(),
]);
throw $e;
}
$em->flush();
@@ -129,29 +356,4 @@ class DocGeneratorTemplateController extends AbstractController
throw new Exception('Unable to generate document.');
}
/**
* @Route(
* "{_locale}/doc/gen/templates/for/{entityClassName}",
* name="chill_docgenerator_templates_for_entity_api"
* )
*/
public function listTemplateApiAction(
string $entityClassName,
DocGeneratorTemplateRepository $templateRepository
): Response {
$entities = $templateRepository->findByEntity($entityClassName);
$ret = [];
foreach ($entities as $entity) {
$ret[] = [
'id' => $entity->getId(),
'name' => $entity->getName(),
'description' => $entity->getDescription(),
];
}
return new JsonResponse(['results' => $ret]);
}
}

View File

@@ -12,9 +12,11 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\DataFixtures\ORM;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Service\DocGenerator\AccompanyingPeriodContext;
use DateTime;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Persistence\ObjectManager;
/**
@@ -28,31 +30,52 @@ class LoadDocGeneratorTemplate extends AbstractFixture
[
'name' => ['fr' => 'FORMULAIRE AEB'],
'desc' => 'stocké sur openstack comedienbe',
'file' => 'FORMULAIRE_AEB_WITH_DATA_INJ.docx',
'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext',
'entities' => [AccompanyingPeriodWorkEvaluation::class],
'file' => [
'filename' => 'pKNlhCrQDCRsAuC8vYHDKa',
'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}',
'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]',
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
'context' => AccompanyingPeriodContext::class,
'entity' => AccompanyingPeriod::class,
'options' => ['mainPerson' => false, 'person1' => false, 'person2' => false],
], [
'name' => ['fr' => 'AIDE ALIMENTAIRE'],
'desc' => 'stocké sur openstack comedienbe',
'file' => 'AIDE_ALIMENTAIRE.docx',
'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext',
'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction', AccompanyingPeriodWorkEvaluation::class],
'file' => [
'filename' => 'pKNlhCrQDCRsAuC8vYHDKa',
'key' => '{"alg":"A256CBC","ext":true,"k":"_VihnD41-VDHlpS-ouwtbMPnu-OXVdtA7ENQWWtAQYM","key_ops":["encrypt","decrypt"],"kty":"oct"}',
'iv' => '[86,231,83,148,117,107,149,173,130,19,105,194,224,145,8,48]',
'type' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
'context' => AccompanyingPeriodContext::class,
'entity' => AccompanyingPeriod::class,
'options' => ['mainPerson' => false, 'person1' => false, 'person2' => false],
],
];
foreach ($templates as $template) {
echo 'Adding doc generator templates ' . $template['file'] . ")\n";
$newStoredObj = (new StoredObject())
->setFilename($template['file']['filename'])
->setKeyInfos(json_decode($template['file']['key'], true))
->setIv(json_decode($template['file']['iv'], true))
->setCreationDate(new DateTime('today'))
->setType($template['file']['type']);
$manager->persist($newStoredObj);
$newTemplate = (new DocGeneratorTemplate())
->setName($template['name'])
->setDescription($template['desc'])
->setFile($template['file'])
->setFile($newStoredObj)
->setContext($template['context'])
->setEntities($template['entities']);
->setEntity($template['entity'])
->setOptions($template['options']);
$manager->persist($newTemplate);
$manager->flush();
}
$manager->flush();
}
}

View File

@@ -26,6 +26,10 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_doc_generator', $config);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader->load('services.yaml');
$loader->load('services/controller.yaml');
@@ -37,6 +41,7 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
{
$this->preprendRoutes($container);
$this->prependCruds($container);
$this->prependClientConfig($container);
}
protected function prependCruds(ContainerBuilder $container)
@@ -78,4 +83,23 @@ class ChillDocGeneratorExtension extends Extension implements PrependExtensionIn
],
]);
}
private function prependClientConfig(ContainerBuilder $container)
{
$configs = $container->getExtensionConfig($this->getAlias());
$resolvingBag = $container->getParameterBag();
$configs = $resolvingBag->resolveValue($configs);
$config = $this->processConfiguration(new Configuration(), $configs);
$container->prependExtensionConfig('framework', [
'http_client' => [
'scoped_clients' => [
'relatorio.client' => [
'scope' => $config['driver']['relatorio']['url'],
],
],
],
]);
}
}

View File

@@ -14,21 +14,35 @@ namespace Chill\DocGeneratorBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
/**
* This is the class that validates and merges configuration from your app/config files.
*
* To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/configuration.html}
*/
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('chill_calendar');
$rootNode = $treeBuilder->getRootNode('chill_calendar');
$treeBuilder = new TreeBuilder('chill_doc_generator');
$rootNode = $treeBuilder->getRootNode();
// Here you should define the parameters that are allowed to
// configure your bundle. See the documentation linked above for
// more information on that topic.
$rootNode
->children()
->arrayNode('driver')
->addDefaultsIfNotSet()
->children()
->enumNode('type')
->isRequired()
->values(['relatorio'])
->defaultValue('relatorio')
->end()
->arrayNode('relatorio')
->addDefaultsIfNotSet()
->children()
->scalarNode('url')
->isRequired()
->defaultValue('http://relatorio:8888/')
->end()
->end()
->end()
->end()
->end()
->end();
return $treeBuilder;
}

View File

@@ -11,44 +11,51 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Entity;
use Chill\DocStoreBundle\Entity\StoredObject;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
* @ORM\Entity
* @ORM\Table(name="chill_docgen_template")
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "docgen_template": DocGeneratorTemplate::class
* })
*/
class DocGeneratorTemplate
{
/**
* @ORM\Column(type="string", length=255)
*
* Class name of the context to use
* @ORM\Column(type="boolean", options={"default": true})
*/
private bool $active = true;
/**
* Class name of the context to use.
*
* so if $context = ''
* this template will use '' as context
*
* @ORM\Column(type="string", length=255)
*/
private string $context;
/**
* @ORM\Column(type="text", nullable=true)
* @Serializer\Groups({"read"})
*/
private string $description;
private ?string $description = null;
/**
* @ORM\Column(type="simple_array")
* Class name of the entity for which this template can be used.
*
* Class name of the entities for which this template can be used
*
* so if $entities = ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction']
* this template can be selected for an AccompanyingPeriod or a SocialAction
* @ORM\Column(type="string", options={"default": ""})
*/
private array $entities = [];
private string $entity = '';
/**
* @ORM\Column(type="string", length=255)
* @ORM\ManyToOne(targetEntity=StoredObject::class, cascade={"persist"}))
*/
private string $file;
private ?StoredObject $file = null;
/**
* @ORM\Id
@@ -64,6 +71,13 @@ class DocGeneratorTemplate
*/
private array $name = [];
/**
* Options for the template.
*
* @ORM\Column(type="json", name="template_options", options={"default":"[]"})
*/
private array $options = [];
public function getContext(): ?string
{
return $this->context;
@@ -74,12 +88,12 @@ class DocGeneratorTemplate
return $this->description;
}
public function getEntities(): ?array
public function getEntity(): string
{
return $this->entities;
return $this->entity;
}
public function getFile(): ?string
public function getFile(): ?StoredObject
{
return $this->file;
}
@@ -94,6 +108,23 @@ class DocGeneratorTemplate
return $this->name;
}
public function getOptions(): array
{
return $this->options;
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): DocGeneratorTemplate
{
$this->active = $active;
return $this;
}
public function setContext(string $context): self
{
$this->context = $context;
@@ -108,14 +139,14 @@ class DocGeneratorTemplate
return $this;
}
public function setEntities(array $entities): self
public function setEntity(string $entity): self
{
$this->entities = $entities;
$this->entity = $entity;
return $this;
}
public function setFile(string $file): self
public function setFile(StoredObject $file): self
{
$this->file = $file;
@@ -128,4 +159,11 @@ class DocGeneratorTemplate
return $this;
}
public function setOptions(array $options): self
{
$this->options = $options;
return $this;
}
}

View File

@@ -11,22 +11,64 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Form;
use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class DocGeneratorTemplateType extends AbstractType
{
private ContextManager $contextManager;
public function __construct(ContextManager $contextManager)
{
$this->contextManager = $contextManager;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
/** @var DocGeneratorTemplate $template */
$template = $options['data'];
$context = $this->contextManager->getContextByKey($template->getContext());
$builder
->add('name', TranslatableStringFormType::class, [
'label' => 'Nom',
])
->add('description')
->add('file');
->add('file', StoredObjectType::class, [
'error_bubbling' => true,
])
->add('active', ChoiceType::class, [
'label' => 'Active',
'choices' => [
'Yes' => true,
'No' => false,
],
'required' => true,
]);
if ($context instanceof DocGeneratorContextWithAdminFormInterface
&& $context->hasAdminForm()) {
$sub = $builder
->create('options', null, ['compound' => true])
->addModelTransformer(new CallbackTransformer(
static function (array $data) use ($context) {
return $context->adminFormTransform($data);
},
static function (array $data) use ($context) {
return $context->adminFormReverseTransform($data);
}
));
$context->buildAdminForm($sub);
$builder->add($sub);
}
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -0,0 +1,22 @@
<?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\DocGeneratorBundle\GeneratorDriver;
interface DriverInterface
{
/**
* @param resource $template
*
* @return resource
*/
public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null);
}

View File

@@ -0,0 +1,35 @@
<?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\DocGeneratorBundle\GeneratorDriver\Exception;
use RuntimeException;
use Throwable;
/**
* A exception to throw when there are syntax errors in the
* template file.
*/
class TemplateException extends RuntimeException
{
private array $errors;
public function __construct(array $errors, $code = 0, ?Throwable $previous = null)
{
parent::__construct('Error while generating document from template', $code, $previous);
$this->errors = $errors;
}
public function getErrors(): array
{
return $this->errors;
}
}

View File

@@ -0,0 +1,92 @@
<?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\DocGeneratorBundle\GeneratorDriver;
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Mime\Part\DataPart;
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class RelatorioDriver implements DriverInterface
{
private LoggerInterface $logger;
private HttpClientInterface $relatorioClient;
private string $url;
public function __construct(
HttpClientInterface $relatorioClient,
ParameterBagInterface $parameterBag,
LoggerInterface $logger
) {
$this->relatorioClient = $relatorioClient;
$this->logger = $logger;
$this->url = $parameterBag->get('chill_doc_generator')['driver']['relatorio']['url'];
}
public function generateFromResource($template, string $resourceType, array $data, ?string $templateName = null)
{
$formFields = [
'variables' => json_encode($data),
'template' => new DataPart($template, $templateName ?? uniqid('template_'), $resourceType),
];
$form = new FormDataPart($formFields);
try {
$response = $this->relatorioClient->request('POST', $this->url, [
'headers' => $form->getPreparedHeaders()->toArray(),
'body' => $form->bodyToIterable(),
]);
return $response->toStream();
} catch (HttpExceptionInterface $e) {
$content = $e->getResponse()->getContent(false);
if (400 === $e->getResponse()->getStatusCode()) {
$content = json_decode($content, true);
$this->logger->error('relatorio: template error', [
'error' => $content['message'] ?? '_not defined',
]);
throw new TemplateException([$content['message']]);
}
$this->logger->error('relatorio: error while generating document', [
'msg' => $e->getMessage(),
'response' => $e->getResponse()->getStatusCode(),
'content' => $content,
]);
throw $e;
} catch (TransportExceptionInterface $e) {
$this->logger->error('relatorio: transport exception', [
'msg' => $e->getMessage(),
'e' => $e->getTraceAsString(),
]);
throw $e;
} catch (DecodingExceptionInterface $e) {
$this->logger->error('relatorio: could not decode response', [
'msg' => $e->getMessage(),
'e' => $e->getTraceAsString(),
]);
throw $e;
}
}
}

View File

@@ -0,0 +1,50 @@
<?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\DocGeneratorBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
class AdminMenuBuilder implements LocalMenuBuilderInterface
{
private Security $security;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator, Security $security)
{
$this->translator = $translator;
$this->security = $security;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if ($this->security->isGranted('ROLE_ADMIN')) {
if (in_array($menuId, ['admin_index', 'admin_section'], true)) {
$menu->addChild($this->translator->trans('docgen.Document generation'), [
'route' => 'chill_crud_docgen_template_index',
])->setExtras([
'order' => 350,
'explain' => 'docgen.Manage templates and document generation',
]);
}
}
}
public static function getMenuIds(): array
{
return ['admin_index', 'admin_section', 'docgen_admin'];
}
}

View File

@@ -25,6 +25,18 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
$this->repository = $entityManager->getRepository(DocGeneratorTemplate::class);
}
public function countByEntity(string $entity): int
{
$builder = $this->repository->createQueryBuilder('t');
$builder
->select('count(t)')
->where('t.entity LIKE :entity')
->setParameter('entity', addslashes($entity));
return $builder->getQuery()->getSingleScalarResult();
}
public function find($id, $lockMode = null, $lockVersion = null): ?DocGeneratorTemplate
{
return $this->repository->find($id, $lockMode, $lockVersion);
@@ -49,15 +61,22 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findByEntity($entity)
/**
* @return array|DocGeneratorTemplate[]
*/
public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array
{
$builder = $this->repository->createQueryBuilder('t');
$builder
->where('t.entities LIKE :entity')
->setParameter('entity', '%' . addslashes($entity) . '%');
->where('t.entity LIKE :entity')
->setParameter('entity', addslashes($entity));
return $builder->getQuery()->execute();
return $builder
->getQuery()
->setFirstResult($start)
->setMaxResults($limit)
->getResult();
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?DocGeneratorTemplate

View File

@@ -0,0 +1,10 @@
import { fetchResults } from "ChillMainAssets/lib/api/apiMethods.js";
const fetchTemplates = (entityClass) => {
let fqdnEntityClass = encodeURI(entityClass);
return fetchResults(`/api/1.0/docgen/templates/by-entity/${fqdnEntityClass}`);
}
export {
fetchTemplates
};

View File

@@ -0,0 +1,27 @@
import {createApp} from 'vue';
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
import {_createI18n} from 'ChillMainAssets/vuejs/_js/i18n';
const i18n = _createI18n({});
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
fetchTemplates(el.dataset.entityClass).then(templates => {
let
picker = {
template: '<pick-template :templates="this.templates" :entityId="this.entityId"></pick-template>',
components: {
PickTemplate,
},
data() {
return {
templates: templates,
entityId: el.dataset.entityId,
}
},
}
;
createApp(picker).use(i18n).mount(el);
})
});

View File

@@ -0,0 +1,141 @@
<template>
<div>
<template v-if="templates.length > 0">
<slot name="title">
<h2>{{ $t('generate_document')}}</h2>
</slot>
<div class="container">
<div class="row">
<div class="col-md-4">
<slot name="label">
<label>{{ $t('select_a_template') }}</label>
</slot>
</div>
<div class="col-md-8">
<div class="input-group mb-3">
<select class="form-select" v-model="template">
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
<template v-for="t in templates">
<option v-bind:value="t.id">{{ t.name.fr }}</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>
</div>
</div>
</div>
<div class="row" v-if="hasDescription">
<div class="col-md-8 align-self-end">
<p>{{ getDescription }}</p>
</div>
</div>
</div>
</template>
</div>
</template>
<script>
export default {
name: "PickTemplate",
props: {
entityId: [String, Number],
entityClass: {
type: String,
required: true,
},
templates: {
required: true,
},
// beforeMove execute "something" before
beforeMove: {
type: Function,
required: false,
}
},
data() {
return {
template: null,
}
},
computed: {
canGenerate() {
return this.template != null;
},
hasDescription() {
if (this.template == null) {
return false;
}
return true;
},
getDescription() {
return this.templates.find(t => t.id === this.template).description || '';
},
},
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;
}
}
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);
},
},
i18n: {
messages: {
fr: {
generate_document: 'Générer un document',
select_a_template: 'Choisir un gabarit',
choose_a_template: 'Choisir',
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -1,12 +1,25 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block title 'docgen.Edit template'|trans %}
{% block layout_wvm_content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block crud_content_header %}
<h1>{{ 'docgen.Edit template'|trans }}</h1>
<h2>{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}</h2>
{% endblock crud_content_header %}
{% block content_form_actions_view %}{% endblock %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}

View File

@@ -1,19 +1,40 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block layout_wvm_content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %}
<th></th>
<th>{{ 'Title'|trans }}</th>
<th>{{ 'File'|trans }}</th>
<th>{{ 'docgen.Context'|trans }}</th>
<th>{{ 'docgen.test generate'|trans }}</th>
<th>{{ 'Edit'|trans }}</th>
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
<td>{{ entity.name | localize_translatable_string }}</td>
<td>{{ entity.file }}</td>
<td>{{ entity.name|localize_translatable_string}}</td>
<td>{{ contextManager.getContextByKey(entity.context).name|trans }}</td>
<td>
<form method="get" action="{{ path('chill_docgenerator_test_generate_redirect') }}">
<input type="hidden" name="returnPath" value="{{ app.request.query.get('returnPath', '/')|e('html_attr') }}" />
<input type="hidden" name="template" value="{{ entity.id|e('html_attr') }}" />
<input type="hidden" name="entityClassName" value="{{ contextManager.getContextByKey(entity.context).entityClass|e('html_attr') }}" />
<input type="text" name="entityId" />
<ul class="record_actions">
<li>
<button type="submit" class="btn btn-mini btn-neutral">{{ 'docgen.test generate'|trans }}</button>
</li>
</ul>
</form>
</td>
<td>
<a href="{{ chill_path_add_return_path('chill_crud_docgen_template_edit', { 'id': entity.id }) }}" class="btn btn-edit">
{{ 'Edit'|trans }}
</a>
</td>
</tr>
{% endfor %}
{% endblock %}

View File

@@ -1,11 +1,25 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block title 'docgen.New template'|trans %}
{% block layout_wvm_content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block crud_content_header %}
<h1>{{ 'docgen.New template'|trans }}</h1>
<h2>{{ 'docgen.With context %name%'|trans({'%name%': context.name|trans }) }}</h2>
{% endblock crud_content_header %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends '@ChillDocGenerator/Admin/layout.html.twig' %}
{% block title 'docgen.Pick template context'|trans %}
{% block layout_wvm_content %}
<div class="col-md-10 col-xxl">
<h1>{{ block('title') }}</h1>
<div class="container">
{% for key, context in contexts %}
<div class="row">
<div class="col-md-4">
<a
href="{{ path('chill_crud_docgen_template_new', { 'context': key }) }}"
class="btn btn-outline-chill-green-dark">
{{ context.name|trans }}
</a>
</div>
<div class="col-md-8">
{{ context.description|trans|nl2br }}
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,4 @@
{% extends '@ChillPerson/Admin/layout.html.twig' %}
{% block vertical_menu_content %}
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends 'ChillMainBundle::layout.html.twig' %}
{% block title 'docgen.Generate a document'|trans %}
{% block content %}
<div class="col-md-10 col-xxl">
<h1>{{ block('title') }}</h1>
<h2>{{ template.name|localize_translatable_string }}</h2>
{{ form_start(form, { 'attr': { 'id': 'generate_doc_form' }}) }}
{{ form(form) }}
{{ form_end(form) }}
<ul class="record_actions sticky-form-buttons">
<li>
<button type="submit" class="btn btn-edit change-icon" form="generate_doc_form">
<i class="fa fa-cog"></i> {{ 'docgen.Generate'|trans }}
</button>
</li>
</ul>
</div>
{% endblock %}

View File

@@ -16,16 +16,27 @@ use function array_merge;
class NormalizeNullValueHelper
{
private ?string $discriminatorType = null;
private ?string $discriminatorValue = null;
private NormalizerInterface $normalizer;
public function __construct(NormalizerInterface $normalizer)
public function __construct(NormalizerInterface $normalizer, $discriminatorType = null, $discriminatorValue = null)
{
$this->normalizer = $normalizer;
$this->discriminatorType = $discriminatorType;
$this->discriminatorValue = $discriminatorValue;
}
public function normalize(array $attributes, string $format = 'docgen', ?array $context = [])
{
$data = [];
$data['isNull'] = true;
if (null !== $this->discriminatorType) {
$data[$this->discriminatorType] = $this->discriminatorValue;
}
foreach ($attributes as $key => $class) {
if (is_numeric($key)) {

View File

@@ -0,0 +1,56 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
use ArrayObject;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
/**
* Normalize a collection for docgen format.
*/
class CollectionDocGenNormalizer implements ContextAwareNormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
/**
* @param Collection $object
*
* @return array|ArrayObject|bool|float|int|string|void|null
*/
public function normalize($object, ?string $format = null, array $context = [])
{
$data = [];
if (null === $object || 0 === $object->count()) {
return $data;
}
foreach ($object->getIterator() as $item) {
$data[] = $this->normalizer->normalize($item, $format, $context);
}
return $data;
}
public function supportsNormalization($data, ?string $format = null, array $context = [])
{
if ('docgen' !== $format) {
return false;
}
return $data instanceof Collection
|| (null === $data && Collection::class === ($context['docgen:expects'] ?? null));
}
}

View File

@@ -12,7 +12,9 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use ReflectionClass;
use RuntimeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
@@ -41,18 +43,32 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
private PropertyAccessor $propertyAccess;
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory)
{
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ClassMetadataFactoryInterface $classMetadataFactory,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->classMetadataFactory = $classMetadataFactory;
$this->propertyAccess = PropertyAccess::createPropertyAccessor();
$this->translatableStringHelper = $translatableStringHelper;
}
public function normalize($object, ?string $format = null, array $context = [])
{
$classMetadataKey = $object ?? $context['docgen:expects'];
$classMetadataKey = $object ?? $context['docgen:expects'] ?? null;
if (null === $classMetadataKey) {
throw new RuntimeException('Could not determine the metadata for this object. Either provide a non-null object, or a "docgen:expects" key in the context');
}
if (!$this->classMetadataFactory->hasMetadataFor($classMetadataKey)) {
throw new LogicException(sprintf('This object does not have metadata: %s. Add groups on this entity to allow to serialize with the format %s and groups %s', is_object($object) ? get_class($object) : $context['docgen:expects'], $format, implode(', ', $context['groups'])));
throw new LogicException(sprintf(
'This object does not have metadata: %s. Add groups on this entity to allow to serialize with the format %s and groups %s',
is_object($object) ? get_class($object) : '(todo' /*$context['docgen:expects'],*/ ,
$format,
implode(', ', ($context['groups'] ?? []))
));
}
$metadata = $this->classMetadataFactory->getMetadataFor($classMetadataKey);
@@ -86,19 +102,56 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
private function getExpectedType(AttributeMetadata $attribute, ReflectionClass $reflection): string
{
// we have to get the expected content
if ($reflection->hasProperty($attribute->getName())) {
$type = $reflection->getProperty($attribute->getName())->getType();
} elseif ($reflection->hasMethod($attribute->getName())) {
$type = $reflection->getMethod($attribute->getName())->getReturnType();
} else {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s. Add attribute property only on property or method',
$attribute->getName()
));
}
$type = null;
if (null === $type) {
do {
// we have to get the expected content
if ($reflection->hasProperty($attribute->getName())) {
if (!$reflection->getProperty($attribute->getName())->hasType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s on class %s. Add a type on this property',
$attribute->getName(),
$reflection->getName()
));
}
$type = $reflection->getProperty($attribute->getName())->getType();
} elseif ($reflection->hasMethod($method = 'get' . ucfirst($attribute->getName()))) {
if (!$reflection->getMethod($method)->hasReturnType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
$attribute->getName(),
$reflection->getName()
));
}
$type = $reflection->getMethod($method)->getReturnType();
} elseif ($reflection->hasMethod($method = 'is' . ucfirst($attribute->getName()))) {
if (!$reflection->getMethod($method)->hasReturnType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
$attribute->getName(),
$reflection->getName()
));
}
$type = $reflection->getMethod($method)->getReturnType();
} elseif ($reflection->hasMethod($attribute->getName())) {
if (!$reflection->getMethod($attribute->getName())->hasReturnType()) {
throw new \LogicException(sprintf(
'Could not determine how the content is determined for the attribute %s on class %s. Add a return type on the method',
$attribute->getName(),
$reflection->getName()
));
}
$type = $reflection->getMethod($attribute->getName())->getReturnType();
} else {
$reflection = $reflection->getParentClass();
}
} while (null === $type && $reflection instanceof ReflectionClass);
if (null === $type ?? null) {
throw new \LogicException(sprintf(
'Could not determine the type for this attribute: %s. Add a return type to the method or property declaration',
$attribute->getName()
@@ -115,12 +168,32 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
{
$keys = [];
// add a discriminator
if (null !== $discriminator = $metadata->getClassDiscriminatorMapping()) {
$typeKey = $discriminator->getTypeProperty();
$typeValue = null;
foreach ($discriminator->getTypesMapping() as $type => $typeClass) {
if ($typeClass === $context['docgen:expects']) {
$typeValue = $type;
break;
}
}
if (null === $typeValue) {
$typeKey = null;
}
} else {
$typeKey = $typeValue = null;
}
foreach ($attributes as $attribute) {
$key = $attribute->getSerializedName() ?? $attribute->getName();
$keys[$key] = $this->getExpectedType($attribute, $metadata->getReflectionClass());
}
$normalizer = new NormalizeNullValueHelper($this->normalizer);
$normalizer = new NormalizeNullValueHelper($this->normalizer, $typeKey, $typeValue);
return $normalizer->normalize($keys, $format, $context);
}
@@ -134,11 +207,19 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
switch ($type) {
case 'array':
if (in_array('is-translatable', $attribute->getNormalizationContextForGroups(['docgen:read']), true)) {
return '';
}
return [];
case 'bool':
case 'double':
case 'float':
case 'int':
case 'resource':
return null;
case 'string':
return '';
@@ -166,14 +247,37 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
private function normalizeObject($object, $format, array $context, array $expectedGroups, ClassMetadata $metadata, array $attributes)
{
$data = [];
$data['isNull'] = false;
$reflection = $metadata->getReflectionClass();
// add a discriminator
if (null !== $discriminator = $metadata->getClassDiscriminatorMapping()) {
$data[$discriminator->getTypeProperty()] = $discriminator->getMappedObjectType($object);
}
foreach ($attributes as $attribute) {
/** @var AttributeMetadata $attribute */
$value = $this->propertyAccess->getValue($object, $attribute->getName());
$key = $attribute->getSerializedName() ?? $attribute->getName();
$isTranslatable = $attribute->getNormalizationContextForGroups(
is_array($context['groups']) ? $context['groups'] : [$context['groups']]
)['is-translatable'] ?? false;
if (is_object($value)) {
if ($isTranslatable) {
$data[$key] = $this->translatableStringHelper
->localize($value);
} elseif (is_iterable($value)) {
$arr = [];
foreach ($value as $k => $v) {
$arr[$k] =
$this->normalizer->normalize($v, $format, array_merge(
$context,
$attribute->getNormalizationContextForGroups($expectedGroups)
));
}
$data[$key] = $arr;
} elseif (is_object($value)) {
$data[$key] =
$this->normalizer->normalize($value, $format, array_merge(
$context,
@@ -182,7 +286,7 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
} elseif (null === $value) {
$data[$key] = $this->normalizeNullOutputValue($format, $context, $attribute, $reflection);
} else {
$data[$key] = (string) $value;
$data[$key] = $value;
}
}

View File

@@ -3,4 +3,6 @@ module.exports = function(encore, entries) {
encore.addAliases({
ChillDocGeneratorAssets: __dirname + '/Resources/public'
});
encore.addEntry('mod_docgen_picktemplate', __dirname + '/Resources/public/module/PickTemplate/index.js');
};

View File

@@ -9,9 +9,38 @@ services:
autoconfigure: true
resource: '../Repository/'
Chill\DocGeneratorBundle\Menu\:
autoconfigure: true
autowire: true
resource: '../Menu/'
Chill\DocGeneratorBundle\Serializer\Normalizer\:
autowire: true
autoconfigure: true
resource: '../Serializer/Normalizer/'
tags:
- { name: 'serializer.normalizer', priority: -152 }
Chill\DocGeneratorBundle\Serializer\Normalizer\CollectionDocGenNormalizer:
tags:
- { name: 'serializer.normalizer', priority: -126 }
Chill\DocGeneratorBundle\Controller\:
resource: "../Controller"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\Form\:
resource: "../Form/"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\GeneratorDriver\:
resource: "../GeneratorDriver/"
autowire: true
autoconfigure: true
Chill\DocGeneratorBundle\Driver\RelatorioDriver: '@Chill\DocGeneratorBundle\Driver\DriverInterface'
Chill\DocGeneratorBundle\Context\ContextManager:
arguments:
$contexts: !tagged_iterator { tag: chill_docgen.context, default_index_method: getKey }

View File

@@ -0,0 +1,46 @@
<?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\DocGenerator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Using DocStore objects inside the DocGenTemplate.
*/
final class Version20211103111010 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template DROP CONSTRAINT FK_49A347E893CB796C');
$this->addSql('DROP INDEX IDX_49A347E893CB796C');
$this->addSql('ALTER TABLE chill_docgen_template ADD file VARCHAR(255) NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template DROP file_id');
$this->addSql('ALTER TABLE chill_docgen_template ALTER entities DROP NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ALTER context DROP NOT NULL');
}
public function getDescription(): string
{
return 'Using DocStore objects inside the DocGenTemplate';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template ADD file_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_docgen_template DROP file');
$this->addSql('ALTER TABLE chill_docgen_template ALTER entities SET NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ALTER context SET NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ADD CONSTRAINT FK_49A347E893CB796C FOREIGN KEY (file_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_49A347E893CB796C ON chill_docgen_template (file_id)');
}
}

View File

@@ -0,0 +1,41 @@
<?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\DocGenerator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20211201191757 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template DROP active');
$this->addSql('ALTER TABLE chill_docgen_template DROP entity');
$this->addSql('ALTER TABLE chill_docgen_template DROP template_options');
$this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\'');
}
public function getDescription(): string
{
return 'Add options, active and link to entity in docgen_template';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_docgen_template ADD active BOOLEAN DEFAULT true NOT NULL');
$this->addSql('ALTER TABLE chill_docgen_template ADD entity VARCHAR(255) NOT NULL DEFAULT \'\'');
$this->addSql('ALTER TABLE chill_docgen_template ADD template_options JSONB NOT NULL DEFAULT \'[]\'::jsonb ');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.template_options IS \'(DC2Type:json)\'');
$this->addSql('ALTER TABLE chill_docgen_template DROP entities');
}
}

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\tests\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
@@ -35,25 +35,36 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
public function testNormalizationBasic()
{
$user = new User();
$user->setUsername('User Test');
$user->setMainCenter($center = new Center());
$center->setName('test');
$scope = new Scope();
$scope->setName(['fr' => 'scope']);
$normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read']]);
$normalized = $this->normalizer->normalize($scope, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
$expected = [
'label' => 'User Test',
'email' => '',
'mainCenter' => [
'name' => 'test',
],
'id' => null,
'name' => 'scope',
'type' => 'scope',
'isNull' => false,
];
$this->assertEquals($expected, $normalized, 'test normalization fo an user');
$this->assertEquals($expected, $normalized, 'test normalization fo a scope');
}
public function testNormalizeNull()
{
$actual = $this->normalizer->normalize(null, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => Scope::class]);
$expected = [
'id' => '',
'name' => '',
'type' => 'scope',
'isNull' => true,
];
$this->assertEquals($expected, $actual, 'test normalization for a null scope');
}
public function testNormalizeNullObjectWithObjectEmbedded()
{
$this->markTestIncomplete('test to implement');
$normalized = $this->normalizer->normalize(null, 'docgen', [
AbstractNormalizer::GROUPS => ['docgen:read'],
'docgen:expects' => User::class,
@@ -62,7 +73,7 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
$expected = [
'label' => '',
'email' => '',
'mainCenter' => [
'main_center' => [
'name' => '',
],
];
@@ -72,10 +83,11 @@ final class DocGenObjectNormalizerTest extends KernelTestCase
public function testNormalizeWithNullValueEmbedded()
{
$this->markTestIncomplete('test to write');
$user = new User();
$user->setUsername('User Test');
$normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read']]);
$normalized = $this->normalizer->normalize($user, 'docgen', [AbstractNormalizer::GROUPS => ['docgen:read'], 'docgen:expects' => User::class]);
$expected = [
'label' => 'User Test',
'email' => '',

View File

@@ -0,0 +1,19 @@
docgen:
Generate a document: Génerer un document
Generate: Génerer
Document generation: Génération de documents
Manage templates and document generation: Gestion des documents générés et de leurs gabarits
Pick template context: Choisir un contexte
Context: Contexte
New template: Nouveau gabarit
Edit template: Modifier gabarit
With context: 'Avec le contexte :'
crud:
docgen_template:
index:
title: Génération de documents
add_new: Créer

View File

@@ -15,7 +15,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table("chill_doc.document_category")
* @ORM\Entity(repositoryClass="Chill\DocStoreBundle\EntityRepository\DocumentCategoryRepository")
* @ORM\Entity
*/
class DocumentCategory
{

View File

@@ -1,31 +0,0 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\DocStoreBundle\EntityRepository;
use Doctrine\ORM\EntityRepository;
/**
* Get an available idInsideBUndle.
*/
class DocumentCategoryRepository extends EntityRepository
{
public function nextIdInsideBundle()
{
$array_res = $this->getEntityManager()
->createQuery(
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
)
->getSingleResult();
return reset($array_res);
}
}

View File

@@ -11,8 +11,9 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Form;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\Document;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType;
@@ -70,11 +71,11 @@ class AccompanyingCourseDocumentType extends AbstractType
//TODO : adapt to using AccompanyingCourseDocument categories. Currently there are none...
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory',
'class' => DocumentCategory::class,
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass')
->setParameter('docClass', PersonDocument::class);
->setParameter('docClass', AccompanyingCourseDocument::class);
},
'choice_label' => function ($entity = null) {
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';

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\DocumentCategory;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
/**
* Get an available idInsideBUndle.
*/
class DocumentCategoryRepository implements ObjectRepository
{
private EntityManagerInterface $em;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->repository = $em->getRepository(DocumentCategory::class);
}
/**
* @param mixed $id
*/
public function find($id): ?DocumentCategory
{
return $this->repository->find($id);
}
/**
* @return array|DocumentCategory[]
*/
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): ?DocumentCategory
{
return $this->findOneBy($criteria);
}
public function getClassName()
{
return DocumentCategory::class;
}
public function nextIdInsideBundle()
{
$array_res = $this->em
->createQuery(
'SELECT MAX(c.idInsideBundle) + 1 FROM ChillDocStoreBundle:DocumentCategory c'
)
->getSingleResult();
return reset($array_res);
}
}

View File

@@ -5,7 +5,7 @@ var algo = 'AES-CBC';
var initializeButtons = (root) => {
var
buttons = root.querySelectorAll('a[data-download-button]');
for (let i = 0; i < buttons.length; i ++) {
initialize(buttons[i]);
}
@@ -33,9 +33,12 @@ var download = (button) => {
fetchError = "Error while fetching file",
key, url
;
console.log('keyData', keyData);
console.log('ivData', ivData);
button.textContent = labelPreparing;
window.fetch(urlGenerator)
.then((r) => {
if (r.ok) {
@@ -46,26 +49,19 @@ var download = (button) => {
})
.then(data => {
url = data.url;
return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']);
})
.catch(e => {
console.error("error while importing key");
console.error(e);
button.appendChild(document.createTextNode(decryptError));
if (keyData.length > 0) {
return window.crypto.subtle.importKey('jwk', keyData, { name: algo, iv: iv}, false, ['decrypt']);
}
return Promise.resolve(undefined);
})
.then(nKey => {
key = nKey;
return window.fetch(url);
})
.catch(e => {
console.error("error while fetching data");
console.error(e);
button.textContent = "";
button.appendChild(document.createTextNode(fetchError));
})
.then(r => {
console.log('r', r);
if (r.ok) {
return r.arrayBuffer();
} else {
@@ -73,16 +69,16 @@ var download = (button) => {
}
})
.then(buffer => {
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
})
.catch(e => {
console.error("error while importing key");
console.error(e);
button.textContent = "";
button.appendChild(document.createTextNode(decryptError));
console.log('buffer', buffer);
if (keyData.length > 0) {
return window.crypto.subtle.decrypt({ name: algo, iv: iv }, key, buffer);
}
return Promise.resolve(buffer);
})
.then(decrypted => {
var
console.log('decrypted', decrypted);
var
blob = new Blob([decrypted], { type: mimeType }),
url = window.URL.createObjectURL(blob)
;

View File

@@ -41,5 +41,6 @@
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}

View File

@@ -11,6 +11,13 @@
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{{ 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') }}
{% endblock %}
{% block content %}
@@ -29,7 +36,7 @@
{% for document in documents %}
<tr>
<td>{{ document.title }}</td>
<td>{{ document.category.name|localize_translatable_string }}</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) %}
@@ -58,6 +65,8 @@
</tbody>
</table>
<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">
<li class="create">

View File

@@ -42,5 +42,6 @@
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}

View File

@@ -14,6 +14,11 @@
{{ encore_entry_script_tags('mod_async_upload') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{% endblock %}
{% block content %}
<h1>{{ 'Document %title%' | trans({ '%title%': document.title }) }}</h1>
@@ -22,8 +27,10 @@
<dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd>
<dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</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>

View File

@@ -19,7 +19,6 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;
use function count;
@@ -145,7 +144,7 @@ class EventSearch extends AbstractSearch
{
// add security clauses
$reachableCenters = $this->helper
->getReachableCenters($this->user, new Role('CHILL_EVENT_SEE'));
->getReachableCenters($this->user, 'CHILL_EVENT_SEE');
if (count($reachableCenters) === 0) {
// add a clause to block all events
@@ -158,7 +157,7 @@ class EventSearch extends AbstractSearch
foreach ($reachableCenters as $center) {
$circles = $this->helper->getReachableScopes(
$this->user,
new Role('CHILL_EVENT_SEE'),
'CHILL_EVENT_SEE',
$center
);
$where = $qb->expr()->andX(

View File

@@ -23,7 +23,6 @@ use Doctrine\ORM\Mapping\ClassMetadata;
use LogicException;
use RuntimeException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use function count;
/**
@@ -188,7 +187,7 @@ class TimelineEventProvider implements TimelineProviderInterface
ClassMetadata $metadataPerson,
Person $person
) {
$role = new Role('CHILL_EVENT_SEE');
$role = 'CHILL_EVENT_SEE';
$reachableCenters = $this->helper->getReachableCenters($this->user, $role);
$associationMapping = $metadataParticipation->getAssociationMapping('person');

View File

@@ -12,14 +12,66 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Chill\MainBundle\Repository\AddressReferenceRepository;
use Chill\MainBundle\Serializer\Model\Collection;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use function trim;
/**
* Class AddressReferenceAPIController.
*/
class AddressReferenceAPIController extends ApiController
final class AddressReferenceAPIController extends ApiController
{
private AddressReferenceRepository $addressReferenceRepository;
private PaginatorFactory $paginatorFactory;
public function __construct(AddressReferenceRepository $addressReferenceRepository, PaginatorFactory $paginatorFactory)
{
$this->addressReferenceRepository = $addressReferenceRepository;
$this->paginatorFactory = $paginatorFactory;
}
/**
* @Route("/api/1.0/main/address-reference/by-postal-code/{id}/search.json")
*/
public function search(PostalCode $postalCode, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_USER');
if (!$request->query->has('q')) {
throw new BadRequestHttpException('You must supply a "q" parameter');
}
$pattern = $request->query->get('q');
if ('' === trim($pattern)) {
throw new BadRequestHttpException('the search pattern is empty');
}
$nb = $this->addressReferenceRepository->countByPostalCodePattern($postalCode, $pattern);
$paginator = $this->paginatorFactory->create($nb);
$addresses = $this->addressReferenceRepository->findByPostalCodePattern(
$postalCode,
$pattern,
false,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage()
);
return $this->json(
new Collection($addresses, $paginator),
Response::HTTP_OK,
[],
[AbstractNormalizer::GROUPS => ['read']]
);
}
protected function customizeQuery(string $action, Request $request, $qb): void
{
if ($request->query->has('postal_code')) {

View File

@@ -108,7 +108,7 @@ class PasswordController extends AbstractController
$username = $query->get(TokenManager::USERNAME_CANONICAL);
$hash = $query->getAlnum(TokenManager::HASH);
$token = $query->getAlnum(TokenManager::TOKEN);
$timestamp = $query->getInt(TokenManager::TIMESTAMP);
$timestamp = $query->getAlnum(TokenManager::TIMESTAMP);
$user = $this->getDoctrine()->getRepository(User::class)
->findOneByUsernameCanonical($username);

View File

@@ -12,13 +12,80 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\PostalCodeRepository;
use Chill\MainBundle\Serializer\Model\Collection;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
/**
* Class PostalCodeAPIController.
*/
class PostalCodeAPIController extends ApiController
final class PostalCodeAPIController extends ApiController
{
private CountryRepository $countryRepository;
private PaginatorFactory $paginatorFactory;
private PostalCodeRepository $postalCodeRepository;
public function __construct(
CountryRepository $countryRepository,
PostalCodeRepository $postalCodeRepository,
PaginatorFactory $paginatorFactory
) {
$this->countryRepository = $countryRepository;
$this->postalCodeRepository = $postalCodeRepository;
$this->paginatorFactory = $paginatorFactory;
}
/**
* @Route("/api/1.0/main/postal-code/search.json")
*/
public function search(Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_USER');
if (!$request->query->has('q')) {
throw new BadRequestHttpException('You must supply a "q" parameter');
}
$pattern = $request->query->get('q');
if ('' === trim($pattern)) {
throw new BadRequestHttpException('the search pattern is empty');
}
if ($request->query->has('country')) {
$country = $this->countryRepository->find($request->query->getInt('country'));
if (null === $country) {
throw new NotFoundHttpException('country not found');
}
} else {
$country = null;
}
$nb = $this->postalCodeRepository->countByPattern($pattern, $country);
$paginator = $this->paginatorFactory->create($nb);
$codes = $this->postalCodeRepository->findByPattern(
$pattern,
$country,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage()
);
return $this->json(
new Collection($codes, $paginator),
Response::HTTP_OK,
[],
[AbstractNormalizer::GROUPS => ['read']]
);
}
protected function customizeQuery(string $action, Request $request, $qb): void
{
if ($request->query->has('country')) {

View File

@@ -26,8 +26,8 @@ class LoadCivility extends Fixture implements FixtureGroupInterface
public function load(ObjectManager $manager): void
{
$civilities = [
['name' => ['fr' => 'Monsieur'], 'abbrev' => ['fr' => 'M.']],
['name' => ['fr' => 'Madame'], 'abbrev' => ['fr' => 'Mme']],
['name' => ['fr' => 'Monsieur'], 'abbrev' => ['fr' => 'M.']],
['name' => ['fr' => 'Docteur'], 'abbrev' => ['fr' => 'Dr']],
['name' => ['fr' => 'Professeur'], 'abbrev' => ['fr' => 'Pr']],
['name' => ['fr' => 'Madame la Directrice'], 'abbrev' => ['fr' => 'Mme']],

View File

@@ -22,6 +22,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
*/
class AddressReference
{
/**
* This is an internal column which is populated by database.
*
* This column will ease the search operations
*
* @ORM\Column(type="text", options={"default": ""})
*/
private string $addressCanonical = '';
/**
* @ORM\Id
* @ORM\GeneratedValue

View File

@@ -38,8 +38,9 @@ class Center implements HasCenterInterface
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Serializer\Groups({"docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255)

View File

@@ -22,7 +22,8 @@ class Civility
{
/**
* @ORM\Column(type="json")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/
private array $abbreviation = [];
@@ -35,16 +36,22 @@ class Civility
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @ORM\Column(type="json")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/
private array $name = [];
/**
* @ORM\Column(type="float", name="ordering", nullable=true, options={"default": 0.0})
*/
private float $order = 0;
public function getAbbreviation(): array
{
return $this->abbreviation;
@@ -65,6 +72,11 @@ class Civility
return $this->name;
}
public function getOrder(): ?float
{
return $this->order;
}
/**
* @return Civility
*/
@@ -88,4 +100,11 @@ class Civility
return $this;
}
public function setOrder(float $order): self
{
$this->order = $order;
return $this;
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Annotation\Groups;
/**
@@ -25,12 +26,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
class Country
{
/**
* @var string
*
* @ORM\Column(type="string", length=3)
* @groups({"read"})
* @groups({"read", "docgen:read"})
* @Context({"is-translatable": true}, groups={"docgen:read"})
*/
private $countryCode;
private string $countryCode = '';
/**
* @var int
@@ -38,15 +38,16 @@ class Country
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @groups({"read"})
* @groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* @var string
*
* @ORM\Column(type="json")
* @groups({"read"})
* @groups({"read", "docgen:read"})
* @Context({"is-translatable": true}, groups={"docgen:read"})
*/
private $name;

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
* Language.
@@ -28,15 +29,18 @@ class Language
*
* @ORM\Id
* @ORM\Column(type="string")
* @Serializer\Groups({"docgen:read"})
*/
private $id;
private ?string $id = null;
/**
* @var string array
*
* @ORM\Column(type="json")
* @Serializer\Groups({"docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/
private $name;
private array $name = [];
/**
* Get id.

View File

@@ -29,6 +29,15 @@ use Symfony\Component\Serializer\Annotation\Groups;
*/
class PostalCode
{
/**
* This is an internal column which is populated by database.
*
* This column will ease the search operations
*
* @ORM\Column(type="text", options={"default": ""})
*/
private string $canonical = '';
/**
* @var Point
*

View File

@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
@@ -28,24 +29,21 @@ use Symfony\Component\Serializer\Annotation\Groups;
class Scope
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id = null;
/**
* translatable names.
*
* @var array
*
* @ORM\Column(type="json")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
* @Context({"is-translatable": true}, groups={"docgen:read"})
*/
private $name = [];
private array $name = [];
/**
* @var Collection
@@ -65,31 +63,27 @@ class Scope
$this->roleScopes = new ArrayCollection();
}
public function addRoleScope(RoleScope $roleScope)
public function addRoleScope(RoleScope $roleScope): self
{
$this->roleScopes->add($roleScope);
return $this;
}
/**
* @return int
*/
public function getId()
public function getId(): ?int
{
return $this->id;
}
/**
* @return array
*/
public function getName()
public function getName(): array
{
return $this->name;
}
/**
* @return Collection
*/
public function getRoleScopes()
public function getRoleScopes(): Collection
{
return $this->roleScopes;
}
@@ -99,7 +93,7 @@ class Scope
*
* @return $this
*/
public function setName($name)
public function setName(array $name): self
{
$this->name = $name;

View File

@@ -33,22 +33,18 @@ use function in_array;
class User implements AdvancedUserInterface
{
/**
* @var int
*
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
protected ?int $id = null;
/**
* Array where SAML attributes's data are stored.
*
* @var array
*
* @ORM\Column(type="json", nullable=true)
*/
private $attributes;
private array $attributes;
/**
* @ORM\ManyToOne(targetEntity=Location::class)
@@ -59,55 +55,44 @@ class User implements AdvancedUserInterface
* @var string
*
* @ORM\Column(type="string", length=150, nullable=true)
* @Serializer\Groups({"docgen:read"})
*/
private ?string $email = null;
/**
* @var string
*
* @ORM\Column(
* type="string",
* length=150,
* nullable=true,
* unique=true)
*/
private $emailCanonical;
private ?string $emailCanonical = null;
/**
* @var bool
*
* @ORM\Column(type="boolean")
*/
private $enabled = true;
private bool $enabled = true;
/**
* @var Collection
*
* @ORM\ManyToMany(
* targetEntity="Chill\MainBundle\Entity\GroupCenter",
* inversedBy="users")
* @ORM\Cache(usage="NONSTRICT_READ_WRITE")
*/
private $groupCenters;
private Collection $groupCenters;
/**
* @ORM\Column(type="string", length=200)
* @Serializer\Groups({"docgen:read"})
*/
private string $label = '';
/**
* @var bool
*
* @ORM\Column(type="boolean")
* sf4 check: in yml was false by default !?
*/
private $locked = true;
private bool $locked = true;
/**
* @ORM\ManyToOne(targetEntity=Center::class)
* @Serializer\Groups({"docgen:read"})
*/
private ?Center $mainCenter = null;
@@ -117,20 +102,16 @@ class User implements AdvancedUserInterface
private ?Scope $mainScope = null;
/**
* @var string
*
* @ORM\Column(type="string", length=255)
*/
private $password;
private string $password = '';
/**
* @var string
*
* @internal must be set to null if we use bcrypt
*
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $salt;
private ?string $salt = null;
/**
* @ORM\ManyToOne(targetEntity=UserJob::class)
@@ -138,22 +119,18 @@ class User implements AdvancedUserInterface
private ?UserJob $userJob = null;
/**
* @var string
*
* @ORM\Column(type="string", length=80)
*/
private $username;
private string $username = '';
/**
* @var string
*
* @ORM\Column(
* type="string",
* length=80,
* unique=true,
* nullable=true)
*/
private $usernameCanonical;
private ?string $usernameCanonical = null;
/**
* User constructor.
@@ -209,7 +186,7 @@ class User implements AdvancedUserInterface
/**
* @return string
*/
public function getEmail()
public function getEmail(): ?string
{
return $this->email;
}
@@ -406,10 +383,7 @@ class User implements AdvancedUserInterface
return $this;
}
/**
* @param bool $enabled
*/
public function setEnabled($enabled)
public function setEnabled(bool $enabled)
{
$this->enabled = $enabled;

View File

@@ -32,14 +32,14 @@ class UserJob
* @ORM\Id
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
protected ?int $id = null;
/**
* @var array|string[]A
* @ORM\Column(name="label", type="json")
* @Serializer\Groups({"read"})
* @Serializer\Groups({"read", "docgen:read"})
*/
protected array $label = [];

View File

@@ -545,7 +545,7 @@ class ExportManager
if (null === $centers) {
$centers = $this->authorizationHelper->getReachableCenters(
$this->user,
$role
$role->getRole(),
);
}
@@ -585,7 +585,7 @@ class ExportManager
'center' => $center,
'circles' => $this->authorizationHelper->getReachableScopes(
$this->user,
$element->requiredRole(),
$element->requiredRole()->getRole(),
$center
),
];

View File

@@ -80,7 +80,7 @@ class ScopePickerType extends AbstractType
{
$items = $this->authorizationHelper->getReachableScopes(
$this->security->getUser(),
$options['role'],
$options['role']->getRole(),
$options['center']
);

View File

@@ -81,7 +81,7 @@ class UserPickerType extends AbstractType
->setAllowedTypes('scope', [Scope::class, 'array', 'null'])
->setNormalizer('choices', function (Options $options) {
$users = $this->userACLAwareRepository
->findUsersByReachedACL($options['role'], $options['center'], $options['scope'], true);
->findUsersByReachedACL($options['role']->getRole(), $options['center'], $options['scope'], true);
if (null !== $options['having_permissions_group_flag']) {
return $this->userRepository

View File

@@ -109,6 +109,10 @@ class Paginator implements PaginatorInterface
return 1;
}
if (0 === $this->totalItems) {
return 1;
}
$nb = floor($this->totalItems / $this->itemPerPage);
if ($this->totalItems % $this->itemPerPage > 0) {
@@ -211,6 +215,10 @@ class Paginator implements PaginatorInterface
public function hasPage($number)
{
if (0 === $this->totalItems) {
return 1 === $number;
}
return 0 < $number
&& $this->countPages() >= $number;
}

View File

@@ -204,7 +204,7 @@ class PhonenumberHelper
return null;
}
$format = json_decode($response->getBody())->national_format;
$format = json_decode($response->getBody()->getContents())->national_format;
$item
->set($format)
@@ -260,7 +260,7 @@ class PhonenumberHelper
return null;
}
$validation = json_decode($response->getBody())->carrier->type;
$validation = json_decode($response->getBody()->getContents())->carrier->type;
$item
->set($validation)

View File

@@ -14,7 +14,7 @@ namespace Chill\MainBundle\Redis;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class RedisConnectionFactory implements EventSubscriberInterface
final class RedisConnectionFactory implements EventSubscriberInterface
{
private string $host;
@@ -29,12 +29,11 @@ class RedisConnectionFactory implements EventSubscriberInterface
$this->host = $parameters['host'];
$this->port = (int) $parameters['port'];
$this->timeout = (int) $parameters['timeout'];
$this->redis = new ChillRedis();
}
public function create()
{
$this->redis = new ChillRedis();
$result = $this->redis->connect($this->host, $this->port, $this->timeout);
if (false === $result) {
@@ -55,8 +54,6 @@ class RedisConnectionFactory implements EventSubscriberInterface
public function onKernelFinishRequest()
{
if (null !== $this->redis) {
$this->redis->close();
}
$this->redis->close();
}
}

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