mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-04 03:08:25 +00:00 
			
		
		
		
	Compare commits
	
		
			110 Commits
		
	
	
		
			move-docum
			...
			456-doc-ge
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 24a66e4985 | |||
| bf38ec22c9 | |||
| 9c2abb2dfa | |||
| 94744b9542 | |||
| f42bb498e4 | |||
| 01889ac671 | |||
| 62e5842311 | |||
| 
						
						
							
						
						8ad6f397a8
	
				 | 
					
					
						|||
| d713704633 | |||
| b1fa9242a0 | |||
| 6ac554f93a | |||
| 372d8e5825 | |||
| 10f05e5559 | |||
| ddb2a65419 | |||
| 8d40a8089f | |||
| e1bf4a24d2 | |||
| 208a378185 | |||
| 9089c8959b | |||
| 
						
						
							
						
						1b9b581c31
	
				 | 
					
					
						|||
| aa1abe4c88 | |||
| d82c9cc9a7 | |||
| a7e3b1c5d2 | |||
| 84cf11933d | |||
| 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 | 
							
								
								
									
										7
									
								
								.changes/unreleased/DX-20251027-150053.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.changes/unreleased/DX-20251027-150053.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
kind: DX
 | 
			
		||||
body: |
 | 
			
		||||
    Send notifications log to dedicated channel, if it exists
 | 
			
		||||
time: 2025-10-27T15:00:53.309372316+01:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Allow the merge of two accompanying period works
 | 
			
		||||
time: 2025-02-11T14:22:43.134106669+01:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "359"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Duplication of a document to another accompanying period work evaluation
 | 
			
		||||
time: 2025-04-03T10:03:11.796736107+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "369"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Fusion of two accompanying period works
 | 
			
		||||
time: 2025-04-03T10:08:57.25079018+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "359"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Add filter to social actions list to filter out actions where current user intervenes
 | 
			
		||||
time: 2025-07-17T11:08:50.128269232+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "400"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Show filters on list pages unfolded by default
 | 
			
		||||
time: 2025-07-22T15:50:39.338057044+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "399"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: adjust display logic for accompanying period dates, include closing date if period is closed.
 | 
			
		||||
time: 2025-08-06T13:46:09.241584292+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "382"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: Fixed
 | 
			
		||||
body: add min and step attributes to integer field in DateIntervalType
 | 
			
		||||
time: 2025-08-06T17:35:27.413787704+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "384"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
kind: UX
 | 
			
		||||
body: Limit display of participations in event list
 | 
			
		||||
time: 2025-07-22T13:26:37.500656935+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/UX-20251103-161910.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/UX-20251103-161910.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: UX
 | 
			
		||||
body: Display whether doc generation template is active or not in admin and order templates alphabetically
 | 
			
		||||
time: 2025-11-03T16:19:10.051947925+01:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: "456"
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
							
								
								
									
										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   
 | 
			
		||||
							
								
								
									
										14
									
								
								.changes/v4.6.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.changes/v4.6.0.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
## v4.6.0 - 2025-10-15
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed   
 | 
			
		||||
* ([#394](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/394)) Only show active workflow on the page "my tracked workflow"   
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix loading of classLists in SocialIssuesAcc.vue, ensure elements are present   
 | 
			
		||||
* Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted   
 | 
			
		||||
* ([#434](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/434)) Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists   
 | 
			
		||||
* Fix loading of social issues and social actions within vue component   
 | 
			
		||||
* ([#446](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/446)) Add unique condition on stored object filename, with cleaning step on existing duplicate filenames   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
 | 
			
		||||
* [workflow] take permissions into account to delete the workflow attachment   
 | 
			
		||||
* ([#448](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/448)) Fix the execution of daily cronjob notification, when the previous last execution storage was invalid   
 | 
			
		||||
							
								
								
									
										3
									
								
								.changes/v4.6.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.changes/v4.6.1.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
## v4.6.1 - 2025-10-27
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php   
 | 
			
		||||
@@ -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,7 +183,7 @@ Once created the, comment's classes should be removed and a description of the c
 | 
			
		||||
 | 
			
		||||
When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of
 | 
			
		||||
`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities,
 | 
			
		||||
where injection does not work when restoring an entity from database, but usually possible in services.
 | 
			
		||||
where injection does not work when restoring an entity from a database, but usually possible in services.
 | 
			
		||||
 | 
			
		||||
In test, we use `\Symfony\Component\Clock\MockClock` which is an implementation of `Symfony\Component\Clock\ClockInterface`
 | 
			
		||||
where we have full and easy control of the date.
 | 
			
		||||
@@ -198,9 +198,9 @@ The project uses PHPUnit for testing. Each bundle has its own test suite, and th
 | 
			
		||||
 | 
			
		||||
For creating mock, we prefer using prophecy (library phpspec/prophecy).
 | 
			
		||||
 | 
			
		||||
##### Useful helpers and tips that avoid create a mock
 | 
			
		||||
##### Useful helpers and tips that avoid creating a mock
 | 
			
		||||
 | 
			
		||||
Some notable implementations that are tests helper, and avoid to create a mock:
 | 
			
		||||
Some notable implementations that are test helpers and avoid creating a mock:
 | 
			
		||||
 | 
			
		||||
- `\Psr\Log\NullLogger`, an implementation of `\Psr\Log\LoggerInterface`;
 | 
			
		||||
- `\Symfony\Component\Clock\MockClock`, an implementation of `Symfony\Component\Clock\ClockInterface` (already mentioned above);
 | 
			
		||||
@@ -240,9 +240,6 @@ The tests are run from the project's root (not from the bundle's root).
 | 
			
		||||
# Run all tests
 | 
			
		||||
vendor/bin/phpunit
 | 
			
		||||
 | 
			
		||||
# Run tests for a specific bundle
 | 
			
		||||
vendor/bin/phpunit --testsuite NameBundle
 | 
			
		||||
 | 
			
		||||
# Run a specific test file
 | 
			
		||||
vendor/bin/phpunit path/to/TestFile.php
 | 
			
		||||
 | 
			
		||||
@@ -250,6 +247,9 @@ vendor/bin/phpunit path/to/TestFile.php
 | 
			
		||||
vendor/bin/phpunit --filter methodName path/to/TestFile.php
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
When writing tests, only test specific files. Do not run all tests or the full
 | 
			
		||||
test suite.
 | 
			
		||||
 | 
			
		||||
#### Test Structure
 | 
			
		||||
 | 
			
		||||
Tests are organized by bundle and follow the same structure as the bundle itself:
 | 
			
		||||
@@ -297,7 +297,7 @@ class TicketTest extends TestCase
 | 
			
		||||
 | 
			
		||||
#### Test Database
 | 
			
		||||
 | 
			
		||||
For tests that require a database, the project uses postgresql database filled by fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
 | 
			
		||||
For tests that require a database, the project uses a postgresql database filled with fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file.
 | 
			
		||||
 | 
			
		||||
### Code Quality Tools
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										97
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -6,6 +6,103 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
 | 
			
		||||
and is generated by [Changie](https://github.com/miniscruff/changie).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v4.6.1 - 2025-10-27
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php   
 | 
			
		||||
 | 
			
		||||
## v4.6.0 - 2025-10-15
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed   
 | 
			
		||||
* ([#394](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/394)) Only show active workflow on the page "my tracked workflow"   
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix loading of classLists in SocialIssuesAcc.vue, ensure elements are present   
 | 
			
		||||
* Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted   
 | 
			
		||||
* ([#434](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/434)) Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists   
 | 
			
		||||
* Fix loading of social issues and social actions within vue component   
 | 
			
		||||
* ([#446](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/446)) Add unique condition on stored object filename, with cleaning step on existing duplicate filenames   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
 | 
			
		||||
* [workflow] take permissions into account to delete the workflow attachment   
 | 
			
		||||
* ([#448](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/448)) Fix the execution of daily cronjob notification, when the previous last execution storage was invalid   
 | 
			
		||||
 | 
			
		||||
## 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   
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
        "ext-openssl": "*",
 | 
			
		||||
        "ext-redis": "*",
 | 
			
		||||
        "ext-zlib": "*",
 | 
			
		||||
        "champs-libres/wopi-bundle": "dev-master@dev",
 | 
			
		||||
        "champs-libres/wopi-bundle": "dev-master#1be045ee95310d2037683859ecefdbf3a10f7be6 as 0.4.x-dev",
 | 
			
		||||
        "champs-libres/wopi-lib": "dev-master@dev",
 | 
			
		||||
        "doctrine/data-fixtures": "^1.8",
 | 
			
		||||
        "doctrine/doctrine-bundle": "^2.1",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,13 @@
 | 
			
		||||
chill_main:
 | 
			
		||||
    available_languages: [ '%env(resolve:LOCALE)%', 'en' ]
 | 
			
		||||
    available_countries: ['BE', 'FR']
 | 
			
		||||
    top_banner:
 | 
			
		||||
        visible: false
 | 
			
		||||
        text:
 | 
			
		||||
            fr: 'Vous travaillez actuellement avec la version de PRÉ-PRODUCTION.'
 | 
			
		||||
            nl: 'Je werkt momenteel in de PRE-PRODUCTIE versie'
 | 
			
		||||
        color: '#353535'
 | 
			
		||||
        background_color: '#d8bb48'
 | 
			
		||||
    notifications:
 | 
			
		||||
        from_email: '%env(resolve:NOTIFICATION_FROM_EMAIL)%'
 | 
			
		||||
        from_name: '%env(resolve:NOTIFICATION_FROM_NAME)%'
 | 
			
		||||
 
 | 
			
		||||
@@ -55,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",
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
 | 
			
		||||
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [];
 | 
			
		||||
        return [
 | 
			
		||||
            'reasons' => [],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,8 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void
 | 
			
		||||
    {
 | 
			
		||||
        error_log('alterQuery called with data: '.json_encode(array_keys($data)));
 | 
			
		||||
 | 
			
		||||
        // create a subquery for activity
 | 
			
		||||
        $sqb = $qb->getEntityManager()->createQueryBuilder();
 | 
			
		||||
        $sqb->select('1')
 | 
			
		||||
@@ -59,7 +61,6 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
        if (\in_array('activity', $qb->getAllAliases(), true)) {
 | 
			
		||||
            $sqb->andWhere('activity_person_having_activity.id = activity.id');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($data['reasons']) && [] !== $data['reasons']) {
 | 
			
		||||
            // add clause activity reason
 | 
			
		||||
            $sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
 | 
			
		||||
@@ -124,12 +125,38 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function normalizeFormData(array $formData): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['date_from_rolling' => $formData['date_from_rolling']->normalize(), 'date_to_rolling' => $formData['date_to_rolling']->normalize()];
 | 
			
		||||
        $normalized = [
 | 
			
		||||
            'date_from_rolling' => $formData['date_from_rolling']->normalize(),
 | 
			
		||||
            'date_to_rolling' => $formData['date_to_rolling']->normalize(),
 | 
			
		||||
            'reasons' => [],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (isset($formData['reasons']) && [] !== $formData['reasons']) {
 | 
			
		||||
            $normalized['reasons'] = array_map(
 | 
			
		||||
                fn (ActivityReason $reason) => $reason->getId(),
 | 
			
		||||
                $formData['reasons']
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $normalized;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalizeFormData(array $formData, int $fromVersion): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']), 'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling'])];
 | 
			
		||||
        $denormalized = [
 | 
			
		||||
            'date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']),
 | 
			
		||||
            'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling']),
 | 
			
		||||
            'reasons' => [],
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (isset($formData['reasons']) && [] !== $formData['reasons']) {
 | 
			
		||||
            $denormalized['reasons'] = array_map(
 | 
			
		||||
                fn ($id) => $this->activityReasonRepository->find($id),
 | 
			
		||||
                $formData['reasons']
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $denormalized;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
@@ -143,10 +170,12 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function describeAction($data, ExportGenerationContext $context): array
 | 
			
		||||
    {
 | 
			
		||||
        $reasons = $data['reasons'] ?? [];
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            [] === $data['reasons'] ?
 | 
			
		||||
                'export.filter.person_between_dates.describe_action_with_no_subject'
 | 
			
		||||
                : 'export.filter.person_between_dates.describe_action_with_subject',
 | 
			
		||||
            [] === $reasons ?
 | 
			
		||||
                'export.filter.activity.describe_action_with_no_subject'
 | 
			
		||||
                : 'export.filter.activity.describe_action_with_subject',
 | 
			
		||||
            [
 | 
			
		||||
                'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
 | 
			
		||||
                'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
 | 
			
		||||
@@ -154,7 +183,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
                    ', ',
 | 
			
		||||
                    array_map(
 | 
			
		||||
                        fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
 | 
			
		||||
                        $data['reasons']
 | 
			
		||||
                        $reasons
 | 
			
		||||
                    )
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
@@ -168,6 +197,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
 | 
			
		||||
 | 
			
		||||
    public function validateForm($data, ExecutionContextInterface $context): void
 | 
			
		||||
    {
 | 
			
		||||
        error_log('validateForm called with data: '.json_encode(array_keys($data)));
 | 
			
		||||
        if ($this->rollingDateConverter->convert($data['date_from_rolling'])
 | 
			
		||||
            >= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
 | 
			
		||||
            $context->buildViolation('export.filter.activity.person_between_dates.date mismatch')
 | 
			
		||||
 
 | 
			
		||||
@@ -136,8 +136,14 @@ export default {
 | 
			
		||||
            issueIsLoading: false,
 | 
			
		||||
            actionIsLoading: false,
 | 
			
		||||
            actionAreLoaded: false,
 | 
			
		||||
            socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
 | 
			
		||||
            socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
 | 
			
		||||
            socialIssuesClassList: {
 | 
			
		||||
                "col-form-label": true,
 | 
			
		||||
                required: false,
 | 
			
		||||
            },
 | 
			
		||||
            socialActionsClassList: {
 | 
			
		||||
                "col-form-label": true,
 | 
			
		||||
                required: false,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    },
 | 
			
		||||
    computed: {
 | 
			
		||||
@@ -158,6 +164,21 @@ export default {
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    mounted() {
 | 
			
		||||
        /* Load classNames after element is present */
 | 
			
		||||
        const socialActionsEl = document.querySelector(
 | 
			
		||||
            "input#chill_activitybundle_activity_socialActions",
 | 
			
		||||
        );
 | 
			
		||||
        if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
 | 
			
		||||
            this.socialActionsClassList.required = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const socialIssuesEl = document.querySelector(
 | 
			
		||||
            "input#chill_activitybundle_activity_socialIssues",
 | 
			
		||||
        );
 | 
			
		||||
        if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
 | 
			
		||||
            this.socialIssuesClassList.required = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* Load other issues in multiselect */
 | 
			
		||||
        this.issueIsLoading = true;
 | 
			
		||||
        this.actionAreLoaded = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -55,5 +55,6 @@
 | 
			
		||||
			</dl>
 | 
			
		||||
 | 
			
		||||
		{% endblock %}
 | 
			
		||||
        {% block content_view_actions_duplicate_link %}{% endblock %}
 | 
			
		||||
	{% endembed %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>({
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -25,12 +25,24 @@
 | 
			
		||||
                            <div class="item-bloc">
 | 
			
		||||
                                <div class="item-row">
 | 
			
		||||
                                    <div class="item-col" style="flex-basis:100%;">
 | 
			
		||||
                                        <h2>{{ entity.name|localize_translatable_string }}</h2>
 | 
			
		||||
                                        <h2>{{ entity.name|localize_translatable_string }} </h2>
 | 
			
		||||
                                        <p style="margin-left: 1rem;"><span class="badge bg-chill-gray">
 | 
			
		||||
                                            {% if entity.active %}
 | 
			
		||||
                                                {{ 'admin.active'|trans }}
 | 
			
		||||
                                            {% else %}
 | 
			
		||||
                                                {{ 'admin.not active'|trans }}
 | 
			
		||||
                                            {% endif %}
 | 
			
		||||
                                        </span></p>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="item-row">
 | 
			
		||||
                                    <p><span class="badge bg-chill-green-dark">{{ contextManager.getContextByKey(entity.context).name|trans }}</span></p>
 | 
			
		||||
                                </div>
 | 
			
		||||
{#                                <div class="item-row">#}
 | 
			
		||||
{#                                    <div class="item-col" style="flex-basis:100%;">#}
 | 
			
		||||
{##}
 | 
			
		||||
{#                                    </div>#}
 | 
			
		||||
{#                                </div>#}
 | 
			
		||||
                                <div class="item-row">
 | 
			
		||||
                                    <div class="item-col"></div>
 | 
			
		||||
                                    <ul class="record_actions item-col flex-shrink-1">
 | 
			
		||||
 
 | 
			
		||||
@@ -49,3 +49,7 @@ crud:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Template file: Fichier modèle
 | 
			
		||||
 | 
			
		||||
admin:
 | 
			
		||||
    active: Actif
 | 
			
		||||
    not active: Non-actif
 | 
			
		||||
 
 | 
			
		||||
@@ -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]]
 | 
			
		||||
            ),
 | 
			
		||||
 
 | 
			
		||||
@@ -23,10 +23,14 @@ use Random\RandomException;
 | 
			
		||||
 * Store each version of StoredObject's.
 | 
			
		||||
 *
 | 
			
		||||
 * A version should not be created manually: use the method @see{StoredObject::registerVersion} instead.
 | 
			
		||||
 *
 | 
			
		||||
 * Each filename must be unique within the same StoredObject. We add a condition on id to apply this condition only for
 | 
			
		||||
 * newly created versions when this new index is applied.
 | 
			
		||||
 */
 | 
			
		||||
#[ORM\Entity]
 | 
			
		||||
#[ORM\Table('chill_doc.stored_object_version')]
 | 
			
		||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_object', columns: ['stored_object_id', 'version'])]
 | 
			
		||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_filename', columns: ['filename'], options: ['where' => '(id > 0)'])]
 | 
			
		||||
class StoredObjectVersion implements TrackCreationInterface
 | 
			
		||||
{
 | 
			
		||||
    use TrackCreationTrait;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -36,6 +36,18 @@ export interface GenericDocForAccompanyingPeriod extends GenericDoc {
 | 
			
		||||
    context: "accompanying-period";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isGenericDocForAccompanyingPeriod(
 | 
			
		||||
    doc: GenericDoc,
 | 
			
		||||
): doc is GenericDocForAccompanyingPeriod {
 | 
			
		||||
    return doc.context === "accompanying-period";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isGenericDocWithStoredObject(
 | 
			
		||||
    doc: GenericDoc,
 | 
			
		||||
): doc is GenericDoc & { storedObject: StoredObject } {
 | 
			
		||||
    return doc.storedObject !== null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BaseMetadataWithHtml extends BaseMetadata {
 | 
			
		||||
    html: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -44,28 +56,33 @@ export interface GenericDocForAccompanyingCourseDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_course_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseActivityDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_course_activity_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseCalendarDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_course_calendar_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCoursePersonDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "person_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
 | 
			
		||||
    extends GenericDocForAccompanyingPeriod {
 | 
			
		||||
    key: "accompanying_period_work_evaluation_document";
 | 
			
		||||
    metadata: BaseMetadataWithHtml;
 | 
			
		||||
    storedObject: StoredObject;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +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_REPLACE, DOCUMENT_ADD, trans } from "translator";
 | 
			
		||||
import { DOCUMENT_ADD, trans } from "translator";
 | 
			
		||||
 | 
			
		||||
interface DropFileConfig {
 | 
			
		||||
    allowRemove: boolean;
 | 
			
		||||
@@ -78,9 +78,7 @@ function closeModal(): void {
 | 
			
		||||
    >
 | 
			
		||||
        {{ trans(DOCUMENT_ADD) }}
 | 
			
		||||
    </button>
 | 
			
		||||
    <button v-else @click="openModal" class="dropdown-item">
 | 
			
		||||
        {{ trans(DOCUMENT_REPLACE) }}
 | 
			
		||||
    </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'];
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,63 @@
 | 
			
		||||
<?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\DocStore;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
final class Version20251013094414 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'DocStore: Enforce filename uniqueness on chill_doc.stored_object_version; clean duplicates and add partial unique index on filename (for new rows only).';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        // 1) Clean duplicates: for each (stored_object_id, filename, key, iv), keep only the last inserted row
 | 
			
		||||
        //    and delete all others. Use ROW_NUMBER over id DESC to define the last one.
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            WITH ranked AS (
 | 
			
		||||
                SELECT id,
 | 
			
		||||
                       rank() OVER (
 | 
			
		||||
                           PARTITION BY stored_object_id, filename, "key"::jsonb, iv::jsonb
 | 
			
		||||
                           ORDER BY id DESC
 | 
			
		||||
                       ) AS rn
 | 
			
		||||
                FROM chill_doc.stored_object_version
 | 
			
		||||
            )
 | 
			
		||||
            DELETE FROM chill_doc.stored_object_version sov
 | 
			
		||||
            USING ranked r
 | 
			
		||||
            WHERE sov.id = r.id
 | 
			
		||||
              AND r.rn > 1
 | 
			
		||||
        SQL);
 | 
			
		||||
 | 
			
		||||
        // 2) Create a partial unique index on filename that applies only to subsequently inserted rows.
 | 
			
		||||
        //    Per user's instruction, compute the cutoff using the stored_object_id sequence value.
 | 
			
		||||
        $nextVal = (int) $this->connection->fetchOne("SELECT nextval('chill_doc.stored_object_version_id_seq')");
 | 
			
		||||
 | 
			
		||||
        // Safety: if somehow sequence is not available, fallback to current max id from the table
 | 
			
		||||
        if ($nextVal <= 0) {
 | 
			
		||||
            $nextVal = (int) $this->connection->fetchOne('SELECT COALESCE(MAX(id), 0) FROM chill_doc.stored_object_version');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->addSql(sprintf(
 | 
			
		||||
            'CREATE UNIQUE INDEX chill_doc_stored_object_version_unique_by_filename ON chill_doc.stored_object_version (filename) WHERE id > %d',
 | 
			
		||||
            $nextVal
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        // Drop the partial unique index; data cleanup is irreversible.
 | 
			
		||||
        $this->addSql('DROP INDEX IF EXISTS chill_doc_stored_object_version_unique_by_filename');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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,11 +56,12 @@ 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, 5) %} {% include
 | 
			
		||||
                '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
 | 
			
		||||
@@ -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