mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 17:28:23 +00:00 
			
		
		
		
	Compare commits
	
		
			122 Commits
		
	
	
		
			v4.0.0
			...
			405-aside-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 80ea99cb9e | |||
| 48df0d49d3 | |||
| bf6375a0b5 | |||
| 21383ddbe7 | |||
| d451d87cdf | |||
| ac6336d197 | |||
| a46b301e44 | |||
| 05f0443011 | |||
| 7f8d8f891e | |||
| ddb932a4fa | |||
| 3a02f15bcd | |||
| bc2fbee5c6 | |||
| ebd10ca522 | |||
| d3a31be412 | |||
| d159a82f88 | |||
| c2d9c73fd4 | |||
| 0d6d15fcf7 | |||
| f9ad96c78b | |||
| fcc9529a20 | |||
| 955cb817c4 | |||
| 823f9546b9 | |||
| be39fa16e7 | |||
| c8bb7575e7 | |||
|  | 80a3734171 | ||
| ab98f3a102 | |||
| 7516e68d77 | |||
| 7b60b7a8af | |||
| d984dec7db | |||
| 46a4dedab8 | |||
| db98519e65 | |||
| c39637180a | |||
| 15f9409bc8 | |||
| 5b90d23367 | |||
| c48625d1cd | |||
| 1195b54a68 | |||
| 2a280b814f | |||
| 230c758255 | |||
| eafda987ae | |||
| 7db8a371fc | |||
| 0d0649dd31 | |||
| ac12b8cdcf | |||
| 9c1611d052 | |||
| 90e3043c3d | |||
| af13bf9088 | |||
| 4aa65d69c7 | |||
| 9e33aec594 | |||
| f88bc7e9f0 | |||
| 8e78c41549 | |||
| 6e36771349 | |||
| 7a82cae155 | |||
| dfab223391 | |||
| 539752485c | |||
| d204df0316 | |||
|  | 82c02f442b | ||
| f32a9dc7bc | |||
| ea06a96f91 | |||
| 76433e2512 | |||
| 1fa464b87a | |||
| 3b75f43e80 | |||
| a40eb95c43 | |||
| 8429c6e693 | |||
| 6db7f6827c | |||
| 3c60c57985 | |||
| 10aa36aae0 | |||
| eed9913a49 | |||
| 1a847d36a0 | |||
| d916962d9b | |||
| 1092fc64ae | |||
| 4e99b6ecbd | |||
| 60d107b541 | |||
| 4c3befe489 | |||
| e176319775 | |||
| 5d810b4230 | |||
| 52b8eea069 | |||
| 4bebeaeaaa | |||
| 3969e12633 | |||
| d60312d4a2 | |||
| d2454ae134 | |||
| 17c2cb1fdc | |||
| 94d7a2a0bb | |||
| aef1efc6cd | |||
| dd0c662c9e | |||
| 6b1696b62e | |||
| c4b760c452 | |||
| 69fe2a8256 | |||
| 8c98242896 | |||
| 7eecfd3882 | |||
| 6713658569 | |||
| 342b786106 | |||
| 80a7437769 | |||
| 8a38ce1a5c | |||
| 5d94bf0556 | |||
| bb71e084b8 | |||
| 27f0bf28e9 | |||
| 383f588795 | |||
| e7a1ff1ac8 | |||
| adc9c47d0a | |||
| e594b65d1e | |||
| c0826bc65c | |||
| 904f4e5ed9 | |||
| 481f82b4c7 | |||
| f5668592ca | |||
| aa085a1562 | |||
| 2754251fdc | |||
| 2f6cef4238 | |||
| 2309636eae | |||
| 56ec8fb516 | |||
| fe6e6e54c1 | |||
| 2a09594b4a | |||
| 7c798e1f63 | |||
| ab8da4ab7a | |||
| 5bdb2df929 | |||
| e3a6b60fa2 | |||
| 5f01673404 | |||
| 63d0a52ea1 | |||
| 837089ff5d | |||
| f383fab578 | |||
| f3cc4a89af | |||
| 703f5dc32d | |||
| b870e71f77 | |||
| a7e278204f | |||
| 4cfdcb2f02 | 
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20251003-224044.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20251003-224044.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Fixed | ||||
| body: Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted | ||||
| time: 2025-10-03T22:40:44.685474863+02:00 | ||||
| custom: | ||||
|     Issue: "" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										6
									
								
								.changes/unreleased/Fixed-20251006-121315.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Fixed-20251006-121315.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| kind: Fixed | ||||
| body: 'Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists' | ||||
| time: 2025-10-06T12:13:15.45905994+02:00 | ||||
| custom: | ||||
|     Issue: "434" | ||||
|     SchemaChange: No schema change | ||||
							
								
								
									
										4
									
								
								.changes/v4.0.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.changes/v4.0.1.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| ## v4.0.1 - 2025-07-08 | ||||
| ### Fixed | ||||
| * Fix package.json for compilation | ||||
|     | ||||
							
								
								
									
										4
									
								
								.changes/v4.0.2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.changes/v4.0.2.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| ## v4.0.2 - 2025-07-09 | ||||
| ### Fixed | ||||
| * Fix add missing translation    | ||||
| * Fix the transfer of evaluations and documents during of accompanyingperiodwork    | ||||
							
								
								
									
										12
									
								
								.changes/v4.1.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.changes/v4.1.0.md
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										10
									
								
								.changes/v4.2.0.md
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								.changes/v4.2.1.md
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										10
									
								
								.changes/v4.3.0.md
									
									
									
									
									
										Normal 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    | ||||
							
								
								
									
										8
									
								
								.changes/v4.4.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.changes/v4.4.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| ## v4.4.0 - 2025-09-11 | ||||
| ### Feature | ||||
| * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works    | ||||
| * ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation    | ||||
| * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works    | ||||
| ### Fixed | ||||
| * Fix display of 'duplicate' and 'merge' buttons in CRUD templates    | ||||
| * Fix saving notification preferences in user's profile    | ||||
							
								
								
									
										3
									
								
								.changes/v4.4.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.changes/v4.4.1.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## v4.4.1 - 2025-09-11 | ||||
| ### Fixed | ||||
| * fix translations in duplicate evaluation document modal and realign close modal button    | ||||
							
								
								
									
										3
									
								
								.changes/v4.4.2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.changes/v4.4.2.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ## v4.4.2 - 2025-09-12 | ||||
