Compare commits

...

74 Commits

Author SHA1 Message Date
2a280b814f Refactor view templates: relocate 'merge' action block and standardize 'duplicate link' block handling 2025-09-09 17:36:46 +02:00
230c758255 Update bundles to v4.3.0 2025-09-08 16:05:09 +02:00
eafda987ae Merge branch '412-absence-enddate' into 'master'
Resolve "Absence user: add end date"

Closes #412

See merge request Chill-Projet/chill-bundles!865
2025-09-08 13:47:14 +00:00
7db8a371fc Resolve "Absence user: add end date" 2025-09-08 13:47:14 +00:00
0d0649dd31 Change route URL to avoid clash with person duplicate controller method 2025-09-08 14:51:54 +02:00
ac12b8cdcf Merge branch 'add-permission-list-command' into 'master'
Add `RoleDumper` and `DumpListPermissionsCommand` to generate a markdown list of permissions

See merge request Chill-Projet/chill-bundles!874
2025-09-05 16:55:45 +00:00
9c1611d052 Add RoleDumper and DumpListPermissionsCommand to generate a markdown list of permissions 2025-09-05 16:55:45 +00:00
90e3043c3d Junie guidelines: fix grammar and typos in development guidelines 2025-09-04 17:26:55 +02:00
af13bf9088 Update chill bundles to v4.2.1 2025-09-03 21:12:21 +02:00
4aa65d69c7 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2025-09-03 21:11:06 +02:00
9e33aec594 Handle different export types in ExportConfigNormalizer and allow null/array checks for dataFormatter in ExportController 2025-09-03 21:10:58 +02:00
f88bc7e9f0 Merge branch 'improve-local-storage' into 'master'
Improve error handling when saving objects to local disk

See merge request Chill-Projet/chill-bundles!872
2025-09-02 19:59:26 +00:00
8e78c41549 Improve error handling when saving objects to local disk by using dumpFile with detailed exception logging. 2025-09-02 21:53:40 +02:00
6e36771349 fix changelog 2025-09-02 17:52:20 +02:00
7a82cae155 Release v4.2.0 2025-09-02 17:13:28 +02:00
dfab223391 Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles 2025-09-02 16:14:13 +02:00
539752485c Allow null values for alias and dataFormatter in buildExportDataForNormalization method 2025-09-02 16:13:48 +02:00
d204df0316 Merge branch '422-password-recover-layout' into 'master'
Resolve "Fix layout of password recover pages"

Closes #422

See merge request Chill-Projet/chill-bundles!869
2025-09-02 08:29:27 +00:00
juminet
82c02f442b Resolve "Fix layout of password recover pages" 2025-09-02 08:29:26 +00:00
f32a9dc7bc Merge branch '64-identifiant-personne' into 'master'
Add external identifiers for person, editable in edit form, with minimal features associated

See merge request Chill-Projet/chill-bundles!871
2025-09-01 08:05:11 +00:00
ea06a96f91 Add external identifiers for person, editable in edit form, with minimal features associated 2025-09-01 08:05:11 +00:00
76433e2512 Fix incorrect parameter name in event details link 2025-08-28 13:49:45 +02:00
1fa464b87a Fix typo in 'uncheckAll' script for centers selection 2025-08-28 13:32:43 +02:00
3b75f43e80 Update chill bundles to v4.1.0 2025-08-26 15:43:21 +02:00
a40eb95c43 Add changie for new event bundle features 2025-08-26 15:41:58 +02:00
8429c6e693 Merge branch 'improvements_event_module' into 'master'
Improvements event module

See merge request Chill-Projet/chill-bundles!825
2025-08-26 13:35:36 +00:00
6db7f6827c Update eslint baseline 2025-08-26 15:24:44 +02:00
3c60c57985 Adapt export list events to new export features 2025-08-26 15:18:08 +02:00
10aa36aae0 Set required to false for entitychoice filter field 2025-08-26 15:18:08 +02:00
eed9913a49 Allow select2 option for entityChoice filterOrderHelper 2025-08-26 15:18:08 +02:00
1a847d36a0 Fixes in template parameters + remove budget elements when removing event 2025-08-26 15:18:08 +02:00
d916962d9b Phpstan fix import Serializer instead of SerializerInterface 2025-08-26 15:18:08 +02:00
1092fc64ae Fix voter for the create event permission 2025-08-26 15:18:08 +02:00
4e99b6ecbd Allow filtering of event list by center and responsable 2025-08-26 15:18:08 +02:00
60d107b541 Create internal and external animators 2025-08-26 15:18:08 +02:00
4c3befe489 WIP change animator field to animator intern and animator extern 2025-08-26 15:18:08 +02:00
e176319775 WIP change animator field 2025-08-26 15:18:08 +02:00
5d810b4230 Add center to the show page of an event 2025-08-26 15:18:08 +02:00
52b8eea069 Fix passing of id parameter to route 2025-08-26 15:18:08 +02:00
4bebeaeaaa Fix wrong import of serializer 2025-08-26 15:18:08 +02:00
3969e12633 Fix cs and phpstan issues 2025-08-26 15:18:08 +02:00
d60312d4a2 Move styling to scss file and fix styling of participation list 2025-08-26 15:18:08 +02:00
d2454ae134 use key for column names in export 2025-08-26 15:18:08 +02:00
17c2cb1fdc Add missing translations 2025-08-26 15:18:08 +02:00
94d7a2a0bb Reverse deleting of organizationCost property on event entity to keep db data 2025-08-26 15:18:08 +02:00
aef1efc6cd Add missing translations and add eventThemeType missing config 2025-08-26 15:18:08 +02:00
dd0c662c9e Add missing description to migration 2025-08-26 15:18:08 +02:00
6b1696b62e phpstan, rector, phpcs fixes 2025-08-26 15:18:08 +02:00
c4b760c452 eslint fixes and new baseline 2025-08-26 15:18:08 +02:00
69fe2a8256 Add translations 2025-08-26 15:18:08 +02:00
8c98242896 Split budget elements in charges and resources column 2025-08-26 15:18:08 +02:00
7eecfd3882 Add new columns to export list event 2025-08-26 15:18:08 +02:00
6713658569 Add animators property to event 2025-08-26 15:18:08 +02:00
342b786106 Create export list of events 2025-08-26 15:18:08 +02:00
80a7437769 Update twig templates for display budget elements 2025-08-26 15:18:08 +02:00
8a38ce1a5c Add event budget element entity, forms and event property 2025-08-26 15:18:08 +02:00
5d94bf0556 Create an event budget kind admin entity 2025-08-26 15:18:08 +02:00
bb71e084b8 Create address on the fly field in event form 2025-08-26 15:18:08 +02:00
27f0bf28e9 Adjust templates and translations 2025-08-26 15:18:08 +02:00
383f588795 Add field in event for themes 2025-08-26 15:18:08 +02:00
e7a1ff1ac8 Add event theme property to event entity 2025-08-26 15:18:08 +02:00
adc9c47d0a Add event theme color for badge 2025-08-26 15:18:08 +02:00
e594b65d1e Create event theme admin entity 2025-08-26 15:18:08 +02:00
c0826bc65c Merge branch '400-add-filter-mes-actions' into 'master'
Add a filter to list for acpw where current user intervenes

Closes #400

See merge request Chill-Projet/chill-bundles!859
2025-08-18 16:26:20 +00:00
904f4e5ed9 Add a filter to list for acpw where current user intervenes 2025-08-18 16:26:20 +00:00
481f82b4c7 Merge branch '355-fusion-thirdparty' into 'master'
Resolve "Fusion des tiers"

Closes #355

See merge request Chill-Projet/chill-bundles!795
2025-08-18 15:34:48 +00:00
f5668592ca Resolve "Fusion des tiers" 2025-08-18 15:34:48 +00:00
aa085a1562 **fix:** add min and step attributes to integer field in DateIntervalType 2025-08-06 17:35:45 +02:00
2754251fdc Merge branch 'master' of https://gitlab.com/Chill-Projet/chill-bundles 2025-08-06 14:20:29 +02:00
2f6cef4238 - **fix:** move closing motive up to be coherent with display elsewhere 2025-08-06 14:20:09 +02:00
2309636eae - **fix:** adjust display logic for accompanying period dates, include closing date if period is closed. 2025-08-06 13:47:29 +02:00
56ec8fb516 Remove 'to_validate' as default for task filter 2025-08-06 09:05:39 +02:00
fe6e6e54c1 Show filters on list pages unfolded by default 2025-07-22 15:50:49 +02:00
2a09594b4a UI improvement: limit display of particapations in event list page 2025-07-22 13:26:44 +02:00
172 changed files with 5511 additions and 1113 deletions

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: Fix display of 'duplicate' and 'merge' buttons in CRUD templates
time: 2025-09-09T17:36:39.960419639+02:00
custom:
Issue: ""
SchemaChange: No schema change

12
.changes/v4.1.0.md Normal file
View File

@@ -0,0 +1,12 @@
## v4.1.0 - 2025-08-26
### Feature
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports
**Schema Change**: Add columns or tables
### Fixed
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType
### UX
* Limit display of participations in event list

10
.changes/v4.2.0.md Normal file
View File

@@ -0,0 +1,10 @@
## v4.2.0 - 2025-09-02
### Feature
* ([#64](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/64)) Add external identifier for a Person
**Schema Change**: Add columns or tables
* ([#330](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/330) Allow users to choose for which notifications they want to receive an email
### Fixed
* ([#422](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/422)) Fixed html layout of pages for recovering password
* Fix typo in 'uncheckAll' script for centers selection
* Fix incorrect parameter name in event details link

6
.changes/v4.2.1.md Normal file
View File

@@ -0,0 +1,6 @@
## v4.2.1 - 2025-09-03
### Fixed
* Fix exports to work with DirectExportInterface
### DX
* Improve error message when a stored object cannot be written on local disk

10
.changes/v4.3.0.md Normal file
View File

@@ -0,0 +1,10 @@
## v4.3.0 - 2025-09-08
### Feature
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
* Add a command to generate a list of permissions
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
**Schema Change**: Add columns or tables
### Fixed
* fix date formatting in calendar range display
* Change route URL to avoid clash with person duplicate controller method

View File

@@ -7,14 +7,6 @@
"message": "'app' is assigned a value but never used.",
"hash": "f8c2979921289906e3baabae31ba101ead91504f"
},
{
"path": "src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js",
"line": 57,
"column": 23,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'event' is defined but never used.",
"hash": "cf0cf378f71403f62a6425f384ccbbdec433d1f2"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/module/Invite/answer.js",
"line": 7,
@@ -127,46 +119,6 @@
"message": "'payload' is defined but never used.",
"hash": "66c545917093ba30f1d6ca10ddaa676140e749bd"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue",
"line": 224,
"column": 10,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reactive' is defined but never used.",
"hash": "96ed76a9828138fb125fc36c4b55e900bbfe87c2"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue",
"line": 230,
"column": 5,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'DropArg' is defined but never used.",
"hash": "bd405399a4091d65e8391404bfb0c4611816c8e0"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue",
"line": 251,
"column": 9,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'t' is assigned a value but never used.",
"hash": "bc09207a496405f7a71c178e522b89aeb1f7ebd3"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue",
"line": 356,
"column": 32,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'arg' is defined but never used.",
"hash": "aeae152f0669b946a1ad681dd52b0ef03393ae79"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue",
"line": 434,
"column": 11,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'changedEvent' is assigned a value but never used.",
"hash": "a7a81a6bf09d00c0364e3aa8207ffad853f0547b"
},
{
"path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue",
"line": 77,
@@ -399,14 +351,6 @@
"message": "'error' is defined but never used.",
"hash": "e26e5e101e90d2b7ee84d6f5de8c819e52129c17"
},
{
"path": "src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts",
"line": 29,
"column": 14,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'vm' is defined but never used.",
"hash": "8e7f5e89dd72c54459cf82156389b88988f97d63"
},
{
"path": "src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/uploader.js",
"line": 39,
@@ -615,14 +559,6 @@
"message": "'ref' is defined but never used.",
"hash": "2a27cd6d06a26e1326654c929068e3704137e24b"
},
{
"path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue",
"line": 57,
"column": 17,
"ruleId": "vue/valid-v-for",
"message": "Custom elements in iteration require 'v-bind:key' directives.",
"hash": "cce787939524e83dd135869e13738ef332d7156c"
},
{
"path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue",
"line": 15,
@@ -983,22 +919,6 @@
"message": "'_e' is defined but never used.",
"hash": "1d6448401778e8c56554020fe5abd47851ed33f3"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js",
"line": 21,
"column": 55,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'e' is defined but never used.",
"hash": "eae499e4f6e9f43a9d17f9cd917cb6d3d97be25c"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js",
"line": 3,
"column": 55,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'e' is defined but never used.",
"hash": "088fd383e7807e484aefc9825209bc7c8942bd22"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/page/homepage_widget/index.js",
"line": 9,
@@ -1089,115 +1009,19 @@
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 247,
"column": 5,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'postAddressToPerson' is defined but never used.",
"hash": "8a41c437cf2b5554cbbe1704cd51f3102b3d5994"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 248,
"column": 5,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'postAddressToHousehold' is defined but never used.",
"hash": "66dec84b2ece299daf21308e5e60d497ba442b27"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 490,
"line": 516,
"column": 21,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"context\" prop.",
"hash": "0d3f40c47974a4371072b3b9ee04b197c830162d"
"hash": "984c4203f2ac1e1bb65f9ce76ecd03b763cfaa83"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 491,
"line": 517,
"column": 21,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"context\" prop.",
"hash": "8e877b7e588c30e182f7b572bdb9685360f9cf99"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 508,
"column": 47,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "5a3e3401bc3c765d91faaf4cfde57697af1262b7"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 525,
"column": 47,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "35a741d90379574b9323279f5802193d0c98a9dc"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 553,
"column": 47,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "c23d1ddf6c0d10ae97948e74aee9c14b9320b86c"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 572,
"column": 47,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "4322e81c6ea9d9734c680633a724d5bd4fabacb2"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 803,
"column": 47,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "7928a6461b9d394c7d97f048933553936f7d8963"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue",
"line": 852,
"column": 47,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "e5afdb8efccb5470a08dde48f755b1268fa947b5"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 93,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "68f5e1cf5c03f9ada59c9e0afca0b74c7f3fca4b"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 101,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "50d730f6109092baff2db66adc44dc1315e2bda2"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 109,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "573e4c041ce663f28b933d7a675c2a525aba644c"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 117,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "293f845eeab515b1df4649d136c2d8219ed59c4d"
"hash": "c9fb019bc21bfa77d989ed596913b99dd653c594"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
@@ -1224,204 +1048,180 @@
"hash": "2d5a5e680ff207ad97c7e7b7d999064b561dfd8a"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 106,
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 149,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "d52356f2af31d0167c02330ec22d09fbfa6b2b9f"
"hash": "e4c1ecd7ae77d46ac3625c5bbe92a24d6a964db9"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 114,
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 157,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "c8e8e06f370f93bf05867e93b5f037dfa46937b1"
"hash": "4dece2db87c6ce1c04ae06c088ddfe916c1c0c61"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 165,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "facc7a0f17bdf19396fae3d0de3da82e60503c0d"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue",
"line": 173,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "19de32c76518387218264d7c4dab914d143a9cca"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 128,
"column": 13,
"line": 130,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "9abaf71ca4b4f292b3b01e724d0a7733365e71f1"
"hash": "239ac02a02694d5b20ab30d4c7ce5838c51d1515"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 129,
"column": 13,
"line": 138,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "0b0743959778a9e3d93089b132608816ee4e6646"
"hash": "a54f9bc6d1edfa4df93c7dd7d409cfef3fccf99e"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 132,
"line": 152,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "9759da7b7859b8ee8efaf74876430658ac6b6fe2"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 133,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "dba8be9a27ab74ec743b7d9e07c05d857b407dd3"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 134,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "9b1f5bce779aafc46b19d7a5d266eaa29f8f9be9"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 139,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "fe6fc4aea0994ba9da15b7c09d308842b67958cb"
"hash": "74a5f664d18f3916ea908897fcd0291cb0128f29"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 153,
"column": 55,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "bd0e024fcad2e3f4566f15293e3c25c840f6dd3e"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 154,
"column": 37,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "596c4b180b926b7829f987384328bf5636cd367a"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 171,
"column": 59,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "5b41d5f9b45da074fb7bbbbd45e0da501da72071"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 172,
"column": 41,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "d92b92a25043244cca809bd129633b7e024e26b4"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 190,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "dd9a85ea740742d620e864796f67c5bff834486d"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 191,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "e3e59960d0d50709a57b336f66b586710b774892"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 192,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "fe11b0e54396511e7b3b08615a78d22fc27e2fad"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 222,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "63c14c2150c33ec701bc4a0ff94efde69537d490"
"hash": "740ea5d793c7a34c9f352d8b333f3aa04cc80ee8"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 156,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "af8aca18f0226a5988ed90d44d95e2d607bfb5e6"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 157,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "7bc2453017793ae20cd6c10005f941d384b59d84"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 158,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "571b4ee5f22358dd165ec59696bb3439b7c9ff6c"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 163,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "cfcb5946c86e289fc61623a794284a5a272d02e8"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 178,
"column": 37,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "0ec402e43cb08bf129e0737c0d2c4f6d0c7af8bd"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 196,
"column": 41,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "ec178d33e067aac892e015002afb6f3a2ff98762"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 214,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "c0f4e5454e672b6064eb9cf6c235c6810f7bfa80"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 215,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "e3dd840d2474f9865a45822872bf9ecfb15961d7"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 216,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "a32a60382b145cc7a4a7ebe01ec435b8e3103320"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue",
"line": 246,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "082447e5c731012f3acc282943502775dfd24797"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 96,
"line": 118,
"column": 20,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "d2a9fdaeef0e2810f480022d4c6f99e4f76a818e"
"hash": "d4fba4fe09af3c0937c0dd164928c8930c1591b5"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 96,
"line": 118,
"column": 20,
"ruleId": "vue/no-side-effects-in-computed-properties",
"message": "Unexpected side effect in \"cities\" computed property.",
"hash": "dd92a60a9b1ebefeb9a90941d45326fbfa483733"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 102,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "04be01ab638ce01f568fb0216929e65e1175ca23"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 110,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "8619c8e0b63e87d09268832f90e4fba06b87e41f"
"hash": "1113a114d5aaf9f32f442916d25458541c5af35c"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 124,
"column": 13,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "281f918da00635079501418b1e6b2c05b62eb4a7"
"hash": "fa56a7c93583f0a9d0c2ecac10228c4f4fc1bc3a"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 125,
"column": 13,
"line": 132,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "c131b09fa67ab1d069f1d04a54582d6b0f206153"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 126,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "3d3a2a4add64c291b8f5f1cddd90a173cd6a819d"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 131,
"column": 21,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "ed48f4988914d7897018a2e06830a97e6740b3e8"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 145,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "744f3a7610d4d6015e50e25149bceffd6c6e2763"
"hash": "9fe87937ea67d1dae95fb3d44d4be0da2eba0905"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
@@ -1441,115 +1241,139 @@
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 149,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "1e7b1ad55866f708baaca72dfa4ff26d6f8e5d21"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 152,
"line": 148,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "84779331536ffceec8d4a8c5ca4307310b882549"
"hash": "ab4f478fbfbc954b8dff75176dcd432f9ff28cfc"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 161,
"line": 153,
"column": 21,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "1d907d149f9ddb62e32140a90efe9a74b3e71fef"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 167,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "0789999841be671a4d8ab080d6fdb679f843eb52"
"hash": "8aa37d2d4f011773e68838a2c88017875de563b5"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 170,
"column": 51,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "bbb17afa114f016e2058d90aa32d2a625804f0d1"
"line": 168,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "a4827a357e52a51fa9262319114d81a130296acf"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 169,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "a4c9715664202949e3242b8d4aa4098288b46dc4"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 171,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "f3e9e21e433e90ec7b615b8940d43c4177372b66"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 174,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "770b7a24cc24b380e88db47d62422c8e1ece2571"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 183,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "2aef3c519a9ec6abcfe7573989d3de19d5c4c752"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 193,
"column": 33,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "5fbe407ceceb37bff2ac800ceddd7942540132f1"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 190,
"column": 55,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'reject' is defined but never used.",
"hash": "e2af91def877befbabef8e93deba4c58a3ee2ded"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 191,
"column": 37,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "ee8544ee45681a650ed7d4918ae979685cdd8f0f"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 210,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "5d9d2217c8c7e6571bc9f72a98ea5b370edb4968"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 211,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "6e04619b373c23c91f6c36c2aad314ac16cdb697"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 212,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "39df045639a62f64ccdb03a80e286bc3ad772587"
"hash": "5d1f97e4d7d9f47399d312e8b9f95ef9e3843b8c"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 213,
"column": 17,
"column": 37,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "c399a43fa797a8ce61c9d96a644a39cc84a387b7"
"hash": "c1df874f790ef0c036bf58ae8a8db1ee173685d4"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 245,
"line": 232,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "476e6588a28ac9382e8b9d2e63a8babecd23bad8"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 233,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "6a0c82ba72d6d87217bf33a6ad8e40a4b81bc802"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 234,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "741d5af6c7d90041c0dc1c1df2e8699b80fca69a"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 235,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "c3ffd141f58d532663875cc5c7d338ed00db2a6d"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue",
"line": 267,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "04337a07944caaa4819cfebcf29e1a7cbfdf248b"
"hash": "2700f258396516a2fe971618fafbcdf72cdda3ab"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue",
"line": 76,
"line": 94,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "373a2e31f110d138c66d77f1faf5dc61545c55af"
"hash": "4be1b0592efa775092a91a1d744e16ce98bd216e"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue",
"line": 81,
"line": 99,
"column": 13,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "421eb6a63224b4b1d81b216677a710c5c99ddee3"
"hash": "19b54b6d76c30249d520a296f826eda9d6eb0668"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue",
@@ -1569,19 +1393,19 @@
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue",
"line": 155,
"line": 169,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "b3a822914fcb5e2fcf28efc331a45b9205002eeb"
"hash": "dcb7b34098062760ddbb849655a5bb3ca65c36d3"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue",
"line": 164,
"line": 178,
"column": 17,
"ruleId": "vue/no-mutating-props",
"message": "Unexpected mutation of \"entity\" prop.",
"hash": "72c7d850f6cdeaf65b373a33234222f9766ee30b"
"hash": "86b3ecf201025cac36878c5e4bf8850fb9d58cb5"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/index.js",
@@ -1631,14 +1455,6 @@
"message": "'app' is assigned a value but never used.",
"hash": "9e6125f4fc387dc362c69cc6e3ce360eb2851f1b"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue",
"line": 60,
"column": 22,
"ruleId": "vue/require-valid-default-prop",
"message": "Type of the default value for 'suggested' prop must be a function.",
"hash": "d30212820bc2e97fa02d75dbc3a014558693f169"
},
{
"path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddressDetails/Parts/AddressDetailsMap.vue",
"line": 24,
@@ -1727,14 +1543,6 @@
"message": "'tags' is assigned a value but never used.",
"hash": "ae9bb2e0651c118ed9efd227e88b86cc83f5d80d"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue",
"line": 116,
"column": 18,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'event' is defined but never used.",
"hash": "201f182769c6dfb87148b841e7d9b592be429669"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js",
"line": 19,
@@ -1767,14 +1575,6 @@
"message": "'app' is assigned a value but never used.",
"hash": "aaaaa63e7a60443b8cbf8191feb9142852ebdf1c"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue",
"line": 79,
"column": 13,
"ruleId": "vue/require-v-for-key",
"message": "Elements in iteration expect to have 'v-bind:key' directives.",
"hash": "422f53925922e59655d0f71624c19af75d41628c"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/index.js",
"line": 12,
@@ -1815,22 +1615,6 @@
"message": "'evalFQDN' is assigned a value but never used.",
"hash": "7fc32caafa23addddf44f3acbc5045b4523a0271"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js",
"line": 611,
"column": 9,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'errors' is assigned a value but never used.",
"hash": "c41cf979fc1626c38328dbf1028800c3395496bd"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue",
"line": 282,
"column": 7,
"ruleId": "@typescript-eslint/no-unused-expressions",
"message": "Expected an assignment or function call and instead saw an expression.",
"hash": "de3a6e2bb10a80a2bacba665be74266c7efc7d64"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/index.js",
"line": 16,
@@ -1847,38 +1631,6 @@
"message": "'app' is assigned a value but never used.",
"hash": "2f161e663689e3e4dfe2c53b0d64c91a4d2b1a60"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue",
"line": 263,
"column": 19,
"ruleId": "vue/return-in-computed-property",
"message": "Expected to return a value in \"refreshNetwork\" computed property.",
"hash": "2c1b08a49098c83b09058cedc0a962126e91e544"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue",
"line": 270,
"column": 7,
"ruleId": "vue/no-side-effects-in-computed-properties",
"message": "Unexpected side effect in \"legendLayers\" computed property.",
"hash": "760948d2187c853f17ac9a1bd7107e883092d4f4"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue",
"line": 281,
"column": 5,
"ruleId": "vue/no-dupe-keys",
"message": "Duplicate key 'checkedLayers'. May cause name collision in script or template tag.",
"hash": "447edb461e15e3ff5c60c8ecba88131e442539aa"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue",
"line": 353,
"column": 7,
"ruleId": "@typescript-eslint/no-unused-expressions",
"message": "Expected an assignment or function call and instead saw an expression.",
"hash": "9cf656cbf1eb3d7cc0082e63adcd320b6093d14f"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js",
"line": 20,
@@ -1887,22 +1639,6 @@
"message": "'app' is assigned a value but never used.",
"hash": "9e94e6412b8a44e47bfe8e66218cad09cff5bed4"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue",
"line": 42,
"column": 16,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'response' is defined but never used.",
"hash": "62de07b13c662e32332bb062038acee23978ea70"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue",
"line": 356,
"column": 28,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'_response' is defined but never used.",
"hash": "097e7788a2b5dea500b80b8a3cf968e57063a66a"
},
{
"path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue",
"line": 6,
@@ -1918,45 +1654,5 @@
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'UserRenderBoxBadge' is defined but never used.",
"hash": "99eba0d8633b2c9497417f4f61ec4194dbb2a96b"
},
{
"path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts",
"line": 4,
"column": 3,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'StoredObjectStatus' is defined but never used.",
"hash": "63f8c4572293916850d6165647774b27d4b732c6"
},
{
"path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts",
"line": 5,
"column": 3,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'StoredObjectStatusChange' is defined but never used.",
"hash": "a87c178e3eb5999bf0f46b3fa1c6da77e1be08b9"
},
{
"path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts",
"line": 30,
"column": 61,
"ruleId": "@typescript-eslint/no-unused-vars",
"message": "'e' is defined but never used.",
"hash": "02953121583f4f73742a19adab099ab63df9076e"
},
{
"path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts",
"line": 31,
"column": 32,
"ruleId": "@typescript-eslint/no-explicit-any",
"message": "Unexpected any. Specify a different type.",
"hash": "af48e21a1651b6017ede882dab249c00a818a44d"
},
{
"path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts",
"line": 37,
"column": 16,
"ruleId": "@typescript-eslint/no-explicit-any",
"message": "Unexpected any. Specify a different type.",
"hash": "7513ea552a0a649ce4ab93b6cf9d40bfef4f68d9"
}
]

3
.gitignore vendored
View File

@@ -18,6 +18,9 @@ migrations/*
templates/*
translations/*
# we allow developers to add customization on their installation, without commiting it
config/packages/dev/*
###> symfony/framework-bundle ###
/.env.local
/.env.local.php

View File

@@ -27,11 +27,11 @@ Chill is a comprehensive web application built as a set of Symfony bundles. It i
## Project Structure
Note: This is a project which exists from a long time ago, and we found multiple structure inside each bundle. When having the choice, the developers should choose the new structure.
Note: This is a project that's existed for a long time, and throughout the years we've used multiple structures inside each bundle. When having the choice, the developers should choose the new structure.
The project follows a standard Symfony bundle structure:
- `/src/Bundle/`: Contains all the Chill bundles. The code is either at the root of the bundle directory, or within a `src/` directory (preferred). See psr4 mapping at the root's `composer.json`.
- each bundle come with his own tests, either in the `Tests` directory (when the code is directly within the bundle directory (for instance `src/Bundle/ChillMainBundle/Tests`, `src/Bundle/ChillPersonBundle/Tests`)), or inside the `tests` directory, alongside to the `src/` sub-directory (example: `src/Bundle/ChillWopiBundle/tests`) (this is the preferred way).
- each bundle comes with its own tests, either in the `Tests` directory (when the code is directly within the bundle directory (for instance `src/Bundle/ChillMainBundle/Tests`, `src/Bundle/ChillPersonBundle/Tests`)), or inside the `tests` directory, alongside the `src/` sub-directory (example: `src/Bundle/ChillWopiBundle/tests`) (this is the preferred way).
- `/docs/`: Contains project documentation
Each bundle typically has the following structure:
@@ -46,13 +46,13 @@ Each bundle typically has the following structure:
### A special word about TicketBundle
The ticket bundle is developed using a kind of "Command" pattern. The controller fill a "Command", and a "CommandHandler" handle this command. They are savec in the `src/Bundle/ChillTicketBundle/src/Action` directory.
The ticket bundle is developed using a kind of "Command" pattern. The controller fills a "Command," and a "CommandHandler" handles this command. They are saved in the `src/Bundle/ChillTicketBundle/src/Action` directory.
## Development Guidelines
### Building and Configuration Instructions
All the command should be run through the `symfony` command, which will configure the required variables.
All the commands should be run through the `symfony` command, which will configure the required variables.
For assets, we must ensure that we use node at version `^20.0.0`. This is done using `nvm use 20`.
@@ -87,7 +87,7 @@ For assets, we must ensure that we use node at version `^20.0.0`. This is done u
docker compose up -d
```
5. **Set Up the Database**:
6. **Set Up the Database**:
```bash
# Create the database
symfony console doctrine:database:create
@@ -99,20 +99,20 @@ For assets, we must ensure that we use node at version `^20.0.0`. This is done u
symfony console doctrine:fixtures:load
```
6. **Build Assets**:
7. **Build Assets**:
```bash
nvm use 20
yarn run encore dev
```
7. **Start the Development Server**:
8. **Start the Development Server**:
```bash
symfony server:start -d
```
#### Docker Setup
The project includes Docker configuration for easier development:
The project includes a Docker configuration for easier development:
1. **Start Docker Services**:
```bash
@@ -153,9 +153,9 @@ Key configuration files:
Each time a doctrine entity is created, we generate migration to adapt the database.
The migration are created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace <namespace>`, where the namespace is the relevant namespace for migration. As this is a bash script, do not forget to quote the `\` (`\` must become `\\` in your command).
The migration is created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace <namespace>`, where the namespace is the relevant namespace for migration. As this is a bash script, remember to quote the `\` (`\` must become `\\` in your command).
Each bundle has his own namespace for migration (always ask me to confirm that command, with a list of updated / created entities so that I can confirm you that it is ok):
Each bundle has his own namespace for migration (always ask me to confirm that command with a list of updated / created entities so that I can confirm to you that it is ok):
- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`;
- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`;
@@ -183,7 +183,7 @@ Once created the, comment's classes should be removed and a description of the c
When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of
`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities,
where injection does not work when restoring an entity from database, but usually possible in services.
where injection does not work when restoring an entity from a database, but usually possible in services.
In test, we use `\Symfony\Component\Clock\MockClock` which is an implementation of `Symfony\Component\Clock\ClockInterface`
where we have full and easy control of the date.
@@ -198,9 +198,9 @@ The project uses PHPUnit for testing. Each bundle has its own test suite, and th
For creating mock, we prefer using prophecy (library phpspec/prophecy).
##### Useful helpers and tips that avoid create a mock
##### Useful helpers and tips that avoid creating a mock
Some notable implementations that are tests helper, and avoid to create a mock:
Some notable implementations that are test helpers and avoid creating a mock:
- `\Psr\Log\NullLogger`, an implementation of `\Psr\Log\LoggerInterface`;
- `\Symfony\Component\Clock\MockClock`, an implementation of `Symfony\Component\Clock\ClockInterface` (already mentioned above);
@@ -297,7 +297,7 @@ class TicketTest extends TestCase
#### Test Database
For tests that require a database, the project uses postgresql database filled by fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
For tests that require a database, the project uses a postgresql database filled with fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
### Code Quality Tools

View File

@@ -6,6 +6,48 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v4.3.0 - 2025-09-08
### Feature
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
* Add a command to generate a list of permissions
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
**Schema Change**: Add columns or tables
### Fixed
* fix date formatting in calendar range display
* Change route URL to avoid clash with person duplicate controller method
## v4.2.1 - 2025-09-03
### Fixed
* Fix exports to work with DirectExportInterface
### DX
* Improve error message when a stored object cannot be written on local disk
## v4.2.0 - 2025-09-02
### Feature
* ([#64](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/64)) Add external identifier for a Person
**Schema Change**: Add columns or tables
* ([#330](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/330) Allow users to choose for which notifications they want to receive an email
### Fixed
* ([#422](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/422)) Fixed html layout of pages for recovering password
* Fix typo in 'uncheckAll' script for centers selection
* Fix incorrect parameter name in event details link
## v4.1.0 - 2025-08-26
### Feature
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports
**Schema Change**: Add columns or tables
### Fixed
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType
### UX
* Limit display of participations in event list
## v4.0.2 - 2025-07-09
### Fixed
* Fix add missing translation

View File

@@ -55,5 +55,6 @@
</dl>
{% endblock %}
{% block content_view_actions_duplicate_link %}{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -70,6 +70,8 @@
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
<option value="00:45:00">45 minutes</option>
<option value="00:60:00">60 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select

View File

@@ -32,6 +32,8 @@
<option value="00:10:00">10 minutes</option>
<option value="00:15:00">15 minutes</option>
<option value="00:30:00">30 minutes</option>
<option value="00:45:00">45 minutes</option>
<option value="00:60:00">60 minutes</option>
</select>
<label class="input-group-text" for="slotMinTime">De</label>
<select
@@ -102,7 +104,8 @@
event.title
}}</b>
<b v-else-if="event.extendedProps.is === 'range'"
>{{ formatDate(event.startStr) }} -
>{{ formatDate(event.startStr, "time") }} -
{{ formatDate(event.endStr, "time") }}:
{{ event.extendedProps.locationName }}</b
>
<b v-else-if="event.extendedProps.is === 'local'">{{
@@ -294,9 +297,26 @@ const nextWeeks = computed((): Weeks[] =>
}),
);
const formatDate = (datetime: string) => {
console.log(typeof datetime);
return ISOToDate(datetime);
const formatDate = (datetime: string, format: null | "time" = null) => {
const date = ISOToDate(datetime);
if (!date) return "";
if (format === "time") {
return date.toLocaleTimeString("fr-FR", {
hour: "2-digit",
minute: "2-digit",
});
}
// French date formatting
return date.toLocaleDateString("fr-FR", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
};
const baseOptions = ref<CalendarOptions>({

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\CustomFieldsBundle\EntityRepository;
use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class CustomFieldsDefaultGroupRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, CustomFieldsDefaultGroup::class);
}
public function findOneByEntity(string $className): ?CustomFieldsDefaultGroup
{
return $this->findOneBy(['entity' => $className]);
}
}

View File

@@ -127,3 +127,7 @@ services:
factory: ["@doctrine", getRepository]
arguments:
- "Chill\\CustomFieldsBundle\\Entity\\CustomFieldLongChoice\\Option"
Chill\CustomFieldsBundle\EntityRepository\CustomFieldsDefaultGroupRepository:
autowire: true
autoconfigure: true

View File

@@ -18,6 +18,7 @@ use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Path;
@@ -147,16 +148,11 @@ class StoredObjectManager implements StoredObjectManagerInterface
public function writeContent(string $filename, string $encryptedContent): void
{
$fullPath = $this->buildPath($filename);
$dir = Path::getDirectory($fullPath);
if (!$this->filesystem->exists($dir)) {
$this->filesystem->mkdir($dir);
}
$result = file_put_contents($fullPath, $encryptedContent);
if (false === $result) {
throw StoredObjectManagerException::unableToStoreDocumentOnDisk();
try {
$this->filesystem->dumpFile($fullPath, $encryptedContent);
} catch (IOExceptionInterface $exception) {
throw StoredObjectManagerException::unableToStoreDocumentOnDisk($exception);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Controller\Admin;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
class EventBudgetKindController extends CRUDController
{
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
/* @var QueryBuilder $query */
$query->addOrderBy('e.type', 'ASC');
return parent::orderQuery($action, $query, $request, $paginator);
}
}

View File

@@ -23,11 +23,11 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Doctrine\Persistence\ManagerRegistry;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Csv;
use PhpOffice\PhpSpreadsheet\Writer\Ods;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -41,6 +41,8 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
@@ -58,7 +60,8 @@ final class EventController extends AbstractController
private readonly TranslatorInterface $translator,
private readonly PaginatorFactory $paginator,
private readonly Security $security,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
private readonly ManagerRegistry $managerRegistry,
private readonly NormalizerInterface $normalizer,
) {}
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'POST', 'DELETE'])]
@@ -75,6 +78,7 @@ final class EventController extends AbstractController
/** @var array $participations */
$participations = $event->getParticipations();
$budgetElements = $event->getBudgetElements();
$form = $this->createDeleteForm($event_id);
@@ -86,6 +90,10 @@ final class EventController extends AbstractController
$em->remove($participation);
}
foreach ($budgetElements as $e) {
$em->remove($e);
}
$em->remove($event);
$em->flush();
@@ -103,7 +111,7 @@ final class EventController extends AbstractController
}
return $this->render('@ChillEvent/Event/confirm_delete.html.twig', [
'event_id' => $event->getId(),
'id' => $event->getId(),
'delete_form' => $form->createView(),
]);
}
@@ -169,6 +177,8 @@ final class EventController extends AbstractController
/**
* Displays a form to create a new Event entity.
*
* @throws ExceptionInterface
*/
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/new', name: 'chill_event__event_new', methods: ['GET', 'POST'])]
public function newAction(?Center $center, Request $request): Response
@@ -199,26 +209,23 @@ final class EventController extends AbstractController
$this->addFlash('success', $this->translator
->trans('The event was created'));
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $entity->getId()]);
return $this->redirectToRoute('chill_event__event_show', ['id' => $entity->getId()]);
}
$entity_array = $this->normalizer->normalize($entity, 'json', ['groups' => 'read']);
return $this->render('@ChillEvent/Event/new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
'entity_json' => $entity_array,
]);
}
/**
* First step of new Event form.
*/
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/new/pick-center', name: 'chill_event__event_new_pickcenter', options: [null])]
public function newPickCenterAction(): Response
{
$role = 'CHILL_EVENT_CREATE';
/**
* @var Center $centers
*/
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(), $role);
if (1 === \count($centers)) {
@@ -238,7 +245,7 @@ final class EventController extends AbstractController
->add('center_id', EntityType::class, [
'class' => Center::class,
'choices' => $centers,
'placeholder' => '',
'placeholder' => $this->translator->trans('Pick a center'),
'label' => 'To which centre should the event be associated ?',
])
->add('submit', SubmitType::class, [
@@ -251,16 +258,7 @@ final class EventController extends AbstractController
]);
}
/**
* Finds and displays a Event entity.
*
* @ParamConverter("event", options={"id": "event_id"})
*
* @return Response
*
* @throws \PhpOffice\PhpSpreadsheet\Exception
*/
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/show', name: 'chill_event__event_show')]
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{id}/show', name: 'chill_event__event_show')]
public function showAction(Event $event, Request $request)
{
if (!$event) {
@@ -317,7 +315,7 @@ final class EventController extends AbstractController
$this->addFlash('success', $this->translator->trans('The event was updated'));
return $this->redirectToRoute('chill_event__event_show', ['event_id' => $event_id]);
return $this->redirectToRoute('chill_event__event_show', ['id' => $event_id]);
}
return $this->render('@ChillEvent/Event/edit.html.twig', [

View File

@@ -15,11 +15,15 @@ use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Entity\EventType;
use Chill\EventBundle\Repository\EventACLAwareRepositoryInterface;
use Chill\EventBundle\Repository\EventTypeRepository;
use Chill\EventBundle\Security\EventVoter;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormFactoryInterface;
@@ -29,17 +33,18 @@ use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Twig\Environment;
final readonly class EventListController
final class EventListController extends AbstractController
{
public function __construct(
private Environment $environment,
private EventACLAwareRepositoryInterface $eventACLAwareRepository,
private EventTypeRepository $eventTypeRepository,
private FilterOrderHelperFactory $filterOrderHelperFactory,
private FormFactoryInterface $formFactory,
private PaginatorFactoryInterface $paginatorFactory,
private TranslatableStringHelperInterface $translatableStringHelper,
private UrlGeneratorInterface $urlGenerator,
private readonly Environment $environment,
private readonly EventACLAwareRepositoryInterface $eventACLAwareRepository,
private readonly EventTypeRepository $eventTypeRepository,
private readonly FilterOrderHelperFactory $filterOrderHelperFactory,
private readonly FormFactoryInterface $formFactory,
private readonly PaginatorFactoryInterface $paginatorFactory,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly UrlGeneratorInterface $urlGenerator,
private readonly AuthorizationHelper $authorizationHelper,
) {}
#[Route(path: '{_locale}/event/event/list', name: 'chill_event_event_list')]
@@ -50,6 +55,8 @@ final readonly class EventListController
'q' => (string) $filter->getQueryString(),
'dates' => $filter->getDateRangeData('dates'),
'event_types' => $filter->getEntityChoiceData('event_types'),
'responsables' => $filter->getUserPickerData('responsables'),
'centers' => $filter->getEntityChoiceData('centers'),
];
$total = $this->eventACLAwareRepository->countAllViewable($filterData);
$pagination = $this->paginatorFactory->create($total);
@@ -73,6 +80,7 @@ final readonly class EventListController
private function buildFilterOrder(): FilterOrderHelper
{
$types = $this->eventTypeRepository->findAllActive();
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(), EventVoter::SEE);
$builder = $this->filterOrderHelperFactory->create(__METHOD__);
$builder
@@ -80,6 +88,16 @@ final readonly class EventListController
->addSearchBox(['name'])
->addEntityChoice('event_types', 'event.filter.event_types', EventType::class, $types, [
'choice_label' => fn (EventType $e) => $this->translatableStringHelper->localize($e->getName()),
'expanded' => false,
'required' => false,
'attr' => ['class' => 'select2'],
])
->addUserPicker('responsables', 'event.filter.pick_responsable', ['multiple' => true, 'required' => false])
->addEntityChoice('centers', 'event.filter.center', Center::class, $centers, [
'choice_label' => fn (Center $c) => $c->getName(),
'expanded' => false,
'required' => false,
'attr' => ['class' => 'select2'],
]);
return $builder->build();

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
class EventThemeController extends CRUDController
{
protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
{
if ('new' === $action) {
return parent::createFormFor($action, $entity, $formClass, ['step' => 'create']);
}
if ('edit' === $action) {
return parent::createFormFor($action, $entity, $formClass, ['step' => 'edit']);
}
throw new \LogicException('action is not supported: '.$action);
}
/**
* @param QueryBuilder|mixed $query
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator): QueryBuilder
{
/* @var QueryBuilder $query */
return $query->orderBy('e.ordering', 'ASC')
->addOrderBy('e.id', 'ASC');
}
}

View File

@@ -228,7 +228,7 @@ final class ParticipationController extends AbstractController
}
return $this->redirectToRoute('chill_event__event_show', [
'event_id' => $participation->getEvent()->getId(),
'id' => $participation->getEvent()->getId(),
]);
}
@@ -242,7 +242,7 @@ final class ParticipationController extends AbstractController
/**
* @param int $participation_id
*/
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/participation/{participation_id}/delete', name: 'chill_event_participation_delete', requirements: ['participation_id' => '\d+'], methods: ['GET', 'DELETE'])]
#[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/participation/{participation_id}/delete', name: 'chill_event_participation_delete', requirements: ['participation_id' => '\d+'])]
public function deleteAction($participation_id, Request $request): Response|\Symfony\Component\HttpFoundation\RedirectResponse
{
$em = $this->managerRegistry->getManager();
@@ -273,7 +273,7 @@ final class ParticipationController extends AbstractController
);
return $this->redirectToRoute('chill_event__event_show', [
'event_id' => $event->getId(),
'id' => $event->getId(),
]);
}
}
@@ -442,7 +442,7 @@ final class ParticipationController extends AbstractController
));
return $this->redirectToRoute('chill_event__event_show', [
'event_id' => $participation->getEvent()->getId(),
'id' => $participation->getEvent()->getId(),
]);
}

View File

@@ -11,6 +11,12 @@ declare(strict_types=1);
namespace Chill\EventBundle\DependencyInjection;
use Chill\EventBundle\Controller\Admin\EventBudgetKindController;
use Chill\EventBundle\Controller\EventThemeController;
use Chill\EventBundle\Entity\EventBudgetKind;
use Chill\EventBundle\Entity\EventTheme;
use Chill\EventBundle\Form\EventBudgetKindType;
use Chill\EventBundle\Form\EventThemeType;
use Chill\EventBundle\Security\EventVoter;
use Chill\EventBundle\Security\ParticipationVoter;
use Symfony\Component\Config\FileLocator;
@@ -26,7 +32,10 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
*/
class ChillEventExtension extends Extension implements PrependExtensionInterface
{
public function load(array $configs, ContainerBuilder $container)
/**
* @throws \Exception
*/
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
@@ -45,16 +54,17 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
/** (non-PHPdoc).
* @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
*/
public function prepend(ContainerBuilder $container)
public function prepend(ContainerBuilder $container): void
{
$this->prependAuthorization($container);
$this->prependCruds($container);
$this->prependRoute($container);
}
/**
* add authorization hierarchy.
*/
protected function prependAuthorization(ContainerBuilder $container)
protected function prependAuthorization(ContainerBuilder $container): void
{
$container->prependExtensionConfig('security', [
'role_hierarchy' => [
@@ -70,7 +80,7 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
/**
* add route to route loader for chill.
*/
protected function prependRoute(ContainerBuilder $container)
protected function prependRoute(ContainerBuilder $container): void
{
// add routes for custom bundle
$container->prependExtensionConfig('chill_main', [
@@ -81,4 +91,54 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
],
]);
}
protected function prependCruds(ContainerBuilder $container): void
{
$container->prependExtensionConfig('chill_main', [
'cruds' => [
[
'class' => EventTheme::class,
'name' => 'event_theme',
'base_path' => '/admin/event/theme',
'form_class' => EventThemeType::class,
'controller' => EventThemeController::class,
'actions' => [
'index' => [
'template' => '@ChillEvent/Admin/EventTheme/index.html.twig',
'role' => 'ROLE_ADMIN',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillEvent/Admin/EventTheme/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillEvent/Admin/EventTheme/edit.html.twig',
],
],
],
[
'class' => EventBudgetKind::class,
'name' => 'event_budget_kind',
'base_path' => '/admin/event/budget',
'form_class' => EventBudgetKindType::class,
'controller' => EventBudgetKindController::class,
'actions' => [
'index' => [
'template' => '@ChillEvent/Admin/BudgetKind/index.html.twig',
'role' => 'ROLE_ADMIN',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillEvent/Admin/BudgetKind/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillEvent/Admin/BudgetKind/edit.html.twig',
],
],
],
],
]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Entity;
enum BudgetTypeEnum: string
{
case CHARGE = 'Charge';
case RESOURCE = 'Resource';
}

View File

@@ -23,10 +23,13 @@ use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
* Class Event.
@@ -46,35 +49,63 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
#[ORM\ManyToOne(targetEntity: Scope::class)]
private ?Scope $circle = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTime $date = null;
#[ORM\Id]
#[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
#[ORM\Column(name: 'id', type: Types::INTEGER)]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: User::class)]
private ?User $moderator = null;
/**
* @var Collection<int, User>
*/
#[ORM\ManyToMany(targetEntity: User::class)]
#[Serializer\Groups(['read'])]
#[ORM\JoinTable('chill_event_animatorsintern')]
private Collection $animatorsIntern;
/**
* @var Collection<int, ThirdParty>
*/
#[ORM\ManyToMany(targetEntity: ThirdParty::class)]
#[Serializer\Groups(['read'])]
#[ORM\JoinTable('chill_event_animatorsextern')]
private Collection $animatorsExtern;
#[Assert\NotBlank]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 150)]
#[Serializer\Groups(['read'])]
#[ORM\Column(type: Types::STRING, length: 150)]
private ?string $name = null;
/**
* @var Collection<int, Participation>
*/
#[ORM\OneToMany(mappedBy: 'event', targetEntity: Participation::class)]
#[Serializer\Groups(['read'])]
private Collection $participations;
#[Assert\NotNull]
#[Serializer\Groups(['read'])]
#[ORM\ManyToOne(targetEntity: EventType::class)]
private ?EventType $type = null;
/**
* @var Collection<int, EventTheme>
*/
#[ORM\ManyToMany(targetEntity: EventTheme::class)]
#[Serializer\Groups(['read'])]
#[ORM\JoinTable('chill_event_eventtheme')]
private Collection $themes;
#[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_')]
private CommentEmbeddable $comment;
#[ORM\ManyToOne(targetEntity: Location::class)]
#[Serializer\Groups(['read'])]
#[ORM\JoinColumn(nullable: true)]
private ?Location $location = null;
@@ -85,7 +116,17 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
#[ORM\JoinTable('chill_event_event_documents')]
private Collection $documents;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DECIMAL, precision: 10, scale: 4, nullable: true, options: ['default' => '0.0'])]
/**
* @var Collection<int, EventBudgetElement>
*/
#[ORM\OneToMany(mappedBy: 'event', targetEntity: EventBudgetElement::class, cascade: ['persist'])]
#[Serializer\Groups(['read'])]
private Collection $budgetElements;
/**
* @deprecated use budgetElements instead
*/
#[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 4, nullable: true, options: ['default' => '0.0'])]
private string $organizationCost = '0.0';
/**
@@ -96,6 +137,20 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
$this->participations = new ArrayCollection();
$this->documents = new ArrayCollection();
$this->comment = new CommentEmbeddable();
$this->themes = new ArrayCollection();
$this->budgetElements = new ArrayCollection();
$this->animatorsIntern = new ArrayCollection();
$this->animatorsExtern = new ArrayCollection();
}
public function addBudgetElement(EventBudgetElement $budgetElement)
{
if (!$this->budgetElements->contains($budgetElement)) {
$this->budgetElements[] = $budgetElement;
$budgetElement->setEvent($this);
}
return $this;
}
/**
@@ -126,38 +181,79 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
return $this;
}
/**
* @return Center
*/
public function getCenter()
public function getThemes(): Collection
{
return $this->themes;
}
public function addTheme(EventTheme $theme): self
{
$this->themes->add($theme);
return $this;
}
public function removeTheme(EventTheme $theme): void
{
$this->themes->removeElement($theme);
}
public function getAnimatorsIntern(): Collection
{
return $this->animatorsIntern;
}
public function getAnimatorsExtern(): Collection
{
return $this->animatorsExtern;
}
public function addAnimatorsIntern(User $ai): self
{
$this->animatorsIntern->add($ai);
return $this;
}
public function removeAnimatorsIntern(User $ai): void
{
$this->animatorsIntern->removeElement($ai);
}
public function addAnimatorsExtern(ThirdParty $ae): self
{
$this->animatorsExtern->add($ae);
return $this;
}
public function removeAnimatorsExtern(ThirdParty $ae): void
{
$this->animatorsExtern->removeElement($ae);
}
public function getCenter(): Center
{
return $this->center;
}
/**
* @return Scope
*/
public function getCircle()
public function getCircle(): ?Scope
{
return $this->circle;
}
/**
* Get date.
*
* @return \DateTime
*/
public function getDate()
public function getDate(): ?\DateTime
{
return $this->date;
}
/**
* Get id.
*
* @return int
*/
public function getId()
public function getId(): ?int
{
return $this->id;
}
@@ -169,14 +265,20 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
/**
* Get label.
*
* @return string
*/
public function getName()
public function getName(): ?string
{
return $this->name;
}
/**
* @return Collection<int, EventBudgetElement>
*/
public function getBudgetElements(): Collection
{
return $this->budgetElements;
}
/**
* @return Collection<int, Participation>
*/
@@ -199,26 +301,26 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
/**
* @deprecated
*
* @return Scope
*/
public function getScope()
public function getScope(): Scope
{
return $this->getCircle();
}
/**
* @return EventType
*/
public function getType()
public function getType(): ?EventType
{
return $this->type;
}
public function removeBudgetElement(EventBudgetElement $budgetElement): void
{
$this->budgetElements->removeElement($budgetElement);
}
/**
* Remove participation.
*/
public function removeParticipation(Participation $participation)
public function removeParticipation(Participation $participation): void
{
$this->participations->removeElement($participation);
}
@@ -314,11 +416,17 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
$this->documents = $documents;
}
/**
* @deprecated
*/
public function getOrganizationCost(): string
{
return $this->organizationCost;
}
/**
* @deprecated
*/
public function setOrganizationCost(string $organizationCost): void
{
$this->organizationCost = $organizationCost;

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Entity;
use Chill\EventBundle\Repository\EventThemeRepository;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: EventThemeRepository::class)]
#[ORM\Table(name: 'chill_event_budget_element')]
class EventBudgetElement
{
#[ORM\Column(name: 'id', type: Types::INTEGER)]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[Assert\GreaterThan(value: 0)]
#[Assert\NotNull(message: 'The amount cannot be empty')]
#[ORM\Column(name: 'amount', type: Types::DECIMAL, precision: 10, scale: 2)]
private string $amount;
#[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_budget_element_')]
private ?CommentEmbeddable $comment = null;
#[ORM\ManyToOne(targetEntity: Event::class)]
private Event $event;
#[ORM\ManyToOne(targetEntity: EventBudgetKind::class, inversedBy: 'EventBudgetElement')]
#[ORM\JoinColumn]
private EventBudgetKind $kind;
/* Getters and Setters */
public function getId(): ?int
{
return $this->id;
}
public function setId(?int $id): void
{
$this->id = $id;
}
public function getAmount(): float
{
return (float) $this->amount;
}
public function getComment(): ?CommentEmbeddable
{
return $this->comment;
}
public function getEvent(): Event
{
return $this->event;
}
public function getKind(): EventBudgetKind
{
return $this->kind;
}
public function setAmount(string $amount): self
{
$this->amount = $amount;
return $this;
}
public function setComment(?CommentEmbeddable $comment = null): self
{
$this->comment = $comment;
return $this;
}
public function setEvent(Event $event): self
{
$this->event = $event;
return $this;
}
public function setKind(EventBudgetKind $kind): self
{
$this->kind = $kind;
return $this;
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
/**
* Type of event budget element.
*/
#[ORM\Entity]
#[ORM\Table(name: 'chill_event_budget_kind')]
class EventBudgetKind
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
private ?int $id = null;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true])]
private bool $isActive = true;
#[ORM\Column(enumType: BudgetTypeEnum::class)]
private BudgetTypeEnum $type;
#[ORM\Column(type: Types::JSON, length: 255, options: ['default' => '{}', 'jsonb' => true])]
private array $name = [];
public function getId(): ?int
{
return $this->id;
}
public function getIsActive(): bool
{
return $this->isActive;
}
public function getType(): BudgetTypeEnum
{
return $this->type;
}
public function getName(): ?array
{
return $this->name;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function setType(BudgetTypeEnum $type): self
{
$this->type = $type;
return $this;
}
public function setName(array $name): self
{
$this->name = $name;
return $this;
}
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Entity;
use Chill\EventBundle\Repository\EventThemeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
/**
* Class EventTheme.
*/
#[ORM\HasLifecycleCallbacks]
#[ORM\Entity(repositoryClass: EventThemeRepository::class)]
#[ORM\Table(name: 'chill_event_event_theme')]
class EventTheme
{
#[ORM\Column(type: Types::BOOLEAN, nullable: false)]
private bool $isActive = true;
#[ORM\Id]
#[ORM\Column(name: 'id', type: Types::INTEGER)]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\Column(type: Types::JSON)]
private array $name;
/**
* @var Collection<int, EventTheme>
*/
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: EventTheme::class)]
private Collection $children;
#[ORM\ManyToOne(targetEntity: EventTheme::class, inversedBy: 'children')]
private ?EventTheme $parent = null;
#[ORM\Column(name: 'ordering', type: Types::FLOAT, options: ['default' => '0.0'])]
private float $ordering = 0.0;
/**
* Constructor.
*/
public function __construct()
{
$this->children = new ArrayCollection();
}
/**
* Get active.
*/
public function getIsActive(): bool
{
return $this->isActive;
}
/**
* Get id.
*/
public function getId(): ?int
{
return $this->id;
}
/**
* Get label.
*/
public function getName(): array
{
return $this->name;
}
public function setIsActive(bool $active): self
{
$this->isActive = $active;
return $this;
}
public function setName(array $label): self
{
$this->name = $label;
return $this;
}
public function addChild(self $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
}
return $this;
}
public function removeChild(self $child): self
{
if ($this->children->removeElement($child)) {
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
public function getChildren(): Collection
{
return $this->children;
}
public function hasChildren(): bool
{
return 0 < $this->getChildren()->count();
}
public function hasParent(): bool
{
return null !== $this->parent;
}
public function getOrdering(): float
{
return $this->ordering;
}
public function setOrdering(float $ordering): EventTheme
{
$this->ordering = $ordering;
return $this;
}
public function getParent(): ?self
{
return $this->parent;
}
public function setParent(?self $parent): self
{
$this->parent = $parent;
$parent?->addChild($this);
return $this;
}
}

View File

@@ -0,0 +1,377 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Export\Export;
use Chill\EventBundle\Entity\BudgetTypeEnum;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Export\Declarations;
use Chill\EventBundle\Repository\EventBudgetElementRepository;
use Chill\EventBundle\Repository\EventThemeRepository;
use Chill\EventBundle\Security\EventVoter;
use Chill\EventBundle\Templating\Entity\EventThemeRender;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatableInterface;
/**
* Render a list of events.
*/
class ListEvents implements ListInterface, GroupedExportInterface
{
protected array $fields = [
'event_id',
'event_center',
'event_name',
'event_date',
'event_location',
'event_type',
'event_themes',
'event_moderator',
'event_animators',
'event_participants_count',
'event_budget_resources',
'event_budget_charges',
];
private readonly bool $filterStatsByCenters;
public function __construct(
protected readonly EntityManagerInterface $entityManager,
ParameterBagInterface $parameterBag,
protected readonly TranslatableStringHelperInterface $translatableStringHelper,
protected readonly EventThemeRender $eventThemeRender,
protected readonly EventThemeRepository $eventThemeRepository,
protected readonly LabelThirdPartyHelper $labelThirdPartyHelper,
protected readonly EventBudgetElementRepository $eventBudgetElementRepository,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder): void
{
$builder
->add('fields', ChoiceType::class, [
'multiple' => true,
'expanded' => true,
'choices' => array_combine($this->fields, $this->fields),
'label' => 'Fields to include in export',
'constraints' => [new Callback([
'callback' => static function ($selected, ExecutionContextInterface $context) {
if (0 === \count($selected)) {
$context->buildViolation('You must select at least one element')
->atPath('fields')
->addViolation();
}
},
])],
]);
}
public function getFormDefaultData(): array
{
return [
'fields' => $this->fields,
];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_LIST];
}
public function getDescription(): string
{
return 'export.event.list.description';
}
public function getGroup(): string
{
return 'Exports of events';
}
public function getLabels($key, array $values, $data)
{
return match ($key) {
'event_id' => fn ($value) => '_header' === $value ? $key : $value,
'event_name' => fn ($value) => '_header' === $value ? $key : $value,
'event_date' => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
if ($value instanceof \DateTime) {
return $value->format('Y-m-d');
}
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
return $date ? $date->format('Y-m-d') : $value;
},
'event_type' => function ($value) use ($key) {
if ('_header' === $value) {
return 'export.event.list.'.$key;
}
return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
},
'event_center' => fn ($value) => '_header' === $value ? $key : $value,
'event_moderator' => fn ($value) => '_header' === $value ? $key : $value,
'event_participants_count' => fn ($value) => '_header' === $value ? $key : $value,
'event_location' => fn ($value) => '_header' === $value ? $key : $value,
'event_animators' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, $key),
'event_themes' => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
if (null === $value) {
return '';
}
return implode(
'|',
array_map(
fn ($t) => $this->eventThemeRender->renderString($this->eventThemeRepository->find($t), []),
json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
)
);
},
'event_budget_resources' => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
if (!$value) {
return '';
}
$ids = explode(',', $value);
$ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
$elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);
return implode('|', array_map(function ($element) {
$name = $this->translatableStringHelper->localize($element->getKind()->getName());
$amount = number_format($element->getAmount(), 2, '.', '');
return $name.': '.$amount;
}, $elements));
},
'event_budget_charges' => function ($value) use ($key) {
if ('_header' === $value) {
return $key;
}
if (!$value) {
return '';
}
$ids = explode(',', $value);
$ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
$elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);
return implode('|', array_map(function ($element) {
$name = $this->translatableStringHelper->localize($element->getKind()->getName());
$amount = number_format($element->getAmount(), 2, '.', '');
return $name.': '.$amount;
}, $elements));
},
default => fn ($value) => '_header' === $value ? $key : $value,
};
}
public function getQueryKeys(array $data): array
{
return $data['fields'];
}
public function getResult($query, $data, ExportGenerationContext $context): array
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string|TranslatableInterface
{
return 'export.event.list.title';
}
public function getType(): string
{
return Declarations::EVENT;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): NativeQuery|QueryBuilder
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
// Throw an error if no fields are present
if (!\array_key_exists('fields', $data)) {
throw new \InvalidArgumentException('No fields have been checked.');
}
$qb = $this->entityManager->createQueryBuilder()
->from(Event::class, 'event');
if ($this->filterStatsByCenters) {
$qb
->andWhere('event.center IN (:authorized_centers)')
->setParameter('authorized_centers', $centers);
}
// Add fields based on selection
foreach ($this->fields as $field) {
if (\in_array($field, $data['fields'], true)) {
switch ($field) {
case 'event_id':
$qb->addSelect('event.id AS event_id');
break;
case 'event_name':
$qb->addSelect('event.name AS event_name');
break;
case 'event_date':
$qb->addSelect('event.date AS event_date');
break;
case 'event_type':
if (!$this->hasJoin($qb, 'event.type')) {
$qb->leftJoin('event.type', 'type');
}
$qb->addSelect('type.name AS event_type');
break;
case 'event_center':
if (!$this->hasJoin($qb, 'event.center')) {
$qb->leftJoin('event.center', 'center');
}
$qb->addSelect('center.name AS event_center');
break;
case 'event_moderator':
if (!$this->hasJoin($qb, 'event.moderator')) {
$qb->leftJoin('event.moderator', 'user');
}
$qb->addSelect('user.username AS event_moderator');
break;
case 'event_participants_count':
$qb->addSelect('(SELECT COUNT(p.id) FROM Chill\EventBundle\Entity\Participation p WHERE p.event = event.id) AS event_participants_count');
break;
case 'event_location':
if (!$this->hasJoin($qb, 'event.location')) {
$qb->leftJoin('event.location', 'location');
}
$qb->addSelect('location.name AS event_location');
break;
case 'event_animators':
$qb->addSelect(
'(SELECT AGGREGATE(tp.id) FROM Chill\ThirdPartyBundle\Entity\ThirdParty tp WHERE tp MEMBER OF event.animators) AS event_animators'
);
break;
case 'event_themes':
$qb->addSelect(
'(SELECT AGGREGATE(t.id) FROM Chill\EventBundle\Entity\EventTheme t WHERE t MEMBER OF event.themes) AS event_themes'
);
break;
case 'event_budget_resources':
$qb->addSelect(
'(SELECT AGGREGATE(ebr.id)
FROM Chill\EventBundle\Entity\EventBudgetElement ebr
JOIN ebr.kind kr
WHERE ebr.event = event.id AND kr.type = :resource_type) AS event_budget_resources'
);
$qb->setParameter('resource_type', BudgetTypeEnum::RESOURCE->value);
break;
case 'event_budget_charges':
$qb->addSelect(
'(SELECT AGGREGATE(ebc.id)
FROM Chill\EventBundle\Entity\EventBudgetElement ebc
JOIN ebc.kind kc
WHERE ebc.event = event.id AND kc.type = :charge_type) AS event_budget_charges'
);
$qb->setParameter('charge_type', BudgetTypeEnum::CHARGE->value);
break;
}
}
}
return $qb;
}
public function requiredRole(): string
{
return EventVoter::STATS;
}
public function supportsModifiers()
{
return [Declarations::EVENT];
}
/**
* Helper method to check if a join already exists in the QueryBuilder.
*/
private function hasJoin($queryBuilder, $joinPath): bool
{
$joins = $queryBuilder->getDQLPart('join');
if (!isset($joins['event'])) {
return false;
}
foreach ($joins['event'] as $join) {
if ($join->getJoin() === $joinPath) {
return true;
}
}
return false;
}
public function normalizeFormData(array $formData): array
{
return ['fields' => $formData['fields']];
}
public function denormalizeFormData(array $formData, int $fromVersion): array
{
return ['fields' => $formData['fields']];
}
public function getNormalizationVersion(): int
{
return 1;
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Form;
use Chill\EventBundle\Entity\BudgetTypeEnum;
use Chill\EventBundle\Entity\EventBudgetElement;
use Chill\EventBundle\Entity\EventBudgetKind;
use Chill\EventBundle\Repository\EventBudgetKindRepository;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AddEventBudgetElementType extends AbstractType
{
public function __construct(private readonly EventBudgetKindRepository $kindRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$charges = $this->kindRepository->findByType(BudgetTypeEnum::CHARGE->value);
$resources = $this->kindRepository->findByType(BudgetTypeEnum::RESOURCE->value);
$builder->add('kind', ChoiceType::class, [
'choices' => [
'event.budget.charges' => $charges,
'event.budget.resources' => $resources,
],
'choice_label' => fn (EventBudgetKind $kind) => $this->translatableStringHelper->localize($kind->getName()),
'choice_value' => fn (?EventBudgetKind $kind) => $kind?->getId(),
'placeholder' => 'event.budget.Select a budget element kind',
])
->add('amount', NumberType::class, [
'required' => true,
])
->add('comment', CommentType::class, [
'label' => 'Comment',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => EventBudgetElement::class,
]);
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Form;
use Chill\EventBundle\Entity\BudgetTypeEnum;
use Chill\EventBundle\Entity\EventBudgetKind;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EnumType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
class EventBudgetKindType extends AbstractType
{
public function __construct(private readonly TranslatorInterface $translator) {}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TranslatableStringFormType::class, [
'label' => 'Title',
])
->add('type', EnumType::class, [
'class' => BudgetTypeEnum::class,
'choice_label' => fn (BudgetTypeEnum $type): string => $this->translator->trans($type->value),
'expanded' => true,
'multiple' => false,
'mapped' => true,
'label' => 'event.admin.Select budget type',
])
->add('isActive', CheckboxType::class, [
'label' => 'Actif ?',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('class', EventBudgetKind::class);
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Form;
use Chill\EventBundle\Entity\EventTheme;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\AbstractType;
class EventThemeType extends AbstractType
{
public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper) {}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TranslatableStringFormType::class, [
'label' => 'Nom',
]);
if ('create' === $options['step']) {
$builder
->add('parent', EntityType::class, [
'class' => EventTheme::class,
'required' => false,
'choice_label' => fn (EventTheme $theme): ?string => $this->translatableStringHelper->localize($theme->getName()),
'mapped' => 'create' == $options['step'],
]);
}
$builder
->add('ordering', NumberType::class, [
'required' => true,
'scale' => 6,
])
->add('isActive', ChoiceType::class, [
'choices' => [
'Yes' => true,
'No' => false,
],
'expanded' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => EventTheme::class,
]);
$resolver->setRequired('step')
->setAllowedValues('step', ['create', 'edit']);
}
}

View File

@@ -13,25 +13,39 @@ namespace Chill\EventBundle\Form;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\EventBundle\Entity\BudgetTypeEnum;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Form\Type\PickEventThemeType;
use Chill\EventBundle\Form\Type\PickEventTypeType;
use Chill\EventBundle\Repository\EventBudgetKindRepository;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateTimeType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Form\Type\PickUserLocationType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Translation\TranslatorInterface;
class EventType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
public function __construct(
private readonly IdToLocationDataTransformer $idToLocationDataTransformer,
private readonly EventBudgetKindRepository $eventBudgetKindRepository,
private readonly TranslatorInterface $translator,
) {}
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$chargeKinds = $this->eventBudgetKindRepository->findByType(BudgetTypeEnum::CHARGE->value);
$resourceKinds = $this->eventBudgetKindRepository->findByType(BudgetTypeEnum::RESOURCE->value);
$builder
->add('name', TextType::class, [
'required' => true,
@@ -49,11 +63,28 @@ class EventType extends AbstractType
'class' => '',
],
])
->add('themes', PickEventThemeType::class, [
'multiple' => true,
])
->add('moderator', PickUserDynamicType::class, [
'label' => 'Pick a moderator',
])
->add('location', PickUserLocationType::class, [
'label' => 'event.fields.location',
->add('animatorsIntern', PickUserDynamicType::class, [
'multiple' => true,
'label' => $this->translator->trans('event.fields.internal animators'),
'required' => false,
])
->add('animatorsExtern', PickThirdpartyDynamicType::class, [
'multiple' => true,
'label' => $this->translator->trans('event.fields.external animators'),
'required' => false,
])
->add('budgetElements', ChillCollectionType::class, [
'entry_type' => AddEventBudgetElementType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
])
->add('comment', CommentType::class, [
'label' => 'Comment',
@@ -69,11 +100,11 @@ class EventType extends AbstractType
'delete_empty' => fn (StoredObject $storedObject): bool => '' === $storedObject->getFilename(),
'button_remove_label' => 'event.form.remove_document',
'button_add_label' => 'event.form.add_document',
])
->add('organizationCost', MoneyType::class, [
'label' => 'event.fields.organizationCost',
'help' => 'event.form.organisationCost_help',
]);
$builder->add('location', HiddenType::class)
->get('location')
->addModelTransformer($this->idToLocationDataTransformer);
}
public function configureOptions(OptionsResolver $resolver)
@@ -87,11 +118,9 @@ class EventType extends AbstractType
->setAllowedTypes('role', 'string');
}
/**
* @return string
*/
public function getBlockPrefix()
public function getBlockPrefix(): string
{
return 'chill_eventbundle_event';
// as the js shares some hardcoded items from the activity bundle, we have to rewrite block prefix
return 'chill_activitybundle_activity';
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Form\Type;
use Chill\EventBundle\Entity\EventTheme;
use Chill\EventBundle\Repository\EventThemeRepository;
use Chill\EventBundle\Templating\Entity\EventThemeRender;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PickEventThemeType extends AbstractType
{
public function __construct(private readonly EventThemeRender $eventThemeRender, private readonly EventThemeRepository $eventThemeRepository) {}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefaults([
'class' => EventTheme::class,
'choices' => $this->eventThemeRepository->findByActiveOrdered(),
'choice_label' => fn (EventTheme $et) => $this->eventThemeRender->renderString($et, []),
'placeholder' => 'event.form.Select one or more themes',
'required' => true,
'attr' => ['class' => 'select2'],
'label' => 'event.theme.label',
'multiple' => false,
])
->setAllowedTypes('multiple', ['bool']);
}
public function getParent(): string
{
return EntityType::class;
}
}

View File

@@ -23,15 +23,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class PickEventTypeType extends AbstractType
{
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
public function __construct(TranslatableStringHelper $helper)
{
$this->translatableStringHelper = $helper;
}
public function __construct(protected TranslatableStringHelper $translatableStringHelper) {}
public function configureOptions(OptionsResolver $resolver)
{

View File

@@ -17,17 +17,9 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AdminMenuBuilder implements LocalMenuBuilderInterface
{
/**
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
public function __construct(protected AuthorizationCheckerInterface $authorizationChecker) {}
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
{
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
return;
@@ -52,6 +44,14 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
$menu->addChild('Role', [
'route' => 'chill_event_admin_role',
])->setExtras(['order' => 6530]);
$menu->addChild('event.theme.label', [
'route' => 'chill_crud_event_theme_index',
])->setExtras(['order' => 6540]);
$menu->addChild('event.budget.label', [
'route' => 'chill_crud_event_budget_kind_index',
])->setExtras(['order' => 6550]);
}
public static function getMenuIds(): array

View File

@@ -88,6 +88,16 @@ final readonly class EventACLAwareRepository implements EventACLAwareRepositoryI
$qb->andWhere('event.type IN (:event_types)');
$qb->setParameter('event_types', $filters['event_types']);
}
if (0 < count($filters['centers'] ?? [])) {
$qb->andWhere('event.center IN (:centers)');
$qb->setParameter('centers', $filters['centers']);
}
if (0 < count($filters['responsables'] ?? [])) {
$qb->andWhere('event.moderator IN (:responsables)');
$qb->setParameter('responsables', $filters['responsables']);
}
}
public function buildQueryByAllViewable(array $filters): QueryBuilder

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\EventBudgetElement;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<EventBudgetElement>
*/
class EventBudgetElementRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EventBudgetElement::class);
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\EventBudgetKind;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<EventBudgetKind>
*/
class EventBudgetKindRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EventBudgetKind::class);
}
public function findByActive(): array
{
return $this->createQueryBuilder('e')
->select('e')
->where('e.active = True')
->getQuery()
->getResult();
}
public function findByType(string $type): array
{
return $this->createQueryBuilder('e')
->select('e')
->where('e.type = :type')
->setParameter('type', $type)
->getQuery()
->getResult();
}
}

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Repository;
use Chill\EventBundle\Entity\EventTheme;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<EventTheme>
*/
class EventThemeRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EventTheme::class);
}
public function findByActiveOrdered(): array
{
return $this->createQueryBuilder('t')
->select('t')
->where('t.isActive = True')
->orderBy('t.ordering', 'ASC')
->getQuery()
->getResult();
}
}

View File

@@ -55,3 +55,13 @@ form#export_tableur {
-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.participation-list {
flex-wrap: wrap;
}
.participations-wrapper {
background-color: whitesmoke;
padding: 1rem;
margin-bottom: .5rem;
}

View File

@@ -0,0 +1,14 @@
<template>
<location />
</template>
<script>
import Location from "ChillActivityAssets/vuejs/Activity/components/Location.vue";
export default {
name: "App",
components: {
Location,
},
};
</script>

View File

@@ -0,0 +1,6 @@
import { createApp } from "vue";
import App from "./App.vue";
import store from "./store";
createApp(App).use(store).mount("#event");

View File

@@ -0,0 +1,76 @@
import "es6-promise/auto";
import { createStore } from "vuex";
import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations";
import { whoami } from "ChillMainAssets/lib/api/user";
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
const debug = process.env.NODE_ENV !== "production";
const store = createStore({
strict: debug,
state: {
activity: window.entity, // activity is the event entity in this case (re-using component from activity bundle)
currentEvent: null,
availableLocations: [],
me: null,
},
getters: {},
actions: {
addAvailableLocationGroup({ commit }, payload) {
commit("addAvailableLocationGroup", payload);
},
updateLocation({ commit }, value) {
// console.log("### action: updateLocation", value);
let hiddenLocation = document.getElementById(
"chill_activitybundle_activity_location",
);
if (value.onthefly) {
const body = {
type: "location",
name:
value.name === "__AccompanyingCourseLocation__" ? null : value.name,
locationType: {
id: value.locationType.id,
type: "location-type",
},
};
if (value.address.id) {
Object.assign(body, {
address: {
id: value.address.id,
},
});
}
postLocation(body)
.then((location) => (hiddenLocation.value = location.id))
.catch((err) => {
console.log(err.message);
});
} else {
hiddenLocation.value = value.id;
}
commit("updateLocation", value);
},
},
mutations: {
setWhoAmiI(state, me) {
state.me = me;
},
addAvailableLocationGroup(state, group) {
state.availableLocations.push(group);
},
updateLocation(state, value) {
// console.log("### mutation: updateLocation", value);
state.activity.location = value;
},
},
});
whoami().then((me) => {
store.commit("setWhoAmiI", me);
});
prepareLocations(store);
export default store;

View File

@@ -0,0 +1,12 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block content_form_actions_view %}{% endblock %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,51 @@
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
{% block title %}{{ 'event.admin.title.Event budget element list'|trans }}{% endblock title %}
{% block admin_content %}
<h1>{{ 'event.admin.title.Event budget element list'|trans }}</h1>
<table class="records_list table table-bordered border-dark">
<thead>
<tr>
<th>{{ 'Name'|trans }}</th>
<th>{{ 'Type'|trans }}</th>
<th>{{ 'Active'|trans }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr>
<td>{{ entity.name|localize_translatable_string }}</td>
<td>{{ entity.type.value|trans }}</td>
<td style="text-align:center;">
{%- if entity.isActive -%}
<i class="fa fa-check-square-o"></i>
{%- else -%}
<i class="fa fa-square-o"></i>
{%- endif -%}
</td>
<td>
<ul class="record_actions">
<li>
<a href="{{ path('chill_crud_event_budget_kind_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
</li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ chill_pagination(paginator) }}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ path('chill_crud_event_budget_kind_new') }}" class="btn btn-create">
{{ 'event.admin.new.Create a new budget kind'|trans }}
</a>
</li>
</ul>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block crud_content_form_rows %}
{{ form_row(form.name) }}
<div class="mb-3 row">
<label class="col-form-label col-sm-4">
{{ 'Parent'|trans }}
</label>
<div class="col-sm-8">
{{ entity.parent|chill_entity_render_box }}
</div>
</div>
{{ form_row(form.ordering) }}
{{ form_row(form.isActive) }}
{% endblock crud_content_form_rows %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock admin_content %}

View File

@@ -0,0 +1,45 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %}
<th>{{ 'Id'|trans }}</th>
<th>{{ 'Title'|trans }}</th>
<th>{{ 'Ordering'|trans }}</th>
<th>{{ 'active'|trans }}</th>
<th>&nbsp;</th>
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
<td>
{{ entity|chill_entity_render_box }}
</td>
<td>{{ entity.ordering }}</td>
<td style="text-align:center;">
{%- if entity.isActive -%}
<i class="fa fa-check-square-o"></i>
{%- else -%}
<i class="fa fa-square-o"></i>
{%- endif -%}
</td>
<td>
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_event_theme_edit', { 'id': entity.id }) }}" class="btn btn-edit"></a>
</li>
</ul>
</td>
</tr>
{% endfor %}
{% endblock %}
{% block actions_before %}
<li class='cancel'>
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
</li>
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock admin_content %}

View File

@@ -0,0 +1,13 @@
{% set reversed_parents = parents|reverse %}
<span class="chill-entity entity-event-theme">
<span class="badge bg-chill-l-gray text-dark">
{%- for p in reversed_parents %}
<span class="parent-{{ loop.revindex0 }}">
{{ p.name|localize_translatable_string }}{{ options['default.separator'] }}
</span>
{%- endfor -%}
<span class="child">
{{ eventTheme.name|localize_translatable_string }}
</span>
</span>
</span>

View File

@@ -12,7 +12,7 @@
'title' : 'Delete event'|trans,
'confirm_question' : 'Are you sure you want to remove that event ?'|trans,
'cancel_route' : activeRouteKey,
'cancel_parameters' : { 'event_id' : event_id },
'cancel_parameters' : { 'id' : id },
'form' : delete_form
}
) }}

View File

@@ -17,10 +17,12 @@
{{ form_row(edit_form.date) }}
{{ form_row(edit_form.type, { label: "Event type" }) }}
{{ form_row(edit_form.themes) }}
{{ form_row(edit_form.moderator) }}
{{ form_row(edit_form.animatorsIntern) }}
{{ form_row(edit_form.animatorsExtern) }}
{{ form_row(edit_form.location) }}
{{ form_row(edit_form.organizationCost) }}
{{ form_row(edit_form.budgetElements) }}
{{ form_row(edit_form.comment) }}
{{ form_row(edit_form.documents) }}

View File

@@ -34,7 +34,7 @@
<ul class="record_actions">
<li>
{# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #}
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-view">
<a href="{{ path('chill_event__event_show', { 'id' : event.id } ) }}" class="btn btn-view">
{{ 'See'|trans }}
</a>
{# {% endif %} #}

View File

@@ -53,7 +53,7 @@
{% set returnLabel = 'Back to %person% events'|trans({ '%person%' : currentPerson } ) %}
{% if is_granted('CHILL_EVENT_SEE_DETAILS', participation.event) %}
<a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
<a href="{{ path('chill_event__event_show', { 'id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
class="btn btn-primary btn-sm" title="{{ 'See details of the event'|trans }}">
<i class="fa fa-fw fa-eye"></i>
</a>

View File

@@ -1,25 +1,30 @@
{% extends '@ChillEvent/layout.html.twig' %} {% block js %}
{{ encore_entry_script_tags("mod_async_upload") }}
{{ encore_entry_script_tags("mod_pickentity_type") }}
{% extends '@ChillEvent/layout.html.twig' %}
{% endblock %} {% block css %}
{% block css %}
{{ encore_entry_link_tags("mod_async_upload") }}
{{ encore_entry_link_tags("mod_pickentity_type") }}
{{ encore_entry_link_tags('vue_event') }}
{% endblock %}
{% endblock %} {% block title 'Event creation'|trans %} {% block event_content
-%}
{% block title 'Event creation'|trans %}
{% block event_content -%}
<div class="col-10">
<h1>{{ "Event creation" | trans }}</h1>
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.circle) }}
{{ form_row(form.name) }}
{{ form_row(form.circle) }}
{{ form_row(form.date) }}
{{ form_row(form.type, { label: "Event type" }) }}
{{ form_row(form.themes) }}
{{ form_row(form.moderator) }}
{{ form_row(form.animatorsIntern) }}
{{ form_row(form.animatorsExtern) }}
{{ form_row(form.location) }}
{{ form_row(form.organizationCost) }}
<div id="location"></div>
{{ form_row(form.budgetElements) }}
{{ form_row(form.comment) }}
{{ form_row(form.documents) }}
@@ -40,5 +45,18 @@
</ul>
{{ form_end(form) }}
<div id="event"></div>
</div>
{% endblock %}
{% block js %}
{{ encore_entry_script_tags("mod_async_upload") }}
{{ encore_entry_script_tags("mod_pickentity_type") }}
{{ encore_entry_script_tags('vue_event') }}
<script type="text/javascript">
window.entity = {{ entity_json|json_encode|raw }};
{% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %}
</script>
{% endblock %}

View File

@@ -11,7 +11,7 @@ block js %}
{{ filter | chill_render_filter_order_helper }}
{# {% if is_granted('CHILL_EVENT_CREATE') %} #}
{% if is_granted('CHILL_EVENT_CREATE') %}
<ul class="record_actions">
<li>
<a
@@ -25,7 +25,9 @@ block js %}
>
</li>
</ul>
{# {% endif %} #} {% if events|length > 0 %}
{% endif %}
{% if events|length > 0 %}
<div class="flex-table">
{% for e in events %}
<div class="item-bloc">
@@ -41,6 +43,12 @@ block js %}
{{ e.moderator | chill_entity_render_box }}
</p>
{% endif %}
<div>
{% for t in e.themes %}
<span>{{ t|chill_entity_render_box }}</span>
{% endfor %}
</div>
</div>
<div class="item-col">
<div class="container" style="text-align: right">
@@ -48,20 +56,21 @@ block js %}
<p>
{{ 'count participations to this event'|trans({'count': e.participations|length}) }}
</p>
<p>{{ "center"|trans }}: {{ e.center.name }}</p>
</div>
</div>
</div>
{% if e.participations|length > 0 %}
<div class="item-row separator">
<div class="participation-list item-row separator">
<strong>{{ "Participations" | trans }}&nbsp;: </strong>
{% for part in e.participations|slice(0, 20) %} {% include
{% for part in e.participations|slice(0, 5) %} {% include
'@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: part.person.id }, action:
'show', displayBadge: true, buttonText:
part.person|chill_entity_render_string, isDead:
part.person.deathdate is not null } %} {% endfor %} {% if
e.participations|length > 20 %}
{{ 'events.and_other_count_participants'|trans({'count': e.participations|length - 20}) }}
part.person.deathdate is not null } %} {% endfor %}
{% if e.participations|length > 5 %}
{{ 'events.and_other_count_participants'|trans({'count': e.participations|length - 5}) }}
{% endif %}
</div>
{% endif %}
@@ -106,7 +115,7 @@ block js %}
href="{{
chill_path_add_return_path(
'chill_event__event_show',
{ event_id: e.id }
{ id: e.id }
)
}}"
class="btn btn-show"

View File

@@ -16,6 +16,16 @@
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
{% endblock %}
{% macro insert_onthefly(type, entity, parent = null) %}
{% 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,
isDead: entity.deathdate is defined and entity.deathdate is not null,
parent: parent
} %}
{% endmacro %}
{% block event_content -%}
<div class="col-10">
<h1>{{ 'Details of an event'|trans }}</h1>
@@ -26,6 +36,10 @@
<th>{{ 'Circle'|trans }}</th>
<td>{{ event.circle.name|localize_translatable_string }}</td>
</tr>
<tr>
<th>{{ 'Center'|trans }}</th>
<td>{{ event.center.name }}</td>
</tr>
<tr>
<th>{{ 'Name'|trans }}</th>
<td>{{ event.name }}</td>
@@ -39,12 +53,32 @@
<td>{{ event.type.name|localize_translatable_string }}</td>
</tr>
<tr>
<th>{{ 'Moderator'|trans }}</th>
<td>{{ event.moderator|trans|default('-') }}</td>
<th>{{ 'event.theme.label'|trans }}
<td>
{% for t in event.themes %}
{{ t|chill_entity_render_box }}
{% endfor %}
</td>
</tr>
<tr>
<th>{{ 'event.fields.organizationCost'|trans }}</th>
<td>{{ event.organizationCost|format_currency('EUR') }}</td>
<th>{{ 'Moderator'|trans }}</th>
<td>{{ event.moderator|chill_entity_render_string }}</td>
</tr>
<tr>
<th>{{ 'event.animators.intern'|trans }}</th>
<td>
{% for a in event.animatorsIntern %}
{{ _self.insert_onthefly('user', a) }}
{% endfor %}
</td>
</tr>
<tr>
<th>{{ 'event.animators.extern'|trans }}</th>
<td>
{% for a in event.animatorsExtern %}
{{ _self.insert_onthefly('thirdparty', a) }}
{% endfor %}
</td>
</tr>
<tr>
<th>{{ 'event.fields.location'|trans }}</th>
@@ -60,6 +94,77 @@
</tbody>
</table>
<div class="budget-wrapper" style="background-color: whitesmoke; padding: 1rem; margin-bottom: .5rem;">
{% set resources = event.budgetElements|filter(e => e.kind.type.value == 'Resource') %}
{% set charges = event.budgetElements|filter(e => e.kind.type.value == 'Charge') %}
<h2>Budget de l'événement</h2>
<h3>Ressources</h3>
{% if resources is not empty %}
<table class="table table-bordered border-dark align-middle">
<thead>
<tr>
<th>{{ 'event.budget.resource type'|trans }}</th>
<th>{{ 'event.budget.amount'|trans }}</th>
<th>{{ 'event.budget.comment'|trans }}</th>
</tr>
</thead>
<tbody>
{% set totalResources = 0 %}
{% for res in resources %}
<tr>
<td>{{ res.kind.name|localize_translatable_string }}</td>
<td>{{ res.amount|format_currency('EUR') }}</td>
<td>{{ res.comment.comment|chill_print_or_message(null, 'blockquote') }}</td>
</tr>
{% set totalResources = totalResources + res.amount %}
{% endfor %}
</tbody>
<tfoot>
<tr>
<th>Total</th>
<td>{{ totalResources|format_currency('EUR') }}</td>
</tr>
</tfoot>
</table>
{% else %}
<p class="chill-no-data-statement">{{ 'event.budget.no elements'|trans }}</p>
{% endif %}
<h3>Charges</h3>
{% if charges is not empty %}
<table class="table table-bordered border-dark align-middle">
<thead>
<tr>
<th>{{ 'event.budget.charge type'|trans }}</th>
<th>{{ 'event.budget.amount'|trans }}</th>
<th>{{ 'event.budget.comment'|trans }}</th>
</tr>
</thead>
<tbody>
{% set totalCharges = 0 %}
{% for chg in charges %}
<tr>
<td>{{ chg.kind.name|localize_translatable_string }}</td>
<td>{{ chg.amount|format_currency('EUR') }}</td>
<td>{{ chg.comment.comment|chill_print_or_message(null, 'blockquote') }}</td>
</tr>
{% set totalCharges = totalCharges + chg.amount %}
{% endfor %}
</tbody>
<tfoot>
<tr>
<th>Total</th>
<td>{{ totalCharges|format_currency('EUR') }}</td>
</tr>
</tfoot>
</table>
{% else %}
<p class="chill-no-data-statement">{{ 'event.budget.no elements'|trans }}</p>
{% endif %}
</div>
{% if event.documents|length > 0 %}
<div>
<p><strong>{{ 'event.fields.documents'|trans }}</strong></p>
@@ -80,6 +185,97 @@
</div>
{% endif %}
<div class="participations-wrapper">
<h2>{{ 'Participations'|trans }}</h2>
{% set count = event.participations|length %}
<p class="chill-no-data-statement">{{ 'count participations to this event'|trans({'count': count}) }}</p>
{% if count > 0 %}
<table class="table table-bordered border-dark align-middle">
<thead>
<tr>
<th>{{ 'Person'|trans }}</th>
<th>{{ 'Role'|trans }}</th>
<th>{{ 'Status'|trans }}</th>
<th>{{ 'Last update'|trans }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for participation in event.participations %}
<tr>
<td>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: participation.person.id },
action: 'show',
displayBadge: true,
buttonText: participation.person|chill_entity_render_string,
isDead: participation.person.deathdate is not null
} %}
</td>
<td>{{ participation.role.name|localize_translatable_string }}</td>
<td>{{ participation.status.name|localize_translatable_string }}</td>
<td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
alternative: knplabs/knp-time-bundle provide filter 'ago' #}
<i class="fa fa-info-circle" title="{{ participation.lastUpdate|format_date("long")|escape('html_attr') }}"></i>
</td>
<td>
<ul class="record_actions">
{% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
<li>
<a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
</li>
<li>
<a href="{{ path('chill_event_participation_delete', {'participation_id' : participation.id } ) }}"
class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
</li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<div class="row">
<div class="col-8">
{{ form_start(form_add_participation_by_person) }}
<div class="input-group">
{{ form_widget(form_add_participation_by_person.person_id, { 'attr' : {
'class' : 'custom-select',
'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
}} ) }}
</div>
<input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
{{ form_end(form_add_participation_by_person) }}
</div>
</div>
<ul class="record_actions">
{% if count > 0 %}
<li>
{{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }}
<div class="input-group">
{{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }}
<div class="input-group-append">
{{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }}
</div>
<a class="bt-"></a>
</div>
{{ form_rest(form_export) }}
{{ form_end(form_export) }}
</li>
<li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
{% endif %}
</ul>
</div>
<div class="post_show">
{{ chill_delegated_block('block_footer_show', { 'event': event }) }}
</div>
<ul class="record_actions">
@@ -100,97 +296,5 @@
</li>
</ul>
<h2>{{ 'Participations'|trans }}</h2>
{% set count = event.participations|length %}
<p>{{ 'count participations to this event'|trans({'count': count}) }}</p>
{% if count > 0 %}
<table class="table table-bordered border-dark align-middle">
<thead>
<tr>
<th>{{ 'Person'|trans }}</th>
<th>{{ 'Role'|trans }}</th>
<th>{{ 'Status'|trans }}</th>
<th>{{ 'Last update'|trans }}</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for participation in event.participations %}
<tr>
<td>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: participation.person.id },
action: 'show',
displayBadge: true,
buttonText: participation.person|chill_entity_render_string,
isDead: participation.person.deathdate is not null
} %}
</td>
<td>{{ participation.role.name|localize_translatable_string }}</td>
<td>{{ participation.status.name|localize_translatable_string }}</td>
<td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
alternative: knplabs/knp-time-bundle provide filter 'ago' #}
<i class="fa fa-info-circle" title="{{ participation.lastUpdate|format_date("long")|escape('html_attr') }}"></i>
</td>
<td>
<ul class="record_actions">
{% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
<li>
<a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
</li>
<li>
<a href="{{ path('chill_event_participation_delete', {'participation_id' : participation.id } ) }}"
class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
</li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<ul class="record_actions">
{% if count > 0 %}
<li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
{% endif %}
</ul>
<div class="row" style="margin-bottom: 10em;">
<div class="col-8">
{{ form_start(form_add_participation_by_person) }}
<div class="input-group">
{{ form_widget(form_add_participation_by_person.person_id, { 'attr' : {
'class' : 'custom-select',
'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
}} ) }}
</div>
<input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
{{ form_end(form_add_participation_by_person) }}
</div>
<div class="col-4">
{{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }}
<div class="input-group">
{{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }}
<div class="input-group-append">
{{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }}
</div>
<a class="bt-"></a>
</div>
{{ form_rest(form_export) }}
{{ form_end(form_export) }}
</div>
</div>
<div class="post_show">
{{ chill_delegated_block('block_footer_show', { 'event': event }) }}
</div>
</div>
{% endblock %}

View File

@@ -11,7 +11,7 @@
'title' : 'Remove participation'|trans,
'confirm_question' : 'Are you sure you want to remove that participation ?'|trans,
'cancel_route' : activeRouteKey,
'cancel_parameters' : { 'event_id' : event_id },
'cancel_parameters' : { 'id' : event_id },
'form' : delete_form
}
) }}

View File

@@ -33,7 +33,7 @@
{% set returnPath = app.request.get('return_path') %}
{% set returnLabel = app.request.get('return_label') %}
<a href="{{ returnPath |default( path('chill_event__event_show', { 'event_id' : participation.event.id } )) }}" class="btn btn-cancel">
<a href="{{ returnPath |default( path('chill_event__event_show', { 'id' : participation.event.id } )) }}" class="btn btn-cancel">
{{ returnLabel |default('Back to the event'|trans) }}
</a>
</li>

View File

@@ -34,7 +34,7 @@
<ul class="record_actions sticky-form-buttons">
<li class="cancel">
<a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id } ) }}" class="btn btn-cancel">
<a href="{{ path('chill_event__event_show', { 'id' : participation.event.id } ) }}" class="btn btn-cancel">
{{ 'Back to the event'|trans }}
</a>
</li>

View File

@@ -54,9 +54,9 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
) {
$this->voterHelper = $voterHelperFactory
->generate(self::class)
->addCheckFor(null, [self::SEE])
->addCheckFor(null, [self::SEE, self::CREATE])
->addCheckFor(Event::class, [...self::ROLES])
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(Person::class, [self::SEE])
->addCheckFor(Center::class, [self::STATS])
->build();
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\EventBundle\Templating\Entity;
use Chill\EventBundle\Entity\EventTheme;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Contracts\Translation\TranslatorInterface;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
/**
* @implements ChillEntityRenderInterface<EventTheme>
*/
class EventThemeRender implements ChillEntityRenderInterface
{
public const AND_CHILDREN_MENTION = 'show_and_children_mention';
public const DEFAULT_ARGS = [
self::SEPARATOR_KEY => ' > ',
self::SHOW_AND_CHILDREN => false,
self::AND_CHILDREN_MENTION => 'event_theme.and children',
];
public const SEPARATOR_KEY = 'default.separator';
/**
* Show a mention "and children" on each EventTheme, if the event theme
* has at least one child.
*/
public const SHOW_AND_CHILDREN = 'show_and_children';
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {}
/**
* @throws RuntimeError
* @throws SyntaxError
* @throws LoaderError
*/
public function renderBox($entity, array $options): string
{
$options = array_merge(self::DEFAULT_ARGS, $options);
// give some help to twig: an array of parents
$parents = $this->buildParents($entity);
return $this
->engine
->render(
'@ChillEvent/Entity/event_theme.html.twig',
[
'eventTheme' => $entity,
'parents' => $parents,
'options' => $options,
]
);
}
public function renderString($entity, array $options): string
{
/** @var EventTheme $entity */
$options = array_merge(self::DEFAULT_ARGS, $options);
$titles = [$this->translatableStringHelper->localize($entity->getName())];
// loop to parent, until root
while ($entity->hasParent()) {
$entity = $entity->getParent();
$titles[] = $this->translatableStringHelper->localize(
$entity->getName()
);
}
$titles = \array_reverse($titles);
$title = \implode($options[self::SEPARATOR_KEY], $titles);
if ($options[self::SHOW_AND_CHILDREN] && $entity->hasChildren()) {
$title .= ' ('.$this->translator->trans($options[self::AND_CHILDREN_MENTION]).')';
}
return $title;
}
public function supports($entity, array $options): bool
{
return $entity instanceof EventTheme;
}
private function buildParents(EventTheme $entity): array
{
$parents = [];
while ($entity->hasParent()) {
$entity = $parents[] = $entity->getParent();
}
return $parents;
}
}

View File

@@ -1,3 +1,10 @@
module.exports = function (encore, entries) {
entries.push(__dirname + "/Resources/public/chill/index.js");
encore.addEntry(
"vue_event",
__dirname + "/Resources/public/vuejs/index.js",
);
};

View File

@@ -1,6 +1,7 @@
services:
Chill\EventBundle\Controller\:
autowire: true
autoconfigure: true
resource: '../Controller'
tags: ['controller.service_arguments']
@@ -8,4 +9,11 @@ services:
autowire: true
autoconfigure: true
resource: '../Menu/'
tags: ['chill.menu_builder']
tags: ['chill.menu_builder']
Chill\EventBundle\Templating\Entity\:
autowire: true
autoconfigure: true
resource: '../Templating/Entity'
tags:
- 'chill.render_entity'

View File

@@ -0,0 +1,4 @@
services:
_defaults:
autowire: true
autoconfigure: true

View File

@@ -8,6 +8,9 @@ services:
Chill\EventBundle\Export\Export\CountEvents:
tags:
- { name: chill.export, alias: 'count_events' }
Chill\EventBundle\Export\Export\ListEvents:
tags:
- { name: chill.export, alias: 'list_events' }
Chill\EventBundle\Export\Export\CountEventParticipations:
tags:
- { name: chill.export, alias: 'count_event_participants' }

View File

@@ -31,3 +31,29 @@ services:
Chill\EventBundle\Form\Type\PickEventType:
tags:
- { name: form.type }
Chill\EventBundle\Form\EventThemeType:
tags:
- { name: form.type }
Chill\EventBundle\Form\Type\PickEventThemeType:
tags:
- { name: form.type }
Chill\EventBundle\Form\EventType:
tags:
- { name: form.type }
Chill\EventBundle\Form\EventBudgetKindType:
tags:
- { name: form.type }
Chill\EventBundle\Form\AddEventBudgetElementType:
tags:
- { name: form.type }
Chill\EventBundle\Form\Type\PickAnimatorType:
tags:
- { name: form.type }

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add event theme entity.
*/
final class Version20250428092611 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add an event theme entity';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE chill_event_event_theme_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_event_event_theme (id INT NOT NULL, parent_id INT DEFAULT NULL, isActive BOOLEAN NOT NULL, name JSON NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0.0\' NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_80D7C6B0727ACA70 ON chill_event_event_theme (parent_id)');
$this->addSql('ALTER TABLE chill_event_event_theme ADD CONSTRAINT FK_80D7C6B0727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_event_event_theme (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_event_event_theme DROP CONSTRAINT FK_80D7C6B0727ACA70');
$this->addSql('DROP TABLE chill_event_event_theme');
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250429062911 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add themes property to event';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE chill_event_eventtheme (event_id INT NOT NULL, eventtheme_id INT NOT NULL, PRIMARY KEY(event_id, eventtheme_id))');
$this->addSql('CREATE INDEX IDX_8D75029771F7E88B ON chill_event_eventtheme (event_id)');
$this->addSql('CREATE INDEX IDX_8D750297A81D3C55 ON chill_event_eventtheme (eventtheme_id)');
$this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D75029771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D750297A81D3C55 FOREIGN KEY (eventtheme_id) REFERENCES chill_event_event_theme (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D75029771F7E88B');
$this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D750297A81D3C55');
$this->addSql('DROP TABLE chill_event_eventtheme');
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250505120818 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add budget kind admin entity';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE SEQUENCE chill_event_budget_kind_id_seq INCREMENT BY 1 MINVALUE 1 START 1
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE chill_event_budget_kind (id INT NOT NULL, isActive BOOLEAN DEFAULT true NOT NULL, type VARCHAR(255) NOT NULL, name JSONB DEFAULT '{}' NOT NULL, PRIMARY KEY(id))
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
DROP SEQUENCE chill_event_budget_kind_id_seq CASCADE
SQL);
$this->addSql(<<<'SQL'
DROP TABLE chill_event_budget_kind
SQL);
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250506114531 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create event budget element entity';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE SEQUENCE chill_event_budget_element_id_seq INCREMENT BY 1 MINVALUE 1 START 1
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE chill_event_budget_element (id INT NOT NULL, event_id INT DEFAULT NULL, kind_id INT DEFAULT NULL, amount NUMERIC(10, 2) NOT NULL, comment_budget_element_comment TEXT DEFAULT NULL, comment_budget_element_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_budget_element_userId INT DEFAULT NULL, PRIMARY KEY(id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_BA25859071F7E88B ON chill_event_budget_element (event_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_BA25859030602CA9 ON chill_event_budget_element (kind_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_budget_element ADD CONSTRAINT FK_BA25859071F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_budget_element ADD CONSTRAINT FK_BA25859030602CA9 FOREIGN KEY (kind_id) REFERENCES chill_event_budget_kind (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
DROP SEQUENCE chill_event_budget_element_id_seq CASCADE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_budget_element DROP CONSTRAINT FK_BA25859071F7E88B
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_budget_element DROP CONSTRAINT FK_BA25859030602CA9
SQL);
$this->addSql(<<<'SQL'
DROP TABLE chill_event_budget_element
SQL);
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250507073301 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add animators field to an event';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE chill_event_thirdparty (event_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(event_id, thirdparty_id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_9946573E71F7E88B ON chill_event_thirdparty (event_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_9946573EC7D3A8E6 ON chill_event_thirdparty (thirdparty_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_thirdparty ADD CONSTRAINT FK_9946573E71F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_thirdparty ADD CONSTRAINT FK_9946573EC7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_thirdparty DROP CONSTRAINT FK_9946573E71F7E88B
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_thirdparty DROP CONSTRAINT FK_9946573EC7D3A8E6
SQL);
$this->addSql(<<<'SQL'
DROP TABLE chill_event_thirdparty
SQL);
}
}

View File

@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Event;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20250702144312 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add internal and external animators to event entity';
}
public function up(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE chill_event_animatorsintern (event_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(event_id, user_id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_E699558771F7E88B ON chill_event_animatorsintern (event_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_E6995587A76ED395 ON chill_event_animatorsintern (user_id)
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE chill_event_animatorsextern (event_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(event_id, thirdparty_id))
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_7EFBF7DE71F7E88B ON chill_event_animatorsextern (event_id)
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_7EFBF7DEC7D3A8E6 ON chill_event_animatorsextern (thirdparty_id)
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsintern ADD CONSTRAINT FK_E699558771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsintern ADD CONSTRAINT FK_E6995587A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsextern ADD CONSTRAINT FK_7EFBF7DE71F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsextern ADD CONSTRAINT FK_7EFBF7DEC7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsintern DROP CONSTRAINT FK_E699558771F7E88B
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsintern DROP CONSTRAINT FK_E6995587A76ED395
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsextern DROP CONSTRAINT FK_7EFBF7DE71F7E88B
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE chill_event_animatorsextern DROP CONSTRAINT FK_7EFBF7DEC7D3A8E6
SQL);
$this->addSql(<<<'SQL'
DROP TABLE chill_event_animatorsintern
SQL);
$this->addSql(<<<'SQL'
DROP TABLE chill_event_animatorsextern
SQL);
}
}

View File

@@ -7,7 +7,8 @@ Participation: Participation
Participations: Participations
Status: Statut
Last update: Dernière mise à jour
Moderator: Animateur
Moderator: Responsable
Animators: Animateurs
#CRUD event
Details of an event: Détails d'un événement
@@ -74,7 +75,7 @@ Show the event: Voir l'événement
Subscribe an event: Ajouter un événement
Pick an event: Choisir un événement
Pick a type of event: Choisir un type d'événement
Pick a moderator: Choisir un animateur
Pick a moderator: Choisir un responsable
# exports
Select a format: Choisir un format
@@ -128,15 +129,70 @@ Create a new type: Créer un nouveau type
Create a new status: Créer un nouveau statut
event:
admin:
title:
Event budget element list: Liste des elements du budget pour un évenement
Select budget type: Selectionner le type d'element du budget
new:
Create a new budget kind: Créér un nouveau element de budget
theme:
label: Thématiques
fields:
organizationCost: Coût d'organisation
location: Localisation
documents: Documents
internal animators: Animateurs internes
external animators: Animateurs externes
form:
organisationCost_help: Coût d'organisation pour la structure. Utile pour les statistiques.
add_document: Ajouter un document
remove_document: Supprimer le document
Select one or more themes: Selectionnez une ou plusieurs thématiques
filter:
event_types: Par types d'événement
event_dates: Par date d'événement
center: Par centre
by_responsable: Par responsable
pick_responsable: Filtrer par responsables
budget:
resources: Ressources
charges: Charges
label: Elements de budget d'un évenement
Select a budget element kind: Selectionner un element de budget
no elements: Il y a aucun element
resource type: Ressource
charge type: Charge
amount: Montant
comment: Commentaire
animators:
intern: Animateurs internes
extern: Animateurs externes
crud:
event_theme:
title_new: Créér une nouvelle thématique
title_edit: Modifier la thématique
index:
title: Liste des thématiques
add_new: Créér une nouvelle thématique
event_budget_kind:
title_new: Créér un nouveau element de budget
export:
event:
list:
title: Liste des évenements
description: Crée la liste des évenements en fonction de différents paramètres.
event_id: Identifiant
event_name: Nom
event_date: Date
event_type: Type d'évenement
event_center: Centre
event_moderator: Responsable
event_participants_count: Nombre de participants
event_location: Localisation
event_budget_resources: Ressources
event_budget_charges: Charges
event_animators: Animateurs
event_themes: Thématiques

View File

@@ -118,7 +118,7 @@
{{ entity.notes|chill_print_or_message("Aucune note", 'blockquote') }}
{% endblock crud_content_view_details %}
{% block content_view_actions_duplicate_link %}{% endblock %}
{% block content_view_actions_back %}
<li class="cancel">
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_job_report_index', { 'person': entity.person.id }) }}">

View File

@@ -46,6 +46,7 @@
</dd>
</dl>
{% endblock crud_content_view_details %}
{% block content_view_actions_duplicate_link %}{% endblock %}
{% block content_view_actions_back %}
<li class="cancel">

View File

@@ -206,6 +206,8 @@
</a>
</li>
{% endblock %}
{% block content_view_actions_duplicate_link %}{% endblock %}
{% block content_view_actions_after %}
<li>
<a class="btn btn-misc" href="{{ chill_return_path_or('chill_crud_immersion_bilan', { 'id': entity.id, 'person_id': entity.person.id }) }}">

View File

@@ -94,6 +94,7 @@
{% endblock crud_content_view_details %}
{% block content_view_actions_duplicate_link %}{% endblock %}
{% block content_view_actions_back %}
<li class="cancel">

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Command;
use Chill\MainBundle\Security\RoleDumper;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'chill:main:dump-list-permissions', description: 'Print a markdown reference of permissions (roles) grouped by title with dependencies).')]
final class DumpListPermissionsCommand extends Command
{
public function __construct(private readonly RoleDumper $roleDumper)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$markdown = $this->roleDumper->dumpAsMarkdown();
$output->writeln($markdown);
return Command::SUCCESS;
}
}

View File

@@ -48,6 +48,7 @@ class AbsenceController extends AbstractController
$user = $this->security->getUser();
$user->setAbsenceStart(null);
$user->setAbsenceEnd(null);
$em = $this->managerRegistry->getManager();
$em->flush();

View File

@@ -345,7 +345,7 @@ class ExportController extends AbstractController
* @param array $dataExport Raw data from export step
* @param array $dataFormatter Raw data from formatter step
*/
private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, array $dataFormatter, ?SavedExport $savedExport): array
private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, ?array $dataFormatter, ?SavedExport $savedExport): array
{
if ($this->filterStatsByCenters) {
$formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null);
@@ -365,7 +365,7 @@ class ExportController extends AbstractController
$formExport->submit($dataExport);
$dataExport = $formExport->getData();
if (\count($dataFormatter) > 0) {
if (is_array($dataFormatter) && \count($dataFormatter) > 0) {
$formFormatter = $this->createCreateFormExport(
$alias,
'generate_formatter',
@@ -381,7 +381,7 @@ class ExportController extends AbstractController
'export' => $dataExport['export']['export'] ?? [],
'filters' => $dataExport['export']['filters'] ?? [],
'aggregators' => $dataExport['export']['aggregators'] ?? [],
'pick_formatter' => $dataExport['export']['pick_formatter']['alias'],
'pick_formatter' => ($dataExport['export']['pick_formatter'] ?? [])['alias'] ?? '',
'formatter' => $dataFormatter['formatter'] ?? [],
];
}

View File

@@ -24,6 +24,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User.
@@ -45,6 +46,8 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeImmutable $absenceStart = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
private ?\DateTimeImmutable $absenceEnd = null;
/**
* Array where SAML attributes's data are stored.
*/
@@ -157,6 +160,11 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
return $this->absenceStart;
}
public function getAbsenceEnd(): ?\DateTimeImmutable
{
return $this->absenceEnd;
}
/**
* Get attributes.
*
@@ -336,7 +344,13 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
public function isAbsent(): bool
{
return null !== $this->getAbsenceStart() && $this->getAbsenceStart() <= new \DateTimeImmutable('now');
$now = new \DateTimeImmutable('now');
$absenceStart = $this->getAbsenceStart();
$absenceEnd = $this->getAbsenceEnd();
return null !== $absenceStart
&& $absenceStart <= $now
&& (null === $absenceEnd || $now <= $absenceEnd);
}
/**
@@ -410,6 +424,11 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
$this->absenceStart = $absenceStart;
}
public function setAbsenceEnd(?\DateTimeImmutable $absenceEnd): void
{
$this->absenceEnd = $absenceEnd;
}
public function setAttributeByDomain(string $domain, string $key, $value): self
{
$this->attributes[$domain][$key] = $value;
@@ -675,4 +694,16 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
{
return 'fr';
}
#[Assert\Callback]
public function validateAbsenceDates(ExecutionContextInterface $context): void
{
if (null !== $this->getAbsenceEnd() && null === $this->getAbsenceStart()) {
$context->buildViolation(
'user.absence_end_requires_start'
)
->atPath('absenceEnd')
->addViolation();
}
}
}

View File

@@ -20,7 +20,7 @@ use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Repository\RegroupmentRepositoryInterface;
/**
* @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, version: int}}
* @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter?: string, formatter: array{form: array<string, mixed>, version: int}}
*/
class ExportConfigNormalizer
{
@@ -72,10 +72,14 @@ class ExportConfigNormalizer
}
$serialized['aggregators'] = $aggregatorsSerialized;
$serialized['pick_formatter'] = $formData['pick_formatter'];
$formatter = $this->exportManager->getFormatter($formData['pick_formatter']);
$serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']);
$serialized['formatter']['version'] = $formatter->getNormalizationVersion();
if ($export instanceof ExportInterface) {
$serialized['pick_formatter'] = $formData['pick_formatter'];
$formatter = $this->exportManager->getFormatter($formData['pick_formatter']);
$serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']);
$serialized['formatter']['version'] = $formatter->getNormalizationVersion();
} elseif ($export instanceof DirectExportInterface) {
$serialized['formatter'] = ['form' => [], 'version' => 0];
}
return $serialized;
}
@@ -87,7 +91,12 @@ class ExportConfigNormalizer
public function denormalizeConfig(string $exportAlias, array $serializedData, bool $replaceDisabledByDefaultData = false): array
{
$export = $this->exportManager->getExport($exportAlias);
$formater = $this->exportManager->getFormatter($serializedData['pick_formatter']);
if ($export instanceof ExportInterface) {
$formatter = $this->exportManager->getFormatter($serializedData['pick_formatter']);
} else {
$formatter = null;
}
$filtersConfig = [];
foreach ($serializedData['filters'] as $alias => $filterData) {
@@ -117,8 +126,8 @@ class ExportConfigNormalizer
'export' => $export->denormalizeFormData($serializedData['export']['form'], $serializedData['export']['version']),
'filters' => $filtersConfig,
'aggregators' => $aggregatorsConfig,
'pick_formatter' => $serializedData['pick_formatter'],
'formatter' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
'pick_formatter' => $serializedData['pick_formatter'] ?? '',
'formatter' => $formatter?->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
'centers' => [
'centers' => array_values(array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']['centers']), fn ($item) => null !== $item)),
'regroupments' => array_values(array_filter(array_map(fn (int $id) => $this->regroupmentRepository->find($id), $serializedData['centers']['regroupments']), fn ($item) => null !== $item)),

View File

@@ -23,9 +23,14 @@ class AbsenceType extends AbstractType
{
$builder
->add('absenceStart', ChillDateType::class, [
'required' => true,
'required' => false,
'input' => 'datetime_immutable',
'label' => 'absence.Absence start',
])
->add('absenceEnd', ChillDateType::class, [
'required' => false,
'input' => 'datetime_immutable',
'label' => 'absence.Absence end',
]);
}

View File

@@ -55,6 +55,10 @@ class DateIntervalType extends AbstractType
{
$builder
->add('n', IntegerType::class, [
'attr' => [
'min' => 0,
'step' => 1,
],
'constraints' => [
new GreaterThan([
'value' => 0,

View File

@@ -105,6 +105,11 @@ class UserType extends AbstractType
'required' => false,
'input' => 'datetime_immutable',
'label' => 'absence.Absence start',
])
->add('absenceEnd', ChillDateType::class, [
'required' => false,
'input' => 'datetime_immutable',
'label' => 'absence.Absence end',
]);
// @phpstan-ignore-next-line

View File

@@ -170,13 +170,14 @@ div.banner {
font-weight: lighter;
font-size: 50%;
margin-left: 0.5em;
&:before { content: '(n°'; }
&:after { content: ')'; }
&.same-size {
font-size: unset;
font-weight: unset;
}
}
span.age {
margin-left: 0.5em;
&:before { content: '('; }
&:after { content: ')'; }
}
}

View File

@@ -37,8 +37,13 @@ export const ISOToDate = (str: string | null): Date | null => {
return null;
}
const [year, month, day] = str.split("-").map((p) => parseInt(p));
// If the string already contains time info, use it directly
if (str.includes("T") || str.includes(" ")) {
return new Date(str);
}
// Otherwise, parse date only
const [year, month, day] = str.split("-").map((p) => parseInt(p));
return new Date(year, month - 1, day, 0, 0, 0, 0);
};
@@ -69,20 +74,19 @@ export const ISOToDatetime = (str: string | null): Date | null => {
*
*/
export const datetimeToISO = (date: Date): string => {
let cal, time, offset;
cal = [
const cal = [
date.getFullYear(),
(date.getMonth() + 1).toString().padStart(2, "0"),
date.getDate().toString().padStart(2, "0"),
].join("-");
time = [
const time = [
date.getHours().toString().padStart(2, "0"),
date.getMinutes().toString().padStart(2, "0"),
date.getSeconds().toString().padStart(2, "0"),
].join(":");
offset = [
const offset = [
date.getTimezoneOffset() <= 0 ? "+" : "-",
Math.abs(Math.floor(date.getTimezoneOffset() / 60))
.toString()

View File

@@ -10,8 +10,9 @@ $chill-household-context: #929d69;
// Badges colors
$social-issue-color: #4bafe8;
$social-action-color: $orange;
$event-theme-color: #ecc546;
$activity-color: yellowgreen;
// budget colors
$budget-resource-color: #6d9e63;
$budget-charge-color: #e03851;
$budget-charge-color: #e03851;

View File

@@ -44,8 +44,6 @@ section.chill-entity {
margin-left: 0.5em;
}
span.id-number {
&:before { content: '(n°'; }
&:after { content: ')'; }
}
}
p.moreinfo {}

View File

@@ -21,10 +21,12 @@
>
<template #header>
<h2 class="modal-title">
{{ $t(getTextTitle) }}
{{ trans(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
<span class="sr-only">{{ $t("loading") }}</span>
<span class="sr-only">{{
trans(ADDRESS_LOADING)
}}</span>
</span>
</h2>
</template>
@@ -43,7 +45,7 @@
<template #footer>
<button @click="openEditPane" class="btn btn-create">
{{ $t("create_a_new_address") }}
{{ trans(CREATE_A_NEW_ADDRESS) }}
</button>
</template>
</modal>
@@ -62,13 +64,13 @@
>
<template #before v-if="!bypassFirstStep">
<a class="btn btn-cancel" @click="resetPane">
{{ $t("action.cancel") }}
{{ trans(CANCEL) }}
</a>
</template>
<template #action>
<li>
<button @click="openEditPane" class="btn btn-create">
{{ $t("create_a_new_address") }}
{{ trans(CREATE_A_NEW_ADDRESS) }}
</button>
</li>
</template>
@@ -85,10 +87,12 @@
>
<template #header>
<h2 class="modal-title">
{{ $t(getTextTitle) }}
{{ trans(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
<span class="sr-only">{{ $t("loading") }}</span>
<span class="sr-only">{{
trans(ADDRESS_LOADING)
}}</span>
</span>
</h2>
</template>
@@ -108,17 +112,17 @@
</template>
<template #footer>
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ $t('action.cancel') }}</button>-->
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ trans(CANCEL) }}</button>-->
<button
v-if="!this.context.edit && this.useDatePane"
class="btn btn-update change-icon"
@click="closeEditPane"
>
{{ $t("nav.next") }}
{{ trans(NEXT) }}
<i class="fa fa-fw fa-arrow-right" />
</button>
<button v-else class="btn btn-save" @click="closeEditPane">
{{ $t("action.save") }}
{{ trans(SAVE) }}
</button>
</template>
</modal>
@@ -139,7 +143,7 @@
>
<template #before>
<a class="btn btn-cancel" @click="resetPane">
{{ $t("action.cancel") }}
{{ trans(CANCEL) }}
</a>
</template>
<template #action>
@@ -148,13 +152,13 @@
class="btn btn-update change-icon"
@click="closeEditPane"
>
{{ $t("nav.next") }}
{{ trans(NEXT) }}
<i class="fa fa-fw fa-arrow-right" />
</button>
</li>
<li v-else>
<button class="btn btn-save" @click="closeEditPane">
{{ $t("action.save") }}
{{ trans(SAVE) }}
</button>
</li>
</template>
@@ -171,10 +175,12 @@
>
<template #header>
<h2 class="modal-title">
{{ $t(getTextTitle) }}
{{ trans(getTextTitle) }}
<span v-if="flag.loading" class="loading">
<i class="fa fa-circle-o-notch fa-spin fa-fw" />
<span class="sr-only">{{ $t("loading") }}</span>
<span class="sr-only">{{
trans(ADDRESS_LOADING)
}}</span>
</span>
</h2>
</template>
@@ -193,10 +199,10 @@
<template #footer>
<button class="btn btn-misc" @click="openEditPane">
<i class="fa fa-fw fa-arrow-left" />
{{ $t("nav.previous") }}
{{ trans(PREVIOUS) }}
</button>
<button class="btn btn-save" @click="closeDatePane">
{{ $t("action.save") }}
{{ trans(SAVE) }}
</button>
<!-- -->
</template>
@@ -216,13 +222,13 @@
<template #before>
<button class="btn btn-misc" @click="openEditPane">
<i class="fa fa-fw fa-arrow-left" />
{{ $t("nav.previous") }}
{{ trans(PREVIOUS) }}
</button>
</template>
<template #action>
<li>
<button class="btn btn-save" @click="closeDatePane">
{{ $t("action.save") }}
{{ trans(SAVE) }}
</button>
</li>
</template>
@@ -244,9 +250,16 @@ import {
postPostalCode,
} from "../api";
import {
postAddressToPerson,
postAddressToHousehold,
} from "ChillPersonAssets/vuejs/_api/AddAddress.js";
CREATE_A_NEW_ADDRESS,
ADDRESS_LOADING,
ACTIVITY_CREATE_ADDRESS,
ACTIVITY_EDIT_ADDRESS,
CANCEL,
SAVE,
PREVIOUS,
NEXT,
trans,
} from "translator";
import ShowPane from "./ShowPane.vue";
import SuggestPane from "./SuggestPane.vue";
import EditPane from "./EditPane.vue";
@@ -254,6 +267,17 @@ import DatePane from "./DatePane.vue";
export default {
name: "AddAddress",
setup() {
return {
trans,
CREATE_A_NEW_ADDRESS,
ADDRESS_LOADING,
CANCEL,
SAVE,
PREVIOUS,
NEXT,
};
},
props: ["context", "options", "addressChangedCallback"],
components: {
Modal,
@@ -373,9 +397,11 @@ export default {
(this.options.title.edit !== null ||
this.options.title.create !== null)
) {
console.log("this.options.title", this.options.title);
return this.context.edit
? this.options.title.edit
: this.options.title.create;
? ACTIVITY_EDIT_ADDRESS
: ACTIVITY_CREATE_ADDRESS;
}
return this.context.edit
? this.defaultz.title.edit
@@ -505,7 +531,7 @@ export default {
getAddress(id)
.then(
(address) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.address = address;
this.flag.loading = false;
resolve();
@@ -522,7 +548,7 @@ export default {
fetchCountries()
.then(
(countries) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.countries = countries.results;
if (this.flag.showPane === true) {
this.closeShowPane();
@@ -550,7 +576,7 @@ export default {
fetchCities(country)
.then(
(cities) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.cities = cities.results.filter(
(c) => c.origin !== 3,
); // filter out user-defined cities
@@ -569,7 +595,7 @@ export default {
fetchReferenceAddresses(city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.addresses = addresses.results;
this.flag.loading = false;
resolve();
@@ -800,7 +826,7 @@ export default {
return postAddress(payload)
.then(
(address) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;
@@ -849,7 +875,7 @@ export default {
return patchAddress(payload.addressId, payload.newAddress)
.then(
(address) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.address = address;
this.flag.loading = false;
this.flag.success = true;

View File

@@ -1,6 +1,6 @@
<template>
<h4 class="h3">
{{ $t("fill_an_address") }}
{{ trans(ADDRESS_FILL_AN_ADDRESS) }}
</h4>
<div class="row my-3">
<div class="col-lg-6" v-if="!isNoAddress">
@@ -9,40 +9,40 @@
class="form-control"
type="text"
name="floor"
:placeholder="$t('floor')"
:placeholder="trans(ADDRESS_FLOOR)"
v-model="floor"
/>
<label for="floor">{{ $t("floor") }}</label>
<label for="floor">{{ trans(ADDRESS_FLOOR) }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="corridor"
:placeholder="$t('corridor')"
:placeholder="trans(ADDRESS_CORRIDOR)"
v-model="corridor"
/>
<label for="corridor">{{ $t("corridor") }}</label>
<label for="corridor">{{ trans(ADDRESS_CORRIDOR) }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="steps"
:placeholder="$t('steps')"
:placeholder="trans(ADDRESS_STEPS)"
v-model="steps"
/>
<label for="steps">{{ $t("steps") }}</label>
<label for="steps">{{ trans(ADDRESS_STEPS) }}</label>
</div>
<div class="form-floating my-1">
<input
class="form-control"
type="text"
name="flat"
:placeholder="$t('flat')"
:placeholder="trans(ADDRESS_FLAT)"
v-model="flat"
/>
<label for="flat">{{ $t("flat") }}</label>
<label for="flat">{{ trans(ADDRESS_FLAT) }}</label>
</div>
</div>
<div :class="isNoAddress ? 'col-lg-12' : 'col-lg-6'">
@@ -52,10 +52,12 @@
type="text"
name="buildingName"
maxlength="255"
:placeholder="$t('buildingName')"
:placeholder="trans(ADDRESS_BUILDING_NAME)"
v-model="buildingName"
/>
<label for="buildingName">{{ $t("buildingName") }}</label>
<label for="buildingName">{{
trans(ADDRESS_BUILDING_NAME)
}}</label>
</div>
<div class="form-floating my-1">
<input
@@ -63,10 +65,10 @@
type="text"
name="extra"
maxlength="255"
:placeholder="$t('extra')"
:placeholder="trans(ADDRESS_EXTRA)"
v-model="extra"
/>
<label for="extra">{{ $t("extra") }}</label>
<label for="extra">{{ trans(ADDRESS_EXTRA) }}</label>
</div>
<div class="form-floating my-1" v-if="!isNoAddress">
<input
@@ -74,18 +76,48 @@
type="text"
name="distribution"
maxlength="255"
:placeholder="$t('distribution')"
:placeholder="trans(ADDRESS_DISTRIBUTION)"
v-model="distribution"
/>
<label for="distribution">{{ $t("distribution") }}</label>
<label for="distribution">{{
trans(ADDRESS_DISTRIBUTION)
}}</label>
</div>
</div>
</div>
</template>
<script>
import {
ADDRESS_STREET,
ADDRESS_STREET_NUMBER,
ADDRESS_FLOOR,
ADDRESS_CORRIDOR,
ADDRESS_STEPS,
ADDRESS_FLAT,
ADDRESS_BUILDING_NAME,
ADDRESS_DISTRIBUTION,
ADDRESS_EXTRA,
ADDRESS_FILL_AN_ADDRESS,
trans,
} from "translator";
export default {
name: "AddressMore",
setup() {
return {
ADDRESS_STREET,
ADDRESS_STREET_NUMBER,
ADDRESS_FLOOR,
ADDRESS_CORRIDOR,
ADDRESS_STEPS,
ADDRESS_FLAT,
ADDRESS_BUILDING_NAME,
ADDRESS_DISTRIBUTION,
ADDRESS_EXTRA,
ADDRESS_FILL_AN_ADDRESS,
trans,
};
},
props: ["entity", "isNoAddress"],
computed: {
floor: {

View File

@@ -1,16 +1,16 @@
<template>
<div class="my-1">
<label class="col-form-label" for="addressSelector">{{
$t("address")
trans(ADDRESS_ADDRESS)
}}</label>
<VueMultiselect
id="addressSelector"
v-model="value"
:placeholder="$t('select_address')"
:tag-placeholder="$t('create_address')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('create_address')"
:selected-label="$t('multiselect.selected_label')"
:placeholder="trans(ADDRESS_SELECT_ADDRESS)"
:tag-placeholder="trans(ADDRESS_CREATE_ADDRESS)"
:select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(ADDRESS_CREATE_ADDRESS)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
@search-change="listenInputSearch"
:internal-search="false"
ref="addressSelector"
@@ -42,10 +42,10 @@
class="form-control"
type="text"
name="street"
:placeholder="$t('street')"
:placeholder="trans(ADDRESS_STREET)"
v-model="street"
/>
<label for="street">{{ $t("street") }}</label>
<label for="street">{{ trans(ADDRESS_STREET) }}</label>
</div>
</div>
<div class="col-2">
@@ -54,10 +54,12 @@
class="form-control"
type="text"
name="streetNumber"
:placeholder="$t('streetNumber')"
:placeholder="trans(ADDRESS_STREET_NUMBER)"
v-model="streetNumber"
/>
<label for="streetNumber">{{ $t("streetNumber") }}</label>
<label for="streetNumber">{{
trans(ADDRESS_STREET_NUMBER)
}}</label>
</div>
</div>
</div>
@@ -69,10 +71,32 @@ import {
searchReferenceAddresses,
fetchReferenceAddresses,
} from "../../api.js";
import {
ADDRESS_STREET,
ADDRESS_STREET_NUMBER,
ADDRESS_ADDRESS,
MULTISELECT_SELECTED_LABEL,
MULTISELECT_SELECT_LABEL,
ADDRESS_SELECT_ADDRESS,
ADDRESS_CREATE_ADDRESS,
trans,
} from "translator";
export default {
name: "AddressSelection",
components: { VueMultiselect },
setup() {
return {
ADDRESS_STREET,
ADDRESS_STREET_NUMBER,
ADDRESS_ADDRESS,
MULTISELECT_SELECTED_LABEL,
MULTISELECT_SELECT_LABEL,
ADDRESS_SELECT_ADDRESS,
ADDRESS_CREATE_ADDRESS,
trans,
};
},
props: ["entity", "context", "updateMapCenter", "flag", "checkErrors"],
data() {
return {
@@ -150,7 +174,7 @@ export default {
searchReferenceAddresses(query, this.entity.selected.city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.addresses =
addresses.results;
this.isLoading = false;
@@ -168,7 +192,7 @@ export default {
fetchReferenceAddresses(this.entity.selected.city)
.then(
(addresses) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.addresses =
addresses.results;
this.isLoading = false;

View File

@@ -1,6 +1,6 @@
<template>
<div class="my-1">
<label class="col-form-label">{{ $t("city") }}</label>
<label class="col-form-label">{{ trans(ADDRESS_CITY) }}</label>
<VueMultiselect
id="citySelector"
v-model="value"
@@ -12,15 +12,15 @@
track-by="id"
label="value"
:custom-label="transName"
:placeholder="$t('select_city')"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('create_postal_code')"
:selected-label="$t('multiselect.selected_label')"
:placeholder="trans(ADDRESS_SELECT_CITY)"
:select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(ADDRESS_CREATE_POSTAL_CODE)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
:taggable="true"
:multiple="false"
:internal-search="false"
@tag="addPostcode"
:tag-placeholder="$t('create_postal_code')"
:tag-placeholder="trans(ADDRESS_CREATE_POSTAL_CODE)"
:loading="isLoading"
:options="cities"
/>
@@ -36,10 +36,10 @@
class="form-control"
type="text"
id="code"
:placeholder="$t('postalCode_code')"
:placeholder="trans(ADDRESS_POSTAL_CODE_CODE)"
v-model="code"
/>
<label for="code">{{ $t("postalCode_code") }}</label>
<label for="code">{{ trans(ADDRESS_POSTAL_CODE_CODE) }}</label>
</div>
</div>
<div class="col-8">
@@ -48,10 +48,10 @@
class="form-control"
type="text"
id="name"
:placeholder="$t('postalCode_name')"
:placeholder="trans(ADDRESS_POSTAL_CODE_NAME)"
v-model="name"
/>
<label for="name">{{ $t("postalCode_name") }}</label>
<label for="name">{{ trans(ADDRESS_POSTAL_CODE_NAME) }}</label>
</div>
</div>
</div>
@@ -60,10 +60,32 @@
<script>
import VueMultiselect from "vue-multiselect";
import { searchCities, fetchCities } from "../../api.js";
import {
MULTISELECT_SELECTED_LABEL,
MULTISELECT_SELECT_LABEL,
ADDRESS_POSTAL_CODE_CODE,
ADDRESS_POSTAL_CODE_NAME,
ADDRESS_CREATE_POSTAL_CODE,
ADDRESS_CITY,
ADDRESS_SELECT_CITY,
trans,
} from "translator";
export default {
name: "CitySelection",
components: { VueMultiselect },
setup() {
return {
MULTISELECT_SELECTED_LABEL,
MULTISELECT_SELECT_LABEL,
ADDRESS_CITY,
ADDRESS_SELECT_CITY,
ADDRESS_POSTAL_CODE_CODE,
ADDRESS_POSTAL_CODE_NAME,
ADDRESS_CREATE_POSTAL_CODE,
trans,
};
},
props: [
"entity",
"context",
@@ -167,7 +189,7 @@ export default {
searchCities(query, this.entity.selected.country)
.then(
(cities) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.cities =
cities.results.filter(
(c) => c.origin !== 3,
@@ -187,7 +209,7 @@ export default {
fetchCities(this.entity.selected.country)
.then(
(cities) =>
new Promise((resolve, reject) => {
new Promise((resolve) => {
this.entity.loaded.cities =
cities.results.filter(
(c) => c.origin !== 3,

View File

@@ -1,19 +1,19 @@
<template>
<div class="my-1">
<label class="col-form-label" for="countrySelect">{{
$t("country")
trans(ADDRESS_COUNTRY)
}}</label>
<VueMultiselect
id="countrySelect"
label="name"
track-by="id"
:custom-label="transName"
:placeholder="$t('select_country')"
:placeholder="trans(ADDRESS_SELECT_COUNTRY)"
:options="sortedCountries"
v-model="value"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
:selected-label="$t('multiselect.selected_label')"
:select-label="trans(MULTISELECT_SELECT_LABEL)"
:deselect-label="trans(MULTISELECT_DESELECT_LABEL)"
:selected-label="trans(MULTISELECT_SELECTED_LABEL)"
@select="selectCountry"
@remove="remove"
/>
@@ -23,10 +23,28 @@
<script>
import VueMultiselect from "vue-multiselect";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import {
MULTISELECT_SELECTED_LABEL,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
ADDRESS_COUNTRY,
ADDRESS_SELECT_COUNTRY,
trans,
} from "translator";
export default {
name: "CountrySelection",
components: { VueMultiselect },
setup() {
return {
MULTISELECT_SELECTED_LABEL,
MULTISELECT_SELECT_LABEL,
MULTISELECT_DESELECT_LABEL,
ADDRESS_COUNTRY,
ADDRESS_SELECT_COUNTRY,
trans,
};
},
props: ["context", "entity", "flag", "checkErrors"],
emits: ["getCities"],
data() {

View File

@@ -18,7 +18,7 @@
</div>
<h4 class="h3">
{{ $t("select_an_address_title") }}
{{ trans(ADDRESS_SELECT_AN_ADDRESS_TITLE) }}
</h4>
<div class="row my-3">
<div class="col-lg-6">
@@ -31,7 +31,7 @@
:value="valueConfidential"
/>
<label class="form-check-label" for="isConfidential">
{{ $t("isConfidential") }}
{{ trans(ADDRESS_IS_CONFIDENTIAL) }}
</label>
</div>
<div class="form-check">
@@ -43,7 +43,7 @@
:value="value"
/>
<label class="form-check-label" for="isNoAddress">
{{ $t("isNoAddress") }}
{{ trans(ADDRESS_IS_NO_ADDRESS) }}
</label>
</div>
@@ -108,6 +108,12 @@ import AddressSelection from "./AddAddress/AddressSelection";
import AddressMap from "./AddAddress/AddressMap";
import AddressMore from "./AddAddress/AddressMore";
import ActionButtons from "./ActionButtons.vue";
import {
ADDRESS_SELECT_AN_ADDRESS_TITLE,
ADDRESS_IS_CONFIDENTIAL,
ADDRESS_IS_NO_ADDRESS,
trans,
} from "translator";
export default {
name: "EditPane",
@@ -119,6 +125,14 @@ export default {
AddressMore,
ActionButtons,
},
setup() {
return {
trans,
ADDRESS_SELECT_AN_ADDRESS_TITLE,
ADDRESS_IS_CONFIDENTIAL,
ADDRESS_IS_NO_ADDRESS,
};
},
props: [
"context",
"options",

View File

@@ -5,7 +5,7 @@
v-if="flag.loading"
class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"
/>
<span class="sr-only">{{ $t("loading") }}</span>
<span class="sr-only">{{ trans(ADDRESS_LOADING) }}</span>
</div>
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
@@ -13,8 +13,10 @@
</div>
<div v-if="flag.success" class="alert alert-success">
{{ $t(getSuccessText) }}
<span v-if="forceRedirect">{{ $t("wait_redirection") }}</span>
{{ trans(getSuccessText) }}
<span v-if="forceRedirect">{{
trans(ADDRESS_WAIT_REDIRECTION)
}}</span>
</div>
<div
@@ -28,7 +30,7 @@
<div class="no-address-yet">
<i class="fa fa-map-marker" aria-hidden="true" />
<p class="chill-no-data-statement">
{{ $t("not_yet_address") }}
{{ trans(ADDRESS_NOT_YET_ADDRESS) }}
</p>
<action-buttons
@@ -43,10 +45,10 @@
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
:title="trans(getTextButton)"
>
<span v-if="displayTextButton">{{
$t(getTextButton)
trans(getTextButton)
}}</span>
</button>
</template>
@@ -71,10 +73,10 @@
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
:title="trans(getTextButton)"
>
<span v-if="displayTextButton">{{
$t(getTextButton)
trans(getTextButton)
}}</span>
</button>
</template>
@@ -95,10 +97,10 @@
:class="getClassButton"
type="button"
name="button"
:title="$t(getTextButton)"
:title="trans(getTextButton)"
>
<span v-if="displayTextButton">{{
$t(getTextButton)
trans(getTextButton)
}}</span>
</button>
</template>
@@ -109,13 +111,36 @@
<script>
import AddressRenderBox from "ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue";
import ActionButtons from "./ActionButtons.vue";
import {
ACTIVITY_CREATE_ADDRESS,
ACTIVITY_EDIT_ADDRESS,
ADDRESS_NOT_YET_ADDRESS,
ADDRESS_WAIT_REDIRECTION,
ADDRESS_LOADING,
ADDRESS_ADDRESS_EDIT_SUCCESS,
ADDRESS_ADDRESS_NEW_SUCCESS,
trans,
} from "translator";
export default {
name: "ShowPane",
methods: {},
components: {
AddressRenderBox,
ActionButtons,
},
setup() {
return {
trans,
ACTIVITY_CREATE_ADDRESS,
ACTIVITY_EDIT_ADDRESS,
ADDRESS_NOT_YET_ADDRESS,
ADDRESS_WAIT_REDIRECTION,
ADDRESS_LOADING,
ADDRESS_ADDRESS_NEW_SUCCESS,
ADDRESS_ADDRESS_EDIT_SUCCESS,
};
},
props: [
"context",
"defaultz",
@@ -156,18 +181,20 @@ export default {
(this.options.button.text.edit !== null ||
this.options.button.text.create !== null)
) {
// console.log('this.options.button.text', this.options.button.text)
return this.context.edit
? this.options.button.text.edit
: this.options.button.text.create;
? ACTIVITY_CREATE_ADDRESS
: ACTIVITY_EDIT_ADDRESS;
}
console.log("defaultz", this.defaultz);
return this.context.edit
? this.defaultz.button.text.edit
: this.defaultz.button.text.create;
},
getSuccessText() {
return this.context.edit
? "address_edit_success"
: "address_new_success";
? ADDRESS_ADDRESS_EDIT_SUCCESS
: ADDRESS_ADDRESS_NEW_SUCCESS;
},
onlyButton() {
return typeof this.options.onlyButton !== "undefined"

View File

@@ -64,3 +64,5 @@ const props = defineProps({
entity: Object,
});
</script>
thirdparty_duplicate: merge: Fussioner find: 'Désigner un tiers doublon'

View File

@@ -4,7 +4,7 @@
{% endblock crud_content_header %}
{% block crud_content_view %}
{% block crud_content_view_details %}
<dl class="chill_view_data">
<dt>id</dt>
@@ -20,7 +20,7 @@
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% endblock %}
{% block content_view_actions_before %}{% endblock %}
{% block content_form_actions_delete %}
{% if chill_crud_action_exists(crud_name, 'delete') %}
@@ -32,7 +32,7 @@
</li>
{% endif %}
{% endif %}
{% endblock content_form_actions_delete %}
{% endblock content_form_actions_delete %}
{% block content_view_actions_duplicate_link %}
{% if chill_crud_action_exists(crud_name, 'new') %}
{% if is_granted(chill_crud_config('role', crud_name, 'new'), entity) %}
@@ -44,6 +44,7 @@
{% endif %}
{% endif %}
{% endblock content_view_actions_duplicate_link %}
{% block content_view_actions_merge %}{% endblock %}
{% block content_view_actions_edit_link %}
{% if chill_crud_action_exists(crud_name, 'edit') %}
{% if is_granted(chill_crud_config('role', crud_name, 'edit'), entity) %}

View File

@@ -63,8 +63,7 @@
<script>
const uncheckAll = () => {
const allCenters = document.getElementsByName('centers[center][]');
const allCenters = document.getElementsByName('centers[centers][]');
allCenters.forEach(checkbox => checkbox.checked = false)
}
</script>

View File

@@ -5,7 +5,7 @@
<strong><i class="fa fa-fw fa-filter"></i>Filtrer la liste</strong>
</button>
</h2>
<div class="accordion-collapse collapse" id="filterOrderCollapse" aria-labelledby="filterOrderHeading" data-bs-parent="#filterOrderAccordion">
<div id="filterOrderCollapse" aria-labelledby="filterOrderHeading" data-bs-parent="#filterOrderAccordion">
{% set btnSubmit = 0 %}
<div class="accordion-body chill_filter_order container-xxl p-5 py-2">
<div class="row my-2">
@@ -68,10 +68,17 @@
{{ form_label(form.entity_choices[checkbox_name])}}
{% endif %}
<div class="col-sm-8 pt-2">
{% for c in form['entity_choices'][checkbox_name].children %}
{{ form_widget(c) }}
{{ form_label(c) }}
{% endfor %}
{% set field = form['entity_choices'][checkbox_name] %}
{% if field.vars.expanded %}
{# Render expanded checkboxes/radios #}
{% for c in field.children %}
{{ form_widget(c) }}
{{ form_label(c) }}
{% endfor %}
{% else %}
{# Render select dropdown #}
{{ form_widget(field) }}
{% endif %}
</div>
</div>
{% endfor %}

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