| ### Fixed | ||||
| * Fix document generation and workflow generation do not work on accompanying period work documents    | ||||
							
								
								
									
										13
									
								
								.changes/v4.5.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.changes/v4.5.0.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| ## v4.5.0 - 2025-10-03 | ||||
| ### Feature | ||||
| * Only allow delete of attachment on workflows that are not final    | ||||
| * Move up signature buttons on index workflow page for easier access    | ||||
| * Filter out document from attachment list if it is the same as the workflow document    | ||||
| * Block edition on attached document on workflow, if the workflow is finalized or sent external    | ||||
| * Convert workflow's attached document to pdf while sending them external    | ||||
| * After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition    | ||||
| ### Fixed | ||||
| * ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance    | ||||
| * Fix permissions on storedObject which are subject by a workflow    | ||||
| ### DX | ||||
| * Introduce a WaitingScreen component to display a waiting screen    | ||||
							
								
								
									
										4
									
								
								.changes/v4.5.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.changes/v4.5.1.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| ## v4.5.1 - 2025-10-03 | ||||
| ### Fixed | ||||
| * Add missing javascript dependency    | ||||
| * Add exception handling for conversion of attachment on sending external, when documens are already in pdf    | ||||
| @@ -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
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -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,16 +183,59 @@ 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. | ||||
|  | ||||
| ### Testing Information | ||||
|  | ||||
| The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level. | ||||
|  | ||||
| #### Use of mock in tests | ||||
|  | ||||
| ##### General mocking | ||||
|  | ||||
| For creating mock, we prefer using prophecy (library phpspec/prophecy). | ||||
|  | ||||
| ##### Useful helpers and tips that avoid creating 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); | ||||
| - `\Symfony\Component\HttpClient\MockHttpClient`, an implementation of `\Symfony\Contracts\HttpClient\HttpClientInterface`; | ||||
| - When using `\Symfony\Component\Mailer\MailerInterface`, we can create the mock with "InMemoryTransport": | ||||
|  | ||||
|     ```php | ||||
|     use Symfony\Component\Mailer\Transport\InMemoryTransport; | ||||
|     use \Symfony\Component\Mailer\Mailer; | ||||
|  | ||||
|     $transport = new InMemoryTransport(); | ||||
|     $mailer = new Mailer($transport); | ||||
|  | ||||
|     // After sending: | ||||
|     $messages = $transport->getSent(); // array of SentMessage | ||||
|     ``` | ||||
| - When using `\Symfony\Contracts\EventDispatcher\EventDispatcherInterface`, we can use directly an instance of `\Symfony\Component\EventDispatcher\EventDispatcher`; | ||||
|  | ||||
| ##### When we prefer not creating a mock | ||||
|  | ||||
| - When we use Doctrine Entities related to the project, we prefer not to use a mock: we instantiate them directly (unless it requires too much code to write); | ||||
|  | ||||
| ##### Mocking final and readonly classes | ||||
|  | ||||
| Classes marked as final can't be mocked. To avoid that, either: | ||||
|  | ||||
| - we remove the `final` keyword from the class; | ||||
| - we extract an interface from the final class. | ||||
|  | ||||
| This must be a decision made by a human, not by an AI. Every AI task must abort with an explicit message in that case. | ||||
|  | ||||
| #### Running Tests | ||||
|  | ||||
| The tests are run from the project's root (not from the bundle's root). | ||||
|  | ||||
| ```bash | ||||
| # Run all tests | ||||
| vendor/bin/phpunit | ||||
| @@ -254,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 | ||||
|  | ||||
|   | ||||
							
								
								
									
										88
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -6,6 +6,94 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), | ||||
| and is generated by [Changie](https://github.com/miniscruff/changie). | ||||
|  | ||||
|  | ||||
| ## v4.5.1 - 2025-10-03 | ||||
| ### Fixed | ||||
| * Add missing javascript dependency    | ||||
| * Add exception handling for conversion of attachment on sending external, when documens are already in pdf    | ||||
|  | ||||
| ## v4.5.0 - 2025-10-03 | ||||
| ### Feature | ||||
| * Only allow delete of attachment on workflows that are not final    | ||||
| * Move up signature buttons on index workflow page for easier access    | ||||
| * Filter out document from attachment list if it is the same as the workflow document    | ||||
| * Block edition on attached document on workflow, if the workflow is finalized or sent external    | ||||
| * Convert workflow's attached document to pdf while sending them external    | ||||
| * After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition    | ||||
| ### Fixed | ||||
| * ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance    | ||||
| * Fix permissions on storedObject which are subject by a workflow    | ||||
| ### DX | ||||
| * Introduce a WaitingScreen component to display a waiting screen    | ||||
|  | ||||
| ## v4.4.2 - 2025-09-12 | ||||
| ### Fixed | ||||
| * Fix document generation and workflow generation do not work on accompanying period work documents    | ||||
|  | ||||
| ## v4.4.1 - 2025-09-11 | ||||
| ### Fixed | ||||
| * fix translations in duplicate evaluation document modal and realign close modal button    | ||||
|  | ||||
| ## v4.4.0 - 2025-09-11 | ||||
| ### Feature | ||||
| * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works    | ||||
| * ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation    | ||||
| * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works    | ||||
| ### Fixed | ||||
| * Fix display of 'duplicate' and 'merge' buttons in CRUD templates    | ||||
| * Fix saving notification preferences in user's profile    | ||||
|  | ||||
| ## 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    | ||||
| * Fix the transfer of evaluations and documents during of accompanyingperiodwork    | ||||
|  | ||||
| ## v4.0.1 - 2025-07-08 | ||||
| ### Fixed | ||||
| * Fix package.json for compilation | ||||
|     | ||||
|  | ||||
| ## v4.0.0 - 2025-07-08 | ||||
| ### Feature | ||||
| * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works | ||||
|   | ||||
							
								
								
									
										2
									
								
								config/packages/chill_aside_activity.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								config/packages/chill_aside_activity.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| chill_aside_activity: | ||||
|     show_concerned_persons_count: hidden | ||||
| @@ -62,8 +62,10 @@ framework: | ||||
|             'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority | ||||
|             'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async | ||||
|             'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async | ||||
|             'Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage': async | ||||
|             'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority | ||||
|             'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async | ||||
|             'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async | ||||
|             # end of routes added by chill-bundles recipes | ||||
|             # Route your messages to the transports | ||||
|             # 'App\Message\YourMessage': async | ||||
|   | ||||
| @@ -11,7 +11,6 @@ | ||||
|     "@hotwired/stimulus": "^3.0.0", | ||||
|     "@luminateone/eslint-baseline": "^1.0.9", | ||||
|     "@symfony/stimulus-bridge": "^3.2.0", | ||||
|     "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", | ||||
|     "@symfony/webpack-encore": "^4.1.0", | ||||
|     "@tsconfig/node20": "^20.1.4", | ||||
|     "@types/dompurify": "^3.0.5", | ||||
| @@ -56,6 +55,7 @@ | ||||
|     "@tsconfig/node20": "^20.1.4", | ||||
|     "@types/dompurify": "^3.0.5", | ||||
|     "@types/leaflet": "^1.9.3", | ||||
|     "@vueuse/core": "^13.9.0", | ||||
|     "bootstrap-icons": "^1.11.3", | ||||
|     "dropzone": "^5.7.6", | ||||
|     "es6-promise": "^4.2.8", | ||||
|   | ||||
| @@ -25,6 +25,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte | ||||
|         $config = $this->processConfiguration($configuration, $configs); | ||||
|  | ||||
|         $container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']); | ||||
|         $container->setParameter('chill_aside_activity.show_concerned_persons_count', 'visible' === $config['show_concerned_persons_count']); | ||||
|  | ||||
|         $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config')); | ||||
|         $loader->load('services.yaml'); | ||||
| @@ -38,6 +39,24 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte | ||||
|     { | ||||
|         $this->prependRoute($container); | ||||
|         $this->prependCruds($container); | ||||
|         $this->prependTwigConfig($container); | ||||
|     } | ||||
|  | ||||
|     protected function prependTwigConfig(ContainerBuilder $container) | ||||
|     { | ||||
|         // Get the configuration for this bundle | ||||
|         $chillAsideActivityConfig = $container->getExtensionConfig($this->getAlias()); | ||||
|         $config = $this->processConfiguration($this->getConfiguration($chillAsideActivityConfig, $container), $chillAsideActivityConfig); | ||||
|  | ||||
|         // Add configuration to twig globals | ||||
|         $twigConfig = [ | ||||
|             'globals' => [ | ||||
|                 'chill_aside_activity_config' => [ | ||||
|                     'show_concerned_persons_count' => 'visible' === $config['show_concerned_persons_count'], | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
|         $container->prependExtensionConfig('twig', $twigConfig); | ||||
|     } | ||||
|  | ||||
|     protected function prependCruds(ContainerBuilder $container) | ||||
|   | ||||
| @@ -141,6 +141,12 @@ class Configuration implements ConfigurationInterface | ||||
|             ->end() | ||||
|             ->end() | ||||
|             ->end() | ||||
|             ->end() | ||||
|             ->enumNode('show_concerned_persons_count') | ||||
|             ->values(['hidden', 'visible']) | ||||
|             ->defaultValue('hidden') | ||||
|             ->info('Show the concerned persons count field in aside activity forms and views') | ||||
|             ->end() | ||||
|             ->end(); | ||||
|  | ||||
|         return $treeBuilder; | ||||
|   | ||||
| @@ -62,6 +62,10 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface | ||||
|     #[ORM\ManyToOne(targetEntity: User::class)] | ||||
|     private User $updatedBy; | ||||
|  | ||||
|     #[Assert\GreaterThanOrEqual(0)] | ||||
|     #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)] | ||||
|     private ?int $concernedPersonsCount = 0; | ||||
|  | ||||
|     public function getAgent(): ?User | ||||
|     { | ||||
|         return $this->agent; | ||||
| @@ -186,4 +190,16 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function getConcernedPersonsCount(): ?int | ||||
|     { | ||||
|         return $this->concernedPersonsCount; | ||||
|     } | ||||
|  | ||||
|     public function setConcernedPersonsCount(?int $concernedPersonsCount): self | ||||
|     { | ||||
|         $this->concernedPersonsCount = $concernedPersonsCount; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,86 @@ | ||||
| <?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\AsideActivityBundle\Export\Aggregator; | ||||
|  | ||||
| use Chill\AsideActivityBundle\Export\Declarations; | ||||
| use Chill\MainBundle\Export\AggregatorInterface; | ||||
| use Doctrine\ORM\QueryBuilder; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
|  | ||||
| class ByConcernedPersonsCountAggregator implements AggregatorInterface | ||||
| { | ||||
|     public function addRole(): ?string | ||||
|     { | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void | ||||
|     { | ||||
|         $qb->addSelect('aside.concernedPersonsCount AS by_concerned_persons_count_aggregator') | ||||
|             ->addGroupBy('by_concerned_persons_count_aggregator'); | ||||
|     } | ||||
|  | ||||
|     public function applyOn(): string | ||||
|     { | ||||
|         return Declarations::ASIDE_ACTIVITY_TYPE; | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder): void | ||||
|     { | ||||
|         // No form needed | ||||
|     } | ||||
|  | ||||
|     public function getNormalizationVersion(): int | ||||
|     { | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|     public function normalizeFormData(array $formData): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function denormalizeFormData(array $formData, int $fromVersion): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data): callable | ||||
|     { | ||||
|         return function ($value): string { | ||||
|             if ('_header' === $value) { | ||||
|                 return 'export.aggregator.Concerned persons count'; | ||||
|             } | ||||
|  | ||||
|             if (null === $value) { | ||||
|                 return 'export.aggregator.No concerned persons count specified'; | ||||
|             } | ||||
|  | ||||
|             return (string) $value; | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data): array | ||||
|     { | ||||
|         return ['by_concerned_persons_count_aggregator']; | ||||
|     } | ||||
|  | ||||
|     public function getTitle(): string | ||||
|     { | ||||
|         return 'export.aggregator.Group by concerned persons count'; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,116 @@ | ||||
| <?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\AsideActivityBundle\Export\Export; | ||||
|  | ||||
| use Chill\AsideActivityBundle\Export\Declarations; | ||||
| use Chill\AsideActivityBundle\Repository\AsideActivityRepository; | ||||
| use Chill\AsideActivityBundle\Security\AsideActivityVoter; | ||||
| use Chill\MainBundle\Export\ExportInterface; | ||||
| use Chill\MainBundle\Export\FormatterInterface; | ||||
| use Chill\MainBundle\Export\GroupedExportInterface; | ||||
| use Doctrine\ORM\Query; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
|  | ||||
| class SumConcernedPersonsCountAsideActivity implements ExportInterface, GroupedExportInterface | ||||
| { | ||||
|     public function __construct(private readonly AsideActivityRepository $repository) {} | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder) {} | ||||
|  | ||||
|     public function getNormalizationVersion(): int | ||||
|     { | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|     public function normalizeFormData(array $formData): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function denormalizeFormData(array $formData, int $fromVersion): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getFormDefaultData(): array | ||||
|     { | ||||
|         return []; | ||||
|     } | ||||
|  | ||||
|     public function getAllowedFormattersTypes(): array | ||||
|     { | ||||
|         return [FormatterInterface::TYPE_TABULAR]; | ||||
|     } | ||||
|  | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'export.Sum concerned persons count for aside activities'; | ||||
|     } | ||||
|  | ||||
|     public function getGroup(): string | ||||
|     { | ||||
|         return 'export.Exports of aside activities'; | ||||
|     } | ||||
|  | ||||
|     public function getLabels($key, array $values, $data) | ||||
|     { | ||||
|         if ('export_sum_concerned_persons_count' !== $key) { | ||||
|             throw new \LogicException("the key {$key} is not used by this export"); | ||||
|         } | ||||
|  | ||||
|         $labels = array_combine($values, $values); | ||||
|         $labels['_header'] = $this->getTitle(); | ||||
|  | ||||
|         return static fn ($value) => $labels[$value]; | ||||
|     } | ||||
|  | ||||
|     public function getQueryKeys($data): array | ||||
|     { | ||||
|         return ['export_sum_concerned_persons_count']; | ||||
|     } | ||||
|  | ||||
|     public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array | ||||
|     { | ||||
|         return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); | ||||
|     } | ||||
|  | ||||
|     public function getTitle(): string | ||||
|     { | ||||
|         return 'export.Sum concerned persons count for aside activities'; | ||||
|     } | ||||
|  | ||||
|     public function getType(): string | ||||
|     { | ||||
|         return Declarations::ASIDE_ACTIVITY_TYPE; | ||||
|     } | ||||
|  | ||||
|     public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder | ||||
|     { | ||||
|         $qb = $this->repository->createQueryBuilder('aside'); | ||||
|  | ||||
|         $qb->select('SUM(COALESCE(aside.concernedPersonsCount, 0)) as export_sum_concerned_persons_count'); | ||||
|  | ||||
|         return $qb; | ||||
|     } | ||||
|  | ||||
|     public function requiredRole(): string | ||||
|     { | ||||
|         return AsideActivityVoter::STATS; | ||||
|     } | ||||
|  | ||||
|     public function supportsModifiers(): array | ||||
|     { | ||||
|         return [ | ||||
|             Declarations::ASIDE_ACTIVITY_TYPE, | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; | ||||
| use Symfony\Component\Form\AbstractType; | ||||
| use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; | ||||
| use Symfony\Component\Form\Extension\Core\Type\ChoiceType; | ||||
| use Symfony\Component\Form\Extension\Core\Type\IntegerType; | ||||
| use Symfony\Component\Form\FormBuilderInterface; | ||||
| use Symfony\Component\Form\FormEvent; | ||||
| use Symfony\Component\Form\FormEvents; | ||||
| @@ -29,11 +30,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver; | ||||
| final class AsideActivityFormType extends AbstractType | ||||
| { | ||||
|     private readonly array $timeChoices; | ||||
|     private readonly bool $showConcernedPersonsCount; | ||||
|  | ||||
|     public function __construct( | ||||
|         ParameterBagInterface $parameterBag, | ||||
|     ) { | ||||
|         $this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration'); | ||||
|         $this->showConcernedPersonsCount = $parameterBag->get('chill_aside_activity.show_concerned_persons_count'); | ||||
|     } | ||||
|  | ||||
|     public function buildForm(FormBuilderInterface $builder, array $options) | ||||
| @@ -76,6 +79,16 @@ final class AsideActivityFormType extends AbstractType | ||||
|             ->add('location', PickUserLocationType::class) | ||||
|         ; | ||||
|  | ||||
|         if ($this->showConcernedPersonsCount) { | ||||
|             $builder->add('concernedPersonsCount', IntegerType::class, [ | ||||
|                 'label' => 'Concerned persons count', | ||||
|                 'required' => false, | ||||
|                 'attr' => [ | ||||
|                     'min' => 0, | ||||
|                 ], | ||||
|             ]); | ||||
|         } | ||||
|  | ||||
|         foreach (['duration'] as $fieldName) { | ||||
|             $builder->get($fieldName) | ||||
|                 ->addModelTransformer($durationTimeTransformer); | ||||
|   | ||||
| @@ -42,6 +42,11 @@ | ||||
|                                 {%- if entity.location.name is defined -%} | ||||
|                                     <div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div> | ||||
|                                 {%- endif -%} | ||||
|  | ||||
|                                 {%- if entity.concernedPersonsCount > 0 -%} | ||||
|                                     <div><i class="fa fa-fw fa-user"></i>{{ entity.concernedPersonsCount }}</div> | ||||
|                                 {%- endif -%} | ||||
|  | ||||
| 							</div> | ||||
| 							<div class="item-col" style="justify-content: flex-end;"> | ||||
| 								<div class="box"> | ||||
|   | ||||
| @@ -38,6 +38,11 @@ | ||||
| 				<dt class="inline">{{ 'Duration'|trans }}</dt> | ||||
| 				<dd>{{ entity.duration|date('H:i') }}</dd> | ||||
|  | ||||
|                 {% if chill_aside_activity_config.show_concerned_persons_count == 'visible' %} | ||||
|                     <dt class="inline">{{ 'Concerned persons count'|trans }}</dt> | ||||
|                     <dd>{{ entity.concernedPersonsCount }}</dd> | ||||
|                 {% endif %} | ||||
|  | ||||
| 				<dt class="inline">{{ 'Remark'|trans }}</dt> | ||||
| 				{%- if entity.note is empty -%} | ||||
| 					<dd> | ||||
| @@ -55,5 +60,6 @@ | ||||
| 			</dl> | ||||
|  | ||||
| 		{% endblock %} | ||||
|         {% block content_view_actions_duplicate_link %}{% endblock %} | ||||
| 	{% endembed %} | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| <?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\AsideActivityBundle\Tests\Export\Aggregator; | ||||
|  | ||||
| use Chill\AsideActivityBundle\Entity\AsideActivity; | ||||
| use Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator; | ||||
| use Chill\MainBundle\Test\Export\AbstractAggregatorTest; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| class ByConcernedPersonsCountAggregatorTest extends AbstractAggregatorTest | ||||
| { | ||||
|     public function getAggregator() | ||||
|     { | ||||
|         return new ByConcernedPersonsCountAggregator(); | ||||
|     } | ||||
|  | ||||
|     public static function getFormData(): array | ||||
|     { | ||||
|         return [ | ||||
|             [], | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public static function getQueryBuilders(): iterable | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|         $em = self::getContainer()->get(EntityManagerInterface::class); | ||||
|  | ||||
|         return [ | ||||
|             $em->createQueryBuilder() | ||||
|                 ->select('count(aside.id)') | ||||
|                 ->from(AsideActivity::class, 'aside'), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,50 @@ | ||||
| <?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\AsideActivityBundle\Tests\Export\Export; | ||||
|  | ||||
| use Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity; | ||||
| use Chill\AsideActivityBundle\Repository\AsideActivityRepository; | ||||
| use Chill\MainBundle\Test\Export\AbstractExportTest; | ||||
|  | ||||
| /** | ||||
|  * @internal | ||||
|  * | ||||
|  * @coversNothing | ||||
|  */ | ||||
| final class SumConcernedPersonsCountAsideActivityTest extends AbstractExportTest | ||||
| { | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         self::bootKernel(); | ||||
|     } | ||||
|  | ||||
|     public function getExport() | ||||
|     { | ||||
|         $repository = self::getContainer()->get(AsideActivityRepository::class); | ||||
|  | ||||
|         yield new SumConcernedPersonsCountAsideActivity($repository); | ||||
|     } | ||||
|  | ||||
|     public static function getFormData(): array | ||||
|     { | ||||
|         return [ | ||||
|             [], | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     public static function getModifiersCombination(): array | ||||
|     { | ||||
|         return [ | ||||
|             ['aside_activity'], | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
| @@ -20,6 +20,10 @@ services: | ||||
|       tags: | ||||
|           - { name: chill.export, alias: 'avg_aside_activity_duration' } | ||||
|  | ||||
|   Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity: | ||||
|       tags: | ||||
|           - { name: chill.export, alias: 'sum_aside_activity_concerned_persons_count' } | ||||
|  | ||||
|   ## Filters | ||||
|   chill.aside_activity.export.date_filter: | ||||
|     class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter | ||||
| @@ -70,3 +74,7 @@ services: | ||||
|   Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator: | ||||
|       tags: | ||||
|           - { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' } | ||||
|  | ||||
|   Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator: | ||||
|       tags: | ||||
|           - { name: chill.export_aggregator, alias: 'aside_activity_concerned_persons_count_aggregator' } | ||||
|   | ||||
| @@ -0,0 +1,33 @@ | ||||
| <?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\AsideActivity; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
|  | ||||
| final class Version20251006113048 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Add concernedPersonsCount property to AsideActivity entity'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD concernedPersonsCount INT DEFAULT 0'); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP concernedPersonsCount'); | ||||
|     } | ||||
| } | ||||
| @@ -27,6 +27,7 @@ Emergency: Urgent | ||||
| by: "Par " | ||||
| location: Lieu | ||||
| Asideactivity location: Localisation de l'activité | ||||
| Concerned persons count: Nombre d'usager concernés | ||||
|  | ||||
| # Crud | ||||
| crud: | ||||
| @@ -190,6 +191,7 @@ export: | ||||
|     Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères | ||||
|     Average aside activities duration: Durée moyenne des activités annexes | ||||
|     Sum aside activities duration: Durée des activités annexes | ||||
|     Sum concerned persons count for aside activities: Nombre d'usager concernés par les activités annexes | ||||
|     filter: | ||||
|         Filter by aside activity date: Filtrer les activités annexes par date | ||||
|         Filter by aside activity type: Filtrer les activités annexes par type d'activité | ||||
| @@ -210,6 +212,8 @@ export: | ||||
|         'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%" | ||||
|     aggregator: | ||||
|         Group by aside activity type: Grouper les activités annexes par type d'activité | ||||
|         Group by concerned persons count: Grouper les activités annexes par nombre d'usagers conernés | ||||
|         Concerned persons count: Nombre d'usagers concernés | ||||
|         Aside activity type: Type d'activité annexe | ||||
|         by_user_job: | ||||
|             Aggregate by user job: Grouper les activités annexes par métier des utilisateurs | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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>({ | ||||
|   | ||||
| @@ -24,7 +24,11 @@ use Doctrine\ORM\EntityManagerInterface; | ||||
|  | ||||
| class CalendarForShortMessageProvider | ||||
| { | ||||
|     public function __construct(private readonly CalendarRepository $calendarRepository, private readonly EntityManagerInterface $em, private readonly RangeGeneratorInterface $rangeGenerator) {} | ||||
|     public function __construct( | ||||
|         private readonly CalendarRepository $calendarRepository, | ||||
|         private readonly EntityManagerInterface $em, | ||||
|         private readonly RangeGeneratorInterface $rangeGenerator, | ||||
|     ) {} | ||||
|  | ||||
|     /** | ||||
|      * Generate calendars instance. | ||||
|   | ||||
| @@ -21,7 +21,6 @@ namespace Chill\CalendarBundle\Tests\Service\ShortMessageNotification; | ||||
| use Chill\CalendarBundle\Entity\Calendar; | ||||
| use Chill\CalendarBundle\Repository\CalendarRepository; | ||||
| use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider; | ||||
| use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultRangeGenerator; | ||||
| use Chill\CalendarBundle\Service\ShortMessageNotification\RangeGeneratorInterface; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| @@ -82,10 +81,16 @@ final class CalendarForShortMessageProviderTest extends TestCase | ||||
|         $em = $this->prophesize(EntityManagerInterface::class); | ||||
|         $em->clear()->shouldBeCalled(); | ||||
|  | ||||
|         $calendarRangeGenerator = $this->prophesize(RangeGeneratorInterface::class); | ||||
|         $calendarRangeGenerator->generateRange(Argument::any())->willReturn([ | ||||
|             'startDate' => new \DateTimeImmutable('yesterday'), | ||||
|             'endDate' => new \DateTimeImmutable('now'), | ||||
|         ]); | ||||
|  | ||||
|         $provider = new CalendarForShortMessageProvider( | ||||
|             $calendarRepository->reveal(), | ||||
|             $em->reveal(), | ||||
|             new DefaultRangeGenerator() | ||||
|             $calendarRangeGenerator->reveal(), | ||||
|         ); | ||||
|  | ||||
|         $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now'))); | ||||
| @@ -103,26 +108,32 @@ final class CalendarForShortMessageProviderTest extends TestCase | ||||
|             Argument::type(\DateTimeImmutable::class), | ||||
|             Argument::type('int'), | ||||
|             Argument::exact(0) | ||||
|         )->will(static fn ($args) => array_fill(0, 1, new Calendar()))->shouldBeCalledTimes(1); | ||||
|         )->will(static fn ($args) => array_fill(0, 10, new Calendar()))->shouldBeCalledTimes(1); | ||||
|         $calendarRepository->findByNotificationAvailable( | ||||
|             Argument::type(\DateTimeImmutable::class), | ||||
|             Argument::type(\DateTimeImmutable::class), | ||||
|             Argument::type('int'), | ||||
|             Argument::not(0) | ||||
|             Argument::exact(10) | ||||
|         )->will(static fn ($args) => [])->shouldBeCalledTimes(1); | ||||
|  | ||||
|         $em = $this->prophesize(EntityManagerInterface::class); | ||||
|         $em->clear()->shouldBeCalled(); | ||||
|  | ||||
|         $calendarRangeGenerator = $this->prophesize(RangeGeneratorInterface::class); | ||||
|         $calendarRangeGenerator->generateRange(Argument::any())->willReturn([ | ||||
|             'startDate' => new \DateTimeImmutable('yesterday'), | ||||
|             'endDate' => new \DateTimeImmutable('now'), | ||||
|         ]); | ||||
|  | ||||
|         $provider = new CalendarForShortMessageProvider( | ||||
|             $calendarRepository->reveal(), | ||||
|             $em->reveal(), | ||||
|             new DefaultRangeGenerator() | ||||
|             $calendarRangeGenerator->reveal(), | ||||
|         ); | ||||
|  | ||||
|         $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now'))); | ||||
|  | ||||
|         $this->assertEquals(1, \count($calendars)); | ||||
|         $this->assertEquals(10, \count($calendars)); | ||||
|         $this->assertContainsOnly(Calendar::class, $calendars); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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]); | ||||
|     } | ||||
| } | ||||
| @@ -127,3 +127,7 @@ services: | ||||
|         factory: ["@doctrine", getRepository] | ||||
|         arguments: | ||||
|             - "Chill\\CustomFieldsBundle\\Entity\\CustomFieldLongChoice\\Option" | ||||
|  | ||||
|     Chill\CustomFieldsBundle\EntityRepository\CustomFieldsDefaultGroupRepository: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -59,7 +59,7 @@ final readonly class StoredObjectVersionApiController | ||||
|  | ||||
|         return new JsonResponse( | ||||
|             $this->serializer->serialize( | ||||
|                 new Collection($items, $paginator), | ||||
|                 new Collection(array_values($items->toArray()), $paginator), | ||||
|                 'json', | ||||
|                 [AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]] | ||||
|             ), | ||||
|   | ||||
| @@ -0,0 +1,20 @@ | ||||
| <?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\DocStoreBundle\Exception; | ||||
|  | ||||
| class ConversionWithSameMimeTypeException extends \RuntimeException | ||||
| { | ||||
|     public function __construct(string $mimeType, ?\Throwable $previous = null) | ||||
|     { | ||||
|         parent::__construct("Conversion to same MIME type '{$mimeType}' is not allowed: already at the same MIME type", 0, $previous); | ||||
|     } | ||||
| } | ||||
| @@ -25,7 +25,7 @@ export interface GenericDoc { | ||||
|     type: "doc_store_generic_doc"; | ||||
|     uniqueKey: string; | ||||
|     key: string; | ||||
|     identifiers: object; | ||||
|     identifiers: { id: number }; | ||||
|     context: "person" | "accompanying-period"; | ||||
|     doc_date: DateTime; | ||||
|     metadata: GenericDocMetadata; | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { StoredObject, StoredObjectVersion } from "../../types"; | ||||
| import DropFileWidget from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFileWidget.vue"; | ||||
| import { computed, reactive } from "vue"; | ||||
| import { useToast } from "vue-toast-notification"; | ||||
| import { DOCUMENT_ADD, trans } from "translator"; | ||||
|  | ||||
| interface DropFileConfig { | ||||
|     allowRemove: boolean; | ||||
| @@ -75,11 +76,9 @@ function closeModal(): void { | ||||
|         @click="openModal" | ||||
|         class="btn btn-create" | ||||
|     > | ||||
|         Ajouter un document | ||||
|     </button> | ||||
|     <button v-else @click="openModal" class="btn btn-edit"> | ||||
|         Remplacer le document | ||||
|         {{ trans(DOCUMENT_ADD) }} | ||||
|     </button> | ||||
|     <button v-else @click="openModal" class="btn btn-edit"></button> | ||||
|     <modal | ||||
|         v-if="state.showModal" | ||||
|         :modal-dialog-class="modalClasses" | ||||
|   | ||||
| @@ -3,9 +3,9 @@ import { | ||||
|     StoredObject, | ||||
|     StoredObjectPointInTime, | ||||
|     StoredObjectVersionWithPointInTime, | ||||
| } from "./../../../types"; | ||||
| } from "ChillDocStoreAssets/types"; | ||||
| import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue"; | ||||
| import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date"; | ||||
| import { ISOToDatetime } from "ChillMainAssets/chill/js/date"; | ||||
| import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue"; | ||||
| import RestoreVersionButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue"; | ||||
| import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DownloadButton.vue"; | ||||
|   | ||||
| @@ -46,6 +46,16 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface | ||||
|  | ||||
|     public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool | ||||
|     { | ||||
|         // we first try to get the permission from the workflow, as attachement (this is the less intensive query) | ||||
|         $workflowPermissionAsAttachment = match ($attribute) { | ||||
|             StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($subject), | ||||
|             StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($subject), | ||||
|         }; | ||||
|  | ||||
|         if (WorkflowRelatedEntityPermissionHelper::FORCE_DENIED === $workflowPermissionAsAttachment) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Retrieve the related entity | ||||
|         $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject); | ||||
|  | ||||
| @@ -66,7 +76,7 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface | ||||
|         return match ($workflowPermission) { | ||||
|             WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true, | ||||
|             WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false, | ||||
|             WorkflowRelatedEntityPermissionHelper::ABSTAIN => $regularPermission, | ||||
|             WorkflowRelatedEntityPermissionHelper::ABSTAIN => WorkflowRelatedEntityPermissionHelper::FORCE_GRANT === $workflowPermissionAsAttachment || $regularPermission, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,12 @@ namespace Chill\DocStoreBundle\Security\Authorization; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||||
|  | ||||
| /** | ||||
|  * Interface for voting on stored object permissions. | ||||
|  * | ||||
|  * Each time a stored object is attached to a document, the voter is responsible for determining | ||||
|  * whether the user has the necessary permissions to access or modify the stored object. | ||||
|  */ | ||||
| interface StoredObjectVoterInterface | ||||
| { | ||||
|     public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool; | ||||
|   | ||||
| @@ -15,6 +15,7 @@ use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\DocStoreBundle\Entity\StoredObjectPointInTime; | ||||
| use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum; | ||||
| use Chill\DocStoreBundle\Entity\StoredObjectVersion; | ||||
| use Chill\DocStoreBundle\Exception\ConversionWithSameMimeTypeException; | ||||
| use Chill\DocStoreBundle\Exception\StoredObjectManagerException; | ||||
| use Chill\WopiBundle\Service\WopiConverter; | ||||
| use Symfony\Component\Mime\MimeTypesInterface; | ||||
| @@ -41,9 +42,10 @@ class StoredObjectToPdfConverter | ||||
|      * | ||||
|      * @return array{0: StoredObjectPointInTime, 1: StoredObjectVersion, 2?: string} contains the point in time before conversion and the new version of the stored object. The converted content is included in the response if $includeConvertedContent is true | ||||
|      * | ||||
|      * @throws \UnexpectedValueException    if the preferred mime type for the conversion is not found | ||||
|      * @throws \RuntimeException            if the conversion or storage of the new version fails | ||||
|      * @throws \UnexpectedValueException           if the preferred mime type for the conversion is not found | ||||
|      * @throws \RuntimeException                   if the conversion or storage of the new version fails | ||||
|      * @throws StoredObjectManagerException | ||||
|      * @throws ConversionWithSameMimeTypeException if the document has already the same mime type79* | ||||
|      */ | ||||
|     public function addConvertedVersion(StoredObject $storedObject, string $lang, $convertTo = 'pdf', bool $includeConvertedContent = false): array | ||||
|     { | ||||
| @@ -56,7 +58,7 @@ class StoredObjectToPdfConverter | ||||
|         $currentVersion = $storedObject->getCurrentVersion(); | ||||
|  | ||||
|         if ($currentVersion->getType() === $newMimeType) { | ||||
|             throw new \UnexpectedValueException('Already at the same mime type'); | ||||
|             throw new ConversionWithSameMimeTypeException($newMimeType); | ||||
|         } | ||||
|  | ||||
|         $content = $this->storedObjectManager->read($currentVersion); | ||||
|   | ||||
| @@ -40,6 +40,10 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase | ||||
|             $storedObject->registerVersion(); | ||||
|         } | ||||
|  | ||||
|         // remove one version in the history | ||||
|         $v5 = $storedObject->getVersions()->get(5); | ||||
|         $storedObject->removeVersion($v5); | ||||
|  | ||||
|         $security = $this->prophesize(Security::class); | ||||
|         $security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject) | ||||
|             ->willReturn(true) | ||||
| @@ -53,6 +57,7 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase | ||||
|         self::assertEquals($response->getStatusCode(), 200); | ||||
|         self::assertIsArray($body); | ||||
|         self::assertArrayHasKey('results', $body); | ||||
|         self::assertIsList($body['results']); | ||||
|         self::assertCount(10, $body['results']); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -86,9 +86,165 @@ class AbstractStoredObjectVoterTest extends TestCase | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @dataProvider dataProviderVoteOnAttribute | ||||
|      * @dataProvider dataProviderVoteOnAttributeWithStoredObjectPermission | ||||
|      */ | ||||
|     public function testVoteOnAttribute( | ||||
|     public function testVoteOnAttributeWithStoredObjectPermission( | ||||
|         StoredObjectRoleEnum $attribute, | ||||
|         bool $expected, | ||||
|         bool $isGrantedRegularPermission, | ||||
|         string $isGrantedWorkflowPermission, | ||||
|         string $isGrantedStoredObjectAttachment, | ||||
|     ): void { | ||||
|         $storedObject = new StoredObject(); | ||||
|         $repository = new DummyRepository($related = new \stdClass()); | ||||
|         $token = new UsernamePasswordToken(new User(), 'dummy'); | ||||
|  | ||||
|         $security = $this->prophesize(Security::class); | ||||
|         $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); | ||||
|  | ||||
|         $workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class); | ||||
|  | ||||
|         $security = $this->prophesize(Security::class); | ||||
|         $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); | ||||
|  | ||||
|         if (StoredObjectRoleEnum::SEE === $attribute) { | ||||
|             $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject) | ||||
|                 ->shouldBeCalled() | ||||
|                 ->willReturn($isGrantedStoredObjectAttachment); | ||||
|             $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related) | ||||
|                 ->willReturn($isGrantedWorkflowPermission); | ||||
|         } elseif (StoredObjectRoleEnum::EDIT === $attribute) { | ||||
|             $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject) | ||||
|                 ->shouldBeCalled() | ||||
|                 ->willReturn($isGrantedStoredObjectAttachment); | ||||
|             $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($related) | ||||
|                 ->willReturn($isGrantedWorkflowPermission); | ||||
|         } else { | ||||
|             throw new \LogicException('Invalid attribute for StoredObjectVoter'); | ||||
|         } | ||||
|  | ||||
|         $storedObjectVoter = new class ($repository, $workflowRelatedEntityPermissionHelper->reveal(), $security->reveal()) extends AbstractStoredObjectVoter { | ||||
|             public function __construct(private $repository, $helper, $security) | ||||
|             { | ||||
|                 parent::__construct($security, $helper); | ||||
|             } | ||||
|  | ||||
|             protected function getRepository(): AssociatedEntityToStoredObjectInterface | ||||
|             { | ||||
|                 return $this->repository; | ||||
|             } | ||||
|  | ||||
|             protected function getClass(): string | ||||
|             { | ||||
|                 return \stdClass::class; | ||||
|             } | ||||
|  | ||||
|             protected function attributeToRole(StoredObjectRoleEnum $attribute): string | ||||
|             { | ||||
|                 return 'SOME_ROLE'; | ||||
|             } | ||||
|  | ||||
|             protected function canBeAssociatedWithWorkflow(): bool | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         $actual = $storedObjectVoter->voteOnAttribute($attribute, $storedObject, $token); | ||||
|  | ||||
|         self::assertEquals($expected, $actual); | ||||
|     } | ||||
|  | ||||
|     public static function dataProviderVoteOnAttributeWithStoredObjectPermission(): iterable | ||||
|     { | ||||
|         foreach (['read' => StoredObjectRoleEnum::SEE, 'write' => StoredObjectRoleEnum::EDIT] as $action => $attribute) { | ||||
|             yield 'Not related to any workflow nor attachment ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 true, | ||||
|                 true, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Not related to any workflow nor attachment (refuse) ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 false, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Is granted by a workflow takes precedence (workflow) ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 true, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Is granted by a workflow takes precedence (stored object) ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 true, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Is granted by a workflow takes precedence (workflow) although grant ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 true, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Is granted by a workflow takes precedence (stored object) although grant ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 true, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Is granted by a workflow takes precedence (initially refused) (workflow) although grant ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 false, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Is granted by a workflow takes precedence (initially refused) (stored object) although grant ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 false, | ||||
|                 false, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Force grant inverse the regular permission (workflow) ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 true, | ||||
|                 false, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|             ]; | ||||
|  | ||||
|             yield 'Force grant inverse the regular permission (so) ('.$action.')' => [ | ||||
|                 $attribute, | ||||
|                 true, | ||||
|                 false, | ||||
|                 WorkflowRelatedEntityPermissionHelper::ABSTAIN, | ||||
|                 WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, | ||||
|             ]; | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @dataProvider dataProviderVoteOnAttributeWithoutStoredObjectPermission | ||||
|      */ | ||||
|     public function testVoteOnAttributeWithoutStoredObjectPermission( | ||||
|         StoredObjectRoleEnum $attribute, | ||||
|         bool $expected, | ||||
|         bool $canBeAssociatedWithWorkflow, | ||||
| @@ -105,6 +261,10 @@ class AbstractStoredObjectVoterTest extends TestCase | ||||
|         $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); | ||||
|  | ||||
|         $workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class); | ||||
|  | ||||
|         $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN); | ||||
|         $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN); | ||||
|  | ||||
|         if (null !== $isGrantedWorkflowPermissionRead) { | ||||
|             $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related) | ||||
|                 ->willReturn($isGrantedWorkflowPermissionRead)->shouldBeCalled(); | ||||
| @@ -123,7 +283,7 @@ class AbstractStoredObjectVoterTest extends TestCase | ||||
|         self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message); | ||||
|     } | ||||
|  | ||||
|     public static function dataProviderVoteOnAttribute(): iterable | ||||
|     public static function dataProviderVoteOnAttributeWithoutStoredObjectPermission(): iterable | ||||
|     { | ||||
|         // not associated on a workflow | ||||
|         yield [StoredObjectRoleEnum::SEE, true, false, true, null, null, 'not associated on a workflow, granted by regular access, must not rely on helper']; | ||||
|   | ||||
| @@ -23,6 +23,8 @@ See the document: Voir le document | ||||
|  | ||||
| document: | ||||
|     Any title: Aucun titre | ||||
|     replace: Remplacer | ||||
|     Add: Ajouter un document | ||||
|  | ||||
| generic_doc: | ||||
|     filter: | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -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', [ | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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'); | ||||
|     } | ||||
| } | ||||
| @@ -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(), | ||||
|             ]); | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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', | ||||
|                         ], | ||||
|                     ], | ||||
|                 ], | ||||
|             ], | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										18
									
								
								src/Bundle/ChillEventBundle/Entity/BudgetTypeEnum.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Bundle/ChillEventBundle/Entity/BudgetTypeEnum.php
									
									
									
									
									
										Normal 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'; | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
							
								
								
									
										103
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetElement.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetElement.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										78
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetKind.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetKind.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										158
									
								
								src/Bundle/ChillEventBundle/Entity/EventTheme.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/Bundle/ChillEventBundle/Entity/EventTheme.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										377
									
								
								src/Bundle/ChillEventBundle/Export/Export/ListEvents.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								src/Bundle/ChillEventBundle/Export/Export/ListEvents.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
| @@ -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, | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										53
									
								
								src/Bundle/ChillEventBundle/Form/EventBudgetKindType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/Bundle/ChillEventBundle/Form/EventBudgetKindType.php
									
									
									
									
									
										Normal 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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										67
									
								
								src/Bundle/ChillEventBundle/Form/EventThemeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/Bundle/ChillEventBundle/Form/EventThemeType.php
									
									
									
									
									
										Normal 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']); | ||||
|     } | ||||
| } | ||||
| @@ -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'; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										45
									
								
								src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|     { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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); | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|     } | ||||
| } | ||||
| @@ -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(); | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
| } | ||||
|   | ||||
							
								
								
									
										14
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue
									
									
									
									
									
										Normal 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> | ||||
| @@ -0,0 +1,6 @@ | ||||
| import { createApp } from "vue"; | ||||
|  | ||||
| import App from "./App.vue"; | ||||
| import store from "./store"; | ||||
|  | ||||
| createApp(App).use(store).mount("#event"); | ||||
							
								
								
									
										76
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js
									
									
									
									
									
										Normal 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; | ||||
| @@ -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 %} | ||||
| @@ -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> </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 %} | ||||
| @@ -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 %} | ||||
| @@ -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 %} | ||||
| @@ -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> </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 %} | ||||
| @@ -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 %} | ||||
| @@ -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> | ||||
| @@ -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 | ||||
|         } | ||||
|     ) }} | ||||
|   | ||||
| @@ -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) }} | ||||
|  | ||||
|   | ||||
| @@ -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 %} #} | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 %} | ||||
|   | ||||
| @@ -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 }} : </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" | ||||
|   | ||||
| @@ -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> </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> </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 %} | ||||
|   | ||||
| @@ -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 | ||||
|         } | ||||
|     ) }} | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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(); | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|     } | ||||
| } | ||||
| @@ -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", | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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' | ||||
|   | ||||
| @@ -0,0 +1,4 @@ | ||||
| services: | ||||
|     _defaults: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user