mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-03 18:58:24 +00:00 
			
		
		
		
	Compare commits
	
		
			127 Commits
		
	
	
		
			ticket/rea
			...
			create-adm
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 19785795ad | |||
| e8450a519d | |||
| c66f0122bf | |||
| 37291b2756 | |||
| a88575463b | |||
| 
						
						
							
						
						6d2e78ce55
	
				 | 
					
					
						|||
| 61ca700bbe | |||
| 
						 | 
					b43aeebc3c | ||
| 056e2dcc5f | |||
| e57d1ac696 | |||
| 0eff1d2e79 | |||
| 3928b2cc7a | |||
| 
						
						
							
						
						4f51ef81ad
	
				 | 
					
					
						|||
| 
						
						
							
						
						4637dc692c
	
				 | 
					
					
						|||
| 38935edb93 | |||
| 
						 | 
					e1ef65d4ca | ||
| ec9d0be70b | |||
| 
						 | 
					0ba2cbc1e8 | ||
| e87429933a | |||
| 
						
						
							
						
						8e2e676e3d
	
				 | 
					
					
						|||
| e12ad563a3 | |||
| 
						 | 
					711aa8db9b | ||
| e78d44953f | |||
| 18f67801c7 | |||
| 
						
						
							
						
						c815e6bc69
	
				 | 
					
					
						|||
| 807f2711fe | |||
| 
						 | 
					cd594cd580 | ||
| ac12b8cdcf | |||
| 9c1611d052 | |||
| 
						
						
							
						
						fb6b26bfb5
	
				 | 
					
					
						|||
| 
						
						
							
						
						c5cedb8bd6
	
				 | 
					
					
						|||
| 
						
						
							
						
						2665e43a61
	
				 | 
					
					
						|||
| 90e3043c3d | |||
| af13bf9088 | |||
| 4aa65d69c7 | |||
| 9e33aec594 | |||
| f88bc7e9f0 | |||
| 
						
						
							
						
						8e78c41549
	
				 | 
					
					
						|||
| 
						
						
							
						
						6e36771349
	
				 | 
					
					
						|||
| 
						
						
							
						
						7a82cae155
	
				 | 
					
					
						|||
| dfab223391 | |||
| 539752485c | |||
| d204df0316 | |||
| 
						 | 
					82c02f442b | ||
| 
						
						
							
						
						25561cdf63
	
				 | 
					
					
						|||
| 10b73e06e1 | |||
| 
						 | 
					e7c04e34a9 | ||
| f32a9dc7bc | |||
| ea06a96f91 | |||
| 76433e2512 | |||
| 1fa464b87a | |||
| 3b75f43e80 | |||
| a40eb95c43 | |||
| 8429c6e693 | |||
| 6db7f6827c | |||
| 3c60c57985 | |||
| 10aa36aae0 | |||
| eed9913a49 | |||
| 1a847d36a0 | |||
| d916962d9b | |||
| 1092fc64ae | |||
| 4e99b6ecbd | |||
| 60d107b541 | |||
| 4c3befe489 | |||
| e176319775 | |||
| 5d810b4230 | |||
| 52b8eea069 | |||
| 4bebeaeaaa | |||
| 3969e12633 | |||
| d60312d4a2 | |||
| d2454ae134 | |||
| 17c2cb1fdc | |||
| 94d7a2a0bb | |||
| aef1efc6cd | |||
| dd0c662c9e | |||
| 6b1696b62e | |||
| c4b760c452 | |||
| 69fe2a8256 | |||
| 8c98242896 | |||
| 7eecfd3882 | |||
| 6713658569 | |||
| 342b786106 | |||
| 80a7437769 | |||
| 8a38ce1a5c | |||
| 5d94bf0556 | |||
| bb71e084b8 | |||
| 27f0bf28e9 | |||
| 383f588795 | |||
| e7a1ff1ac8 | |||
| adc9c47d0a | |||
| e594b65d1e | |||
| c0826bc65c | |||
| 904f4e5ed9 | |||
| 481f82b4c7 | |||
| f5668592ca | |||
| 164beee7c6 | |||
| 
						 | 
					4d96eb9457 | ||
| aa085a1562 | |||
| 2754251fdc | |||
| 2f6cef4238 | |||
| 2309636eae | |||
| 56ec8fb516 | |||
| fe6e6e54c1 | |||
| 2a09594b4a | |||
| 7c798e1f63 | |||
| ab8da4ab7a | |||
| 5bdb2df929 | |||
| e3a6b60fa2 | |||
| fe2eba3b29 | |||
| 
						 | 
					61d1232e31 | ||
| 6594d4f6a6 | |||
| 1a66a9e864 | |||
| 1b74c119dc | |||
| 14d88810f3 | |||
| 445a2c9358 | |||
| c8baf0a8aa | |||
| faed443a96 | |||
| bbf387d96f | |||
| 
						 | 
					b7c9b60744 | ||
| 5f01673404 | |||
| 63d0a52ea1 | |||
| 
						
						
							
						
						2bd303bbbe
	
				 | 
					
					
						|||
| 837089ff5d | |||
| f383fab578 | |||
| f3cc4a89af | |||
| 703f5dc32d | |||
| b870e71f77 | 
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20250904-181032.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20250904-181032.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Add a command to generate a list of permissions
 | 
			
		||||
time: 2025-09-04T18:10:32.334524026+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20251007-155945.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20251007-155945.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Admin interface for Motive entity
 | 
			
		||||
time: 2025-10-07T15:59:45.597029709+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: No schema change
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/unreleased/Feature-20251022-111552.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/unreleased/Feature-20251022-111552.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
kind: Feature
 | 
			
		||||
body: Add an admin interface for Motive entity
 | 
			
		||||
time: 2025-10-22T11:15:52.13937955+02:00
 | 
			
		||||
custom:
 | 
			
		||||
    Issue: ""
 | 
			
		||||
    SchemaChange: Add columns or tables
 | 
			
		||||
							
								
								
									
										4
									
								
								.changes/v4.0.2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.changes/v4.0.2.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
## v4.0.2 - 2025-07-09
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix add missing translation   
 | 
			
		||||
* Fix the transfer of evaluations and documents during of accompanyingperiodwork   
 | 
			
		||||
							
								
								
									
										12
									
								
								.changes/v4.1.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.changes/v4.1.0.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
## v4.1.0 - 2025-08-26
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes   
 | 
			
		||||
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default   
 | 
			
		||||
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Add columns or tables
 | 
			
		||||
### Fixed
 | 
			
		||||
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.   
 | 
			
		||||
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType   
 | 
			
		||||
### UX
 | 
			
		||||
* Limit display of participations in event list   
 | 
			
		||||
							
								
								
									
										10
									
								
								.changes/v4.2.0.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.changes/v4.2.0.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
## v4.2.0 - 2025-09-02
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#64](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/64)) Add external identifier for a Person
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Add columns or tables
 | 
			
		||||
* ([#330](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/330) Allow users to choose for which notifications they want to receive an email
 | 
			
		||||
### Fixed
 | 
			
		||||
* ([#422](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/422)) Fixed html layout of pages for recovering password
 | 
			
		||||
* Fix typo in 'uncheckAll' script for centers selection
 | 
			
		||||
* Fix incorrect parameter name in event details link
 | 
			
		||||
							
								
								
									
										6
									
								
								.changes/v4.2.1.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.changes/v4.2.1.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
## v4.2.1 - 2025-09-03
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix exports to work with DirectExportInterface   
 | 
			
		||||
### DX
 | 
			
		||||
* Improve error message when a stored object cannot be written on local disk
 | 
			
		||||
   
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										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,23 +183,60 @@ Once created the, comment's classes should be removed and a description of the c
 | 
			
		||||
 | 
			
		||||
When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of
 | 
			
		||||
`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities,
 | 
			
		||||
where injection does not work when restoring an entity from database, but usually possible in services.
 | 
			
		||||
where injection does not work when restoring an entity from a database, but usually possible in services.
 | 
			
		||||
 | 
			
		||||
In test, we use `\Symfony\Component\Clock\MockClock` which is an implementation of `Symfony\Component\Clock\ClockInterface`
 | 
			
		||||
where we have full and easy control of the date.
 | 
			
		||||
 | 
			
		||||
### Testing Information
 | 
			
		||||
 | 
			
		||||
The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level.
 | 
			
		||||
 | 
			
		||||
#### Use of mock in tests
 | 
			
		||||
 | 
			
		||||
##### General mocking
 | 
			
		||||
 | 
			
		||||
For creating mock, we prefer using prophecy (library phpspec/prophecy).
 | 
			
		||||
 | 
			
		||||
##### Useful helpers and tips that avoid creating a mock
 | 
			
		||||
 | 
			
		||||
Some notable implementations that are test helpers and avoid creating a mock:
 | 
			
		||||
 | 
			
		||||
- `\Psr\Log\NullLogger`, an implementation of `\Psr\Log\LoggerInterface`;
 | 
			
		||||
- `\Symfony\Component\Clock\MockClock`, an implementation of `Symfony\Component\Clock\ClockInterface` (already mentioned above);
 | 
			
		||||
- `\Symfony\Component\HttpClient\MockHttpClient`, an implementation of `\Symfony\Contracts\HttpClient\HttpClientInterface`;
 | 
			
		||||
- When using `\Symfony\Component\Mailer\MailerInterface`, we can create the mock with "InMemoryTransport":
 | 
			
		||||
 | 
			
		||||
    ```php
 | 
			
		||||
    use Symfony\Component\Mailer\Transport\InMemoryTransport;
 | 
			
		||||
    use \Symfony\Component\Mailer\Mailer;
 | 
			
		||||
 | 
			
		||||
    $transport = new InMemoryTransport();
 | 
			
		||||
    $mailer = new Mailer($transport);
 | 
			
		||||
 | 
			
		||||
    // After sending:
 | 
			
		||||
    $messages = $transport->getSent(); // array of SentMessage
 | 
			
		||||
    ```
 | 
			
		||||
- When using `\Symfony\Contracts\EventDispatcher\EventDispatcherInterface`, we can use directly an instance of `\Symfony\Component\EventDispatcher\EventDispatcher`;
 | 
			
		||||
 | 
			
		||||
##### When we prefer not creating a mock
 | 
			
		||||
 | 
			
		||||
- When we use Doctrine Entities related to the project, we prefer not to use a mock: we instantiate them directly (unless it requires too much code to write);
 | 
			
		||||
 | 
			
		||||
##### Mocking final and readonly classes
 | 
			
		||||
 | 
			
		||||
Classes marked as final can't be mocked. To avoid that, either:
 | 
			
		||||
 | 
			
		||||
- we remove the `final` keyword from the class;
 | 
			
		||||
- we extract an interface from the final class.
 | 
			
		||||
 | 
			
		||||
This must be a decision made by a human, not by an AI. Every AI task must abort with an explicit message in that case.
 | 
			
		||||
 | 
			
		||||
#### Running Tests
 | 
			
		||||
 | 
			
		||||
The tests are run from the project's root (not from the bundle's root: so, do not change the directory to any bundle directory before running tests).
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
@@ -254,7 +291,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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -6,6 +6,42 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
 | 
			
		||||
and is generated by [Changie](https://github.com/miniscruff/changie).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v4.2.1 - 2025-09-03
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix exports to work with DirectExportInterface   
 | 
			
		||||
### DX
 | 
			
		||||
* Improve error message when a stored object cannot be written on local disk
 | 
			
		||||
   
 | 
			
		||||
 | 
			
		||||
## v4.2.0 - 2025-09-02
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#64](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/64)) Add external identifier for a Person
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Add columns or tables
 | 
			
		||||
* ([#330](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/330) Allow users to choose for which notifications they want to receive an email
 | 
			
		||||
### Fixed
 | 
			
		||||
* ([#422](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/422)) Fixed html layout of pages for recovering password
 | 
			
		||||
* Fix typo in 'uncheckAll' script for centers selection
 | 
			
		||||
* Fix incorrect parameter name in event details link
 | 
			
		||||
 | 
			
		||||
## v4.1.0 - 2025-08-26
 | 
			
		||||
### Feature
 | 
			
		||||
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes   
 | 
			
		||||
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default   
 | 
			
		||||
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports   
 | 
			
		||||
 | 
			
		||||
  **Schema Change**: Add columns or tables
 | 
			
		||||
### Fixed
 | 
			
		||||
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.   
 | 
			
		||||
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType   
 | 
			
		||||
### UX
 | 
			
		||||
* Limit display of participations in event list   
 | 
			
		||||
 | 
			
		||||
## v4.0.2 - 2025-07-09
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix add missing translation   
 | 
			
		||||
* Fix the transfer of evaluations and documents during of accompanyingperiodwork   
 | 
			
		||||
 | 
			
		||||
## v4.0.1 - 2025-07-08
 | 
			
		||||
### Fixed
 | 
			
		||||
* Fix package.json for compilation
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,12 @@
 | 
			
		||||
import { trans, setLocale, setLocaleFallbacks } from "./ux-translator";
 | 
			
		||||
import {
 | 
			
		||||
  trans,
 | 
			
		||||
  setLocale,
 | 
			
		||||
  getLocale,
 | 
			
		||||
  setLocaleFallbacks,
 | 
			
		||||
} from "./ux-translator";
 | 
			
		||||
 | 
			
		||||
setLocaleFallbacks({"en": "fr", "nl": "fr", "fr": "en"});
 | 
			
		||||
setLocale('fr');
 | 
			
		||||
setLocaleFallbacks({ en: "fr", nl: "fr", fr: "en" });
 | 
			
		||||
setLocale("fr");
 | 
			
		||||
 | 
			
		||||
export { trans };
 | 
			
		||||
export * from '../var/translations';
 | 
			
		||||
export { trans, getLocale };
 | 
			
		||||
export * from "../var/translations";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
chill_doc_store:
 | 
			
		||||
    use_driver: openstack
 | 
			
		||||
    use_driver: local_storage
 | 
			
		||||
    local_storage:
 | 
			
		||||
        storage_path: '%kernel.project_dir%/var/storage'
 | 
			
		||||
    openstack:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
chill_ticket:
 | 
			
		||||
    ticket:
 | 
			
		||||
        person_per_ticket:    one # One of "one"; "many"
 | 
			
		||||
        response_time_exceeded_delay: PT12H
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -62,8 +62,10 @@ framework:
 | 
			
		||||
            'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority
 | 
			
		||||
            'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async
 | 
			
		||||
            'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async
 | 
			
		||||
            'Chill\MainBundle\Notification\Email\NotificationEmailMessages\SendImmediateNotificationEmailMessage': async
 | 
			
		||||
            'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
 | 
			
		||||
            'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
 | 
			
		||||
            'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
 | 
			
		||||
            # end of routes added by chill-bundles recipes
 | 
			
		||||
            # Route your messages to the transports
 | 
			
		||||
            # 'App\Message\YourMessage': async
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								resources/ticket_motives_import/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								resources/ticket_motives_import/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
In this directory, you find an example of file for the command `chill:main:ticket_motives_import`.
 | 
			
		||||
 | 
			
		||||
This file contains a list of ticket motives to import into the system. Each entry is a dictionary with two keys: `code` and `label`. The `code` key contains the unique code for the ticket motive, and the `label` key contains the human-readable label for the ticket motive.
 | 
			
		||||
 | 
			
		||||
The `stored_objects` key contains the documents that will be associated with the tickets. They must be found in the same directory.
 | 
			
		||||
 | 
			
		||||
The command `chill:main:ticket_motives_import` uses this file to import the specified ticket motives into the system.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										136
									
								
								resources/ticket_motives_import/motives.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								resources/ticket_motives_import/motives.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,136 @@
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour annonce de décès
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Date du décès
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: lieu du décès (domicile ou hôpital)
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: nom de l’hôpital
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: service concerné
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 2_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 3_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 4_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: 'Appel famille pour annonce absence : hospitalisation ou consultation'
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Quel hôpital
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: quel service
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: pour quelles raisons
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 'consultation : date et heure'
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: hospitalisation complète ou HDJ
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 5_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 6_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 7_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: 'Appel famille pour annonce absence : interruption de prise en charge'
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Pour quelles raisons ? Date
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: durée
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: accord médical ?
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 8_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 9_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 10_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: 'Appel famille pour annonce absence : changement d’adresse'
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Où
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Pourquoi ? Pour combien de temps ? Besoin d’un relais des soins ? Nouvelle adresse ?
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 11_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 12_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 13_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour altération de l’état général du patient
 | 
			
		||||
  urgent: true
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Recherche des symptômes
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Attentes par rapport à la demande
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 14_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 15_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 16_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour prise en charge de la douleur
 | 
			
		||||
  urgent: true
 | 
			
		||||
  supplementary_informations:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Localisation douleur
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Horaire dernier passage
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: Traitements en cours
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 17_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 18_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 19_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
- label:
 | 
			
		||||
    fr: Appel famille pour information sur la date de prise en charge
 | 
			
		||||
  urgent: false
 | 
			
		||||
  supplementary_informations: []
 | 
			
		||||
  stored_objects:
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: ☀️ De 07h à 21h
 | 
			
		||||
    filename: 20_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🌙 De 21h à 07h du matin
 | 
			
		||||
    filename: 21_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
  - label:
 | 
			
		||||
      fr: 🗓️ Dimanches et jours fériés
 | 
			
		||||
    filename: 22_doc_20250402_Pelotons flux externes consolidés.pdf
 | 
			
		||||
							
								
								
									
										6
									
								
								resources/translation_override/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								resources/translation_override/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
In this directory, you find an example of file for the command `chill:main:override_translation`.
 | 
			
		||||
 | 
			
		||||
This file contains a list of translations to override in the translation catalogue. Each entry is a dictionary with two keys: `from` and `to`. The `from` key contains the original translation string, and the `to` key contains the replacement string.
 | 
			
		||||
 | 
			
		||||
The command `chill:main:override_translation` uses this file to generate a new translation catalogue with the specified overrides applied.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								resources/translation_override/overrides.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								resources/translation_override/overrides.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
- {from: "de l'usager", to: "du patient"}
 | 
			
		||||
- {from: "l'usager", to: "le patient"}
 | 
			
		||||
- {from: "L'usager", to: "Le patient"}
 | 
			
		||||
- {from: "d'usagers", to: "de patients"}
 | 
			
		||||
- {from: "usagers", to: "patients"}
 | 
			
		||||
- {from: "Usagers", to: "Patients"}
 | 
			
		||||
- {from: "usager", to: "patient"}
 | 
			
		||||
- {from: "Usager", to: "Patient"}
 | 
			
		||||
@@ -24,7 +24,11 @@ use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
 | 
			
		||||
class CalendarForShortMessageProvider
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private readonly CalendarRepository $calendarRepository, private readonly EntityManagerInterface $em, private readonly RangeGeneratorInterface $rangeGenerator) {}
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private readonly CalendarRepository $calendarRepository,
 | 
			
		||||
        private readonly EntityManagerInterface $em,
 | 
			
		||||
        private readonly RangeGeneratorInterface $rangeGenerator,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generate calendars instance.
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ namespace Chill\CalendarBundle\Tests\Service\ShortMessageNotification;
 | 
			
		||||
use Chill\CalendarBundle\Entity\Calendar;
 | 
			
		||||
use Chill\CalendarBundle\Repository\CalendarRepository;
 | 
			
		||||
use Chill\CalendarBundle\Service\ShortMessageNotification\CalendarForShortMessageProvider;
 | 
			
		||||
use Chill\CalendarBundle\Service\ShortMessageNotification\DefaultRangeGenerator;
 | 
			
		||||
use Chill\CalendarBundle\Service\ShortMessageNotification\RangeGeneratorInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
@@ -82,10 +81,16 @@ final class CalendarForShortMessageProviderTest extends TestCase
 | 
			
		||||
        $em = $this->prophesize(EntityManagerInterface::class);
 | 
			
		||||
        $em->clear()->shouldBeCalled();
 | 
			
		||||
 | 
			
		||||
        $calendarRangeGenerator = $this->prophesize(RangeGeneratorInterface::class);
 | 
			
		||||
        $calendarRangeGenerator->generateRange(Argument::any())->willReturn([
 | 
			
		||||
            'startDate' => new \DateTimeImmutable('yesterday'),
 | 
			
		||||
            'endDate' => new \DateTimeImmutable('now'),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $provider = new CalendarForShortMessageProvider(
 | 
			
		||||
            $calendarRepository->reveal(),
 | 
			
		||||
            $em->reveal(),
 | 
			
		||||
            new DefaultRangeGenerator()
 | 
			
		||||
            $calendarRangeGenerator->reveal(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now')));
 | 
			
		||||
@@ -103,26 +108,32 @@ final class CalendarForShortMessageProviderTest extends TestCase
 | 
			
		||||
            Argument::type(\DateTimeImmutable::class),
 | 
			
		||||
            Argument::type('int'),
 | 
			
		||||
            Argument::exact(0)
 | 
			
		||||
        )->will(static fn ($args) => array_fill(0, 1, new Calendar()))->shouldBeCalledTimes(1);
 | 
			
		||||
        )->will(static fn ($args) => array_fill(0, 10, new Calendar()))->shouldBeCalledTimes(1);
 | 
			
		||||
        $calendarRepository->findByNotificationAvailable(
 | 
			
		||||
            Argument::type(\DateTimeImmutable::class),
 | 
			
		||||
            Argument::type(\DateTimeImmutable::class),
 | 
			
		||||
            Argument::type('int'),
 | 
			
		||||
            Argument::not(0)
 | 
			
		||||
            Argument::exact(10)
 | 
			
		||||
        )->will(static fn ($args) => [])->shouldBeCalledTimes(1);
 | 
			
		||||
 | 
			
		||||
        $em = $this->prophesize(EntityManagerInterface::class);
 | 
			
		||||
        $em->clear()->shouldBeCalled();
 | 
			
		||||
 | 
			
		||||
        $calendarRangeGenerator = $this->prophesize(RangeGeneratorInterface::class);
 | 
			
		||||
        $calendarRangeGenerator->generateRange(Argument::any())->willReturn([
 | 
			
		||||
            'startDate' => new \DateTimeImmutable('yesterday'),
 | 
			
		||||
            'endDate' => new \DateTimeImmutable('now'),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        $provider = new CalendarForShortMessageProvider(
 | 
			
		||||
            $calendarRepository->reveal(),
 | 
			
		||||
            $em->reveal(),
 | 
			
		||||
            new DefaultRangeGenerator()
 | 
			
		||||
            $calendarRangeGenerator->reveal(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        $calendars = iterator_to_array($provider->getCalendars(new \DateTimeImmutable('now')));
 | 
			
		||||
 | 
			
		||||
        $this->assertEquals(1, \count($calendars));
 | 
			
		||||
        $this->assertEquals(10, \count($calendars));
 | 
			
		||||
        $this->assertContainsOnly(Calendar::class, $calendars);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\CustomFieldsBundle\EntityRepository;
 | 
			
		||||
 | 
			
		||||
use Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
class CustomFieldsDefaultGroupRepository extends ServiceEntityRepository
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(ManagerRegistry $registry)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, CustomFieldsDefaultGroup::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findOneByEntity(string $className): ?CustomFieldsDefaultGroup
 | 
			
		||||
    {
 | 
			
		||||
        return $this->findOneBy(['entity' => $className]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -127,3 +127,7 @@ services:
 | 
			
		||||
        factory: ["@doctrine", getRepository]
 | 
			
		||||
        arguments:
 | 
			
		||||
            - "Chill\\CustomFieldsBundle\\Entity\\CustomFieldLongChoice\\Option"
 | 
			
		||||
 | 
			
		||||
    Chill\CustomFieldsBundle\EntityRepository\CustomFieldsDefaultGroupRepository:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
 | 
			
		||||
use Chill\DocStoreBundle\Service\Cryptography\KeyGenerator;
 | 
			
		||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
 | 
			
		||||
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
 | 
			
		||||
use Symfony\Component\Filesystem\Filesystem;
 | 
			
		||||
use Symfony\Component\Filesystem\Path;
 | 
			
		||||
 | 
			
		||||
@@ -147,16 +148,11 @@ class StoredObjectManager implements StoredObjectManagerInterface
 | 
			
		||||
    public function writeContent(string $filename, string $encryptedContent): void
 | 
			
		||||
    {
 | 
			
		||||
        $fullPath = $this->buildPath($filename);
 | 
			
		||||
        $dir = Path::getDirectory($fullPath);
 | 
			
		||||
 | 
			
		||||
        if (!$this->filesystem->exists($dir)) {
 | 
			
		||||
            $this->filesystem->mkdir($dir);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $result = file_put_contents($fullPath, $encryptedContent);
 | 
			
		||||
 | 
			
		||||
        if (false === $result) {
 | 
			
		||||
            throw StoredObjectManagerException::unableToStoreDocumentOnDisk();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->filesystem->dumpFile($fullPath, $encryptedContent);
 | 
			
		||||
        } catch (IOExceptionInterface $exception) {
 | 
			
		||||
            throw StoredObjectManagerException::unableToStoreDocumentOnDisk($exception);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ class StoredObject implements Document, TrackCreationInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, StoredObjectVersion>&Selectable<int, StoredObjectVersion>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true)]
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'storedObject', targetEntity: StoredObjectVersion::class, cascade: ['persist'], orphanRemoval: true, fetch: 'EAGER')]
 | 
			
		||||
    private Collection&Selectable $versions;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -43,11 +43,17 @@ class StoredObjectVersionNormalizer implements NormalizerInterface, NormalizerAw
 | 
			
		||||
            'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, [...$context, UserNormalizer::AT_DATE => $object->getCreatedAt()]),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
 | 
			
		||||
        $normalizationGroups = $context[AbstractNormalizer::GROUPS] ?? [];
 | 
			
		||||
 | 
			
		||||
        if (is_string($normalizationGroups)) {
 | 
			
		||||
            $normalizationGroups = [$normalizationGroups];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array(self::WITH_POINT_IN_TIMES_CONTEXT, $normalizationGroups, true)) {
 | 
			
		||||
            $data['point-in-times'] = $this->normalizer->normalize($object->getPointInTimes(), $format, $context);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array(self::WITH_RESTORED_CONTEXT, $context[AbstractNormalizer::GROUPS] ?? [], true)) {
 | 
			
		||||
        if (in_array(self::WITH_RESTORED_CONTEXT, $normalizationGroups, true)) {
 | 
			
		||||
            $data['from-restored'] = $this->normalizer->normalize($object->getCreatedFrom(), $format, [AbstractNormalizer::GROUPS => ['read']]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Controller\Admin;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorInterface;
 | 
			
		||||
use Doctrine\ORM\QueryBuilder;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
class EventBudgetKindController extends CRUDController
 | 
			
		||||
{
 | 
			
		||||
    protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
 | 
			
		||||
    {
 | 
			
		||||
        /* @var QueryBuilder $query */
 | 
			
		||||
        $query->addOrderBy('e.type', 'ASC');
 | 
			
		||||
 | 
			
		||||
        return parent::orderQuery($action, $query, $request, $paginator);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,11 +23,11 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
 | 
			
		||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
 | 
			
		||||
use PhpOffice\PhpSpreadsheet\Writer\Csv;
 | 
			
		||||
use PhpOffice\PhpSpreadsheet\Writer\Ods;
 | 
			
		||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 | 
			
		||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
 | 
			
		||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
@@ -41,6 +41,8 @@ use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpFoundation\StreamedResponse;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\ExceptionInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -58,7 +60,8 @@ final class EventController extends AbstractController
 | 
			
		||||
        private readonly TranslatorInterface $translator,
 | 
			
		||||
        private readonly PaginatorFactory $paginator,
 | 
			
		||||
        private readonly Security $security,
 | 
			
		||||
        private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
 | 
			
		||||
        private readonly ManagerRegistry $managerRegistry,
 | 
			
		||||
        private readonly NormalizerInterface $normalizer,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/delete', name: 'chill_event__event_delete', requirements: ['event_id' => '\d+'], methods: ['GET', 'POST', 'DELETE'])]
 | 
			
		||||
@@ -75,6 +78,7 @@ final class EventController extends AbstractController
 | 
			
		||||
 | 
			
		||||
        /** @var array $participations */
 | 
			
		||||
        $participations = $event->getParticipations();
 | 
			
		||||
        $budgetElements = $event->getBudgetElements();
 | 
			
		||||
 | 
			
		||||
        $form = $this->createDeleteForm($event_id);
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +90,10 @@ final class EventController extends AbstractController
 | 
			
		||||
                    $em->remove($participation);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                foreach ($budgetElements as $e) {
 | 
			
		||||
                    $em->remove($e);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $em->remove($event);
 | 
			
		||||
                $em->flush();
 | 
			
		||||
 | 
			
		||||
@@ -103,7 +111,7 @@ final class EventController extends AbstractController
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->render('@ChillEvent/Event/confirm_delete.html.twig', [
 | 
			
		||||
            'event_id' => $event->getId(),
 | 
			
		||||
            'id' => $event->getId(),
 | 
			
		||||
            'delete_form' => $form->createView(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
@@ -169,6 +177,8 @@ final class EventController extends AbstractController
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a form to create a new Event entity.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws ExceptionInterface
 | 
			
		||||
     */
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/new', name: 'chill_event__event_new', methods: ['GET', 'POST'])]
 | 
			
		||||
    public function newAction(?Center $center, Request $request): Response
 | 
			
		||||
@@ -199,26 +209,23 @@ final class EventController extends AbstractController
 | 
			
		||||
            $this->addFlash('success', $this->translator
 | 
			
		||||
                ->trans('The event was created'));
 | 
			
		||||
 | 
			
		||||
            return $this->redirectToRoute('chill_event__event_show', ['event_id' => $entity->getId()]);
 | 
			
		||||
            return $this->redirectToRoute('chill_event__event_show', ['id' => $entity->getId()]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $entity_array = $this->normalizer->normalize($entity, 'json', ['groups' => 'read']);
 | 
			
		||||
 | 
			
		||||
        return $this->render('@ChillEvent/Event/new.html.twig', [
 | 
			
		||||
            'entity' => $entity,
 | 
			
		||||
            'form' => $form->createView(),
 | 
			
		||||
            'entity_json' => $entity_array,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * First step of new Event form.
 | 
			
		||||
     */
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/new/pick-center', name: 'chill_event__event_new_pickcenter', options: [null])]
 | 
			
		||||
    public function newPickCenterAction(): Response
 | 
			
		||||
    {
 | 
			
		||||
        $role = 'CHILL_EVENT_CREATE';
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * @var Center $centers
 | 
			
		||||
         */
 | 
			
		||||
        $centers = $this->authorizationHelper->getReachableCenters($this->getUser(), $role);
 | 
			
		||||
 | 
			
		||||
        if (1 === \count($centers)) {
 | 
			
		||||
@@ -238,7 +245,7 @@ final class EventController extends AbstractController
 | 
			
		||||
            ->add('center_id', EntityType::class, [
 | 
			
		||||
                'class' => Center::class,
 | 
			
		||||
                'choices' => $centers,
 | 
			
		||||
                'placeholder' => '',
 | 
			
		||||
                'placeholder' => $this->translator->trans('Pick a center'),
 | 
			
		||||
                'label' => 'To which centre should the event be associated ?',
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('submit', SubmitType::class, [
 | 
			
		||||
@@ -251,16 +258,7 @@ final class EventController extends AbstractController
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds and displays a Event entity.
 | 
			
		||||
     *
 | 
			
		||||
     * @ParamConverter("event", options={"id": "event_id"})
 | 
			
		||||
     *
 | 
			
		||||
     * @return Response
 | 
			
		||||
     *
 | 
			
		||||
     * @throws \PhpOffice\PhpSpreadsheet\Exception
 | 
			
		||||
     */
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{event_id}/show', name: 'chill_event__event_show')]
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/event/{id}/show', name: 'chill_event__event_show')]
 | 
			
		||||
    public function showAction(Event $event, Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$event) {
 | 
			
		||||
@@ -317,7 +315,7 @@ final class EventController extends AbstractController
 | 
			
		||||
 | 
			
		||||
            $this->addFlash('success', $this->translator->trans('The event was updated'));
 | 
			
		||||
 | 
			
		||||
            return $this->redirectToRoute('chill_event__event_show', ['event_id' => $event_id]);
 | 
			
		||||
            return $this->redirectToRoute('chill_event__event_show', ['id' => $event_id]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->render('@ChillEvent/Event/edit.html.twig', [
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,15 @@ use Chill\EventBundle\Entity\Event;
 | 
			
		||||
use Chill\EventBundle\Entity\EventType;
 | 
			
		||||
use Chill\EventBundle\Repository\EventACLAwareRepositoryInterface;
 | 
			
		||||
use Chill\EventBundle\Repository\EventTypeRepository;
 | 
			
		||||
use Chill\EventBundle\Security\EventVoter;
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorFactoryInterface;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
 | 
			
		||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
 | 
			
		||||
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
 | 
			
		||||
use Symfony\Component\Form\FormFactoryInterface;
 | 
			
		||||
@@ -29,17 +33,18 @@ use Symfony\Component\Routing\Annotation\Route;
 | 
			
		||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
 | 
			
		||||
use Twig\Environment;
 | 
			
		||||
 | 
			
		||||
final readonly class EventListController
 | 
			
		||||
final class EventListController extends AbstractController
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private Environment $environment,
 | 
			
		||||
        private EventACLAwareRepositoryInterface $eventACLAwareRepository,
 | 
			
		||||
        private EventTypeRepository $eventTypeRepository,
 | 
			
		||||
        private FilterOrderHelperFactory $filterOrderHelperFactory,
 | 
			
		||||
        private FormFactoryInterface $formFactory,
 | 
			
		||||
        private PaginatorFactoryInterface $paginatorFactory,
 | 
			
		||||
        private TranslatableStringHelperInterface $translatableStringHelper,
 | 
			
		||||
        private UrlGeneratorInterface $urlGenerator,
 | 
			
		||||
        private readonly Environment $environment,
 | 
			
		||||
        private readonly EventACLAwareRepositoryInterface $eventACLAwareRepository,
 | 
			
		||||
        private readonly EventTypeRepository $eventTypeRepository,
 | 
			
		||||
        private readonly FilterOrderHelperFactory $filterOrderHelperFactory,
 | 
			
		||||
        private readonly FormFactoryInterface $formFactory,
 | 
			
		||||
        private readonly PaginatorFactoryInterface $paginatorFactory,
 | 
			
		||||
        private readonly TranslatableStringHelperInterface $translatableStringHelper,
 | 
			
		||||
        private readonly UrlGeneratorInterface $urlGenerator,
 | 
			
		||||
        private readonly AuthorizationHelper $authorizationHelper,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    #[Route(path: '{_locale}/event/event/list', name: 'chill_event_event_list')]
 | 
			
		||||
@@ -50,6 +55,8 @@ final readonly class EventListController
 | 
			
		||||
            'q' => (string) $filter->getQueryString(),
 | 
			
		||||
            'dates' => $filter->getDateRangeData('dates'),
 | 
			
		||||
            'event_types' => $filter->getEntityChoiceData('event_types'),
 | 
			
		||||
            'responsables' => $filter->getUserPickerData('responsables'),
 | 
			
		||||
            'centers' => $filter->getEntityChoiceData('centers'),
 | 
			
		||||
        ];
 | 
			
		||||
        $total = $this->eventACLAwareRepository->countAllViewable($filterData);
 | 
			
		||||
        $pagination = $this->paginatorFactory->create($total);
 | 
			
		||||
@@ -73,6 +80,7 @@ final readonly class EventListController
 | 
			
		||||
    private function buildFilterOrder(): FilterOrderHelper
 | 
			
		||||
    {
 | 
			
		||||
        $types = $this->eventTypeRepository->findAllActive();
 | 
			
		||||
        $centers = $this->authorizationHelper->getReachableCenters($this->getUser(), EventVoter::SEE);
 | 
			
		||||
 | 
			
		||||
        $builder = $this->filterOrderHelperFactory->create(__METHOD__);
 | 
			
		||||
        $builder
 | 
			
		||||
@@ -80,6 +88,16 @@ final readonly class EventListController
 | 
			
		||||
            ->addSearchBox(['name'])
 | 
			
		||||
            ->addEntityChoice('event_types', 'event.filter.event_types', EventType::class, $types, [
 | 
			
		||||
                'choice_label' => fn (EventType $e) => $this->translatableStringHelper->localize($e->getName()),
 | 
			
		||||
                'expanded' => false,
 | 
			
		||||
                'required' => false,
 | 
			
		||||
                'attr' => ['class' => 'select2'],
 | 
			
		||||
            ])
 | 
			
		||||
            ->addUserPicker('responsables', 'event.filter.pick_responsable', ['multiple' => true, 'required' => false])
 | 
			
		||||
            ->addEntityChoice('centers', 'event.filter.center', Center::class, $centers, [
 | 
			
		||||
                'choice_label' => fn (Center $c) => $c->getName(),
 | 
			
		||||
                'expanded' => false,
 | 
			
		||||
                'required' => false,
 | 
			
		||||
                'attr' => ['class' => 'select2'],
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        return $builder->build();
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorInterface;
 | 
			
		||||
use Doctrine\ORM\QueryBuilder;
 | 
			
		||||
use Symfony\Component\Form\FormInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
class EventThemeController extends CRUDController
 | 
			
		||||
{
 | 
			
		||||
    protected function createFormFor(string $action, $entity, ?string $formClass = null, array $formOptions = []): FormInterface
 | 
			
		||||
    {
 | 
			
		||||
        if ('new' === $action) {
 | 
			
		||||
            return parent::createFormFor($action, $entity, $formClass, ['step' => 'create']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ('edit' === $action) {
 | 
			
		||||
            return parent::createFormFor($action, $entity, $formClass, ['step' => 'edit']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new \LogicException('action is not supported: '.$action);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param QueryBuilder|mixed $query
 | 
			
		||||
     */
 | 
			
		||||
    protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator): QueryBuilder
 | 
			
		||||
    {
 | 
			
		||||
        /* @var QueryBuilder $query */
 | 
			
		||||
        return $query->orderBy('e.ordering', 'ASC')
 | 
			
		||||
            ->addOrderBy('e.id', 'ASC');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -228,7 +228,7 @@ final class ParticipationController extends AbstractController
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return $this->redirectToRoute('chill_event__event_show', [
 | 
			
		||||
                'event_id' => $participation->getEvent()->getId(),
 | 
			
		||||
                'id' => $participation->getEvent()->getId(),
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -242,7 +242,7 @@ final class ParticipationController extends AbstractController
 | 
			
		||||
    /**
 | 
			
		||||
     * @param int $participation_id
 | 
			
		||||
     */
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/participation/{participation_id}/delete', name: 'chill_event_participation_delete', requirements: ['participation_id' => '\d+'], methods: ['GET', 'DELETE'])]
 | 
			
		||||
    #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/event/participation/{participation_id}/delete', name: 'chill_event_participation_delete', requirements: ['participation_id' => '\d+'])]
 | 
			
		||||
    public function deleteAction($participation_id, Request $request): Response|\Symfony\Component\HttpFoundation\RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $em = $this->managerRegistry->getManager();
 | 
			
		||||
@@ -273,7 +273,7 @@ final class ParticipationController extends AbstractController
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                return $this->redirectToRoute('chill_event__event_show', [
 | 
			
		||||
                    'event_id' => $event->getId(),
 | 
			
		||||
                    'id' => $event->getId(),
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -442,7 +442,7 @@ final class ParticipationController extends AbstractController
 | 
			
		||||
            ));
 | 
			
		||||
 | 
			
		||||
            return $this->redirectToRoute('chill_event__event_show', [
 | 
			
		||||
                'event_id' => $participation->getEvent()->getId(),
 | 
			
		||||
                'id' => $participation->getEvent()->getId(),
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,12 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\DependencyInjection;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Controller\Admin\EventBudgetKindController;
 | 
			
		||||
use Chill\EventBundle\Controller\EventThemeController;
 | 
			
		||||
use Chill\EventBundle\Entity\EventBudgetKind;
 | 
			
		||||
use Chill\EventBundle\Entity\EventTheme;
 | 
			
		||||
use Chill\EventBundle\Form\EventBudgetKindType;
 | 
			
		||||
use Chill\EventBundle\Form\EventThemeType;
 | 
			
		||||
use Chill\EventBundle\Security\EventVoter;
 | 
			
		||||
use Chill\EventBundle\Security\ParticipationVoter;
 | 
			
		||||
use Symfony\Component\Config\FileLocator;
 | 
			
		||||
@@ -26,7 +32,10 @@ use Symfony\Component\HttpKernel\DependencyInjection\Extension;
 | 
			
		||||
 */
 | 
			
		||||
class ChillEventExtension extends Extension implements PrependExtensionInterface
 | 
			
		||||
{
 | 
			
		||||
    public function load(array $configs, ContainerBuilder $container)
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws \Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function load(array $configs, ContainerBuilder $container): void
 | 
			
		||||
    {
 | 
			
		||||
        $configuration = new Configuration();
 | 
			
		||||
        $config = $this->processConfiguration($configuration, $configs);
 | 
			
		||||
@@ -45,16 +54,17 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
 | 
			
		||||
    /** (non-PHPdoc).
 | 
			
		||||
     * @see \Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface::prepend()
 | 
			
		||||
     */
 | 
			
		||||
    public function prepend(ContainerBuilder $container)
 | 
			
		||||
    public function prepend(ContainerBuilder $container): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->prependAuthorization($container);
 | 
			
		||||
        $this->prependCruds($container);
 | 
			
		||||
        $this->prependRoute($container);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * add authorization hierarchy.
 | 
			
		||||
     */
 | 
			
		||||
    protected function prependAuthorization(ContainerBuilder $container)
 | 
			
		||||
    protected function prependAuthorization(ContainerBuilder $container): void
 | 
			
		||||
    {
 | 
			
		||||
        $container->prependExtensionConfig('security', [
 | 
			
		||||
            'role_hierarchy' => [
 | 
			
		||||
@@ -70,7 +80,7 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * add route to route loader for chill.
 | 
			
		||||
     */
 | 
			
		||||
    protected function prependRoute(ContainerBuilder $container)
 | 
			
		||||
    protected function prependRoute(ContainerBuilder $container): void
 | 
			
		||||
    {
 | 
			
		||||
        // add routes for custom bundle
 | 
			
		||||
        $container->prependExtensionConfig('chill_main', [
 | 
			
		||||
@@ -81,4 +91,54 @@ class ChillEventExtension extends Extension implements PrependExtensionInterface
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function prependCruds(ContainerBuilder $container): void
 | 
			
		||||
    {
 | 
			
		||||
        $container->prependExtensionConfig('chill_main', [
 | 
			
		||||
            'cruds' => [
 | 
			
		||||
                [
 | 
			
		||||
                    'class' => EventTheme::class,
 | 
			
		||||
                    'name' => 'event_theme',
 | 
			
		||||
                    'base_path' => '/admin/event/theme',
 | 
			
		||||
                    'form_class' => EventThemeType::class,
 | 
			
		||||
                    'controller' => EventThemeController::class,
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        'index' => [
 | 
			
		||||
                            'template' => '@ChillEvent/Admin/EventTheme/index.html.twig',
 | 
			
		||||
                            'role' => 'ROLE_ADMIN',
 | 
			
		||||
                        ],
 | 
			
		||||
                        'new' => [
 | 
			
		||||
                            'role' => 'ROLE_ADMIN',
 | 
			
		||||
                            'template' => '@ChillEvent/Admin/EventTheme/new.html.twig',
 | 
			
		||||
                        ],
 | 
			
		||||
                        'edit' => [
 | 
			
		||||
                            'role' => 'ROLE_ADMIN',
 | 
			
		||||
                            'template' => '@ChillEvent/Admin/EventTheme/edit.html.twig',
 | 
			
		||||
                        ],
 | 
			
		||||
                    ],
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    'class' => EventBudgetKind::class,
 | 
			
		||||
                    'name' => 'event_budget_kind',
 | 
			
		||||
                    'base_path' => '/admin/event/budget',
 | 
			
		||||
                    'form_class' => EventBudgetKindType::class,
 | 
			
		||||
                    'controller' => EventBudgetKindController::class,
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        'index' => [
 | 
			
		||||
                            'template' => '@ChillEvent/Admin/BudgetKind/index.html.twig',
 | 
			
		||||
                            'role' => 'ROLE_ADMIN',
 | 
			
		||||
                        ],
 | 
			
		||||
                        'new' => [
 | 
			
		||||
                            'role' => 'ROLE_ADMIN',
 | 
			
		||||
                            'template' => '@ChillEvent/Admin/BudgetKind/new.html.twig',
 | 
			
		||||
                        ],
 | 
			
		||||
                        'edit' => [
 | 
			
		||||
                            'role' => 'ROLE_ADMIN',
 | 
			
		||||
                            'template' => '@ChillEvent/Admin/BudgetKind/edit.html.twig',
 | 
			
		||||
                        ],
 | 
			
		||||
                    ],
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								src/Bundle/ChillEventBundle/Entity/BudgetTypeEnum.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Bundle/ChillEventBundle/Entity/BudgetTypeEnum.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Entity;
 | 
			
		||||
 | 
			
		||||
enum BudgetTypeEnum: string
 | 
			
		||||
{
 | 
			
		||||
    case CHARGE = 'Charge';
 | 
			
		||||
    case RESOURCE = 'Resource';
 | 
			
		||||
}
 | 
			
		||||
@@ -23,10 +23,13 @@ use Chill\MainBundle\Entity\HasScopeInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\Location;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\DBAL\Types\Types;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Symfony\Component\Validator\Constraints as Assert;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation as Serializer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class Event.
 | 
			
		||||
@@ -46,35 +49,63 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: Scope::class)]
 | 
			
		||||
    private ?Scope $circle = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE)]
 | 
			
		||||
    #[ORM\Column(type: Types::DATETIME_MUTABLE)]
 | 
			
		||||
    private ?\DateTime $date = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Id]
 | 
			
		||||
    #[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
 | 
			
		||||
    #[ORM\Column(name: 'id', type: Types::INTEGER)]
 | 
			
		||||
    #[ORM\GeneratedValue(strategy: 'AUTO')]
 | 
			
		||||
    private ?int $id = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: User::class)]
 | 
			
		||||
    private ?User $moderator = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, User>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\ManyToMany(targetEntity: User::class)]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    #[ORM\JoinTable('chill_event_animatorsintern')]
 | 
			
		||||
    private Collection $animatorsIntern;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, ThirdParty>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\ManyToMany(targetEntity: ThirdParty::class)]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    #[ORM\JoinTable('chill_event_animatorsextern')]
 | 
			
		||||
    private Collection $animatorsExtern;
 | 
			
		||||
 | 
			
		||||
    #[Assert\NotBlank]
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 150)]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    #[ORM\Column(type: Types::STRING, length: 150)]
 | 
			
		||||
    private ?string $name = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, Participation>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'event', targetEntity: Participation::class)]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    private Collection $participations;
 | 
			
		||||
 | 
			
		||||
    #[Assert\NotNull]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: EventType::class)]
 | 
			
		||||
    private ?EventType $type = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, EventTheme>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\ManyToMany(targetEntity: EventTheme::class)]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    #[ORM\JoinTable('chill_event_eventtheme')]
 | 
			
		||||
    private Collection $themes;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_')]
 | 
			
		||||
    private CommentEmbeddable $comment;
 | 
			
		||||
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: Location::class)]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    #[ORM\JoinColumn(nullable: true)]
 | 
			
		||||
    private ?Location $location = null;
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +116,17 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
    #[ORM\JoinTable('chill_event_event_documents')]
 | 
			
		||||
    private Collection $documents;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DECIMAL, precision: 10, scale: 4, nullable: true, options: ['default' => '0.0'])]
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, EventBudgetElement>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'event', targetEntity: EventBudgetElement::class, cascade: ['persist'])]
 | 
			
		||||
    #[Serializer\Groups(['read'])]
 | 
			
		||||
    private Collection $budgetElements;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated use budgetElements instead
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\Column(type: Types::DECIMAL, precision: 10, scale: 4, nullable: true, options: ['default' => '0.0'])]
 | 
			
		||||
    private string $organizationCost = '0.0';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -96,6 +137,20 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
        $this->participations = new ArrayCollection();
 | 
			
		||||
        $this->documents = new ArrayCollection();
 | 
			
		||||
        $this->comment = new CommentEmbeddable();
 | 
			
		||||
        $this->themes = new ArrayCollection();
 | 
			
		||||
        $this->budgetElements = new ArrayCollection();
 | 
			
		||||
        $this->animatorsIntern = new ArrayCollection();
 | 
			
		||||
        $this->animatorsExtern = new ArrayCollection();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addBudgetElement(EventBudgetElement $budgetElement)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->budgetElements->contains($budgetElement)) {
 | 
			
		||||
            $this->budgetElements[] = $budgetElement;
 | 
			
		||||
            $budgetElement->setEvent($this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -126,38 +181,79 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Center
 | 
			
		||||
     */
 | 
			
		||||
    public function getCenter()
 | 
			
		||||
    public function getThemes(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->themes;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addTheme(EventTheme $theme): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->themes->add($theme);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeTheme(EventTheme $theme): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->themes->removeElement($theme);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAnimatorsIntern(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->animatorsIntern;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAnimatorsExtern(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->animatorsExtern;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addAnimatorsIntern(User $ai): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->animatorsIntern->add($ai);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeAnimatorsIntern(User $ai): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->animatorsIntern->removeElement($ai);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addAnimatorsExtern(ThirdParty $ae): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->animatorsExtern->add($ae);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeAnimatorsExtern(ThirdParty $ae): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->animatorsExtern->removeElement($ae);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCenter(): Center
 | 
			
		||||
    {
 | 
			
		||||
        return $this->center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Scope
 | 
			
		||||
     */
 | 
			
		||||
    public function getCircle()
 | 
			
		||||
    public function getCircle(): ?Scope
 | 
			
		||||
    {
 | 
			
		||||
        return $this->circle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get date.
 | 
			
		||||
     *
 | 
			
		||||
     * @return \DateTime
 | 
			
		||||
     */
 | 
			
		||||
    public function getDate()
 | 
			
		||||
    public function getDate(): ?\DateTime
 | 
			
		||||
    {
 | 
			
		||||
        return $this->date;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get id.
 | 
			
		||||
     *
 | 
			
		||||
     * @return int
 | 
			
		||||
     */
 | 
			
		||||
    public function getId()
 | 
			
		||||
    public function getId(): ?int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
@@ -169,14 +265,20 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get label.
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function getName()
 | 
			
		||||
    public function getName(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Collection<int, EventBudgetElement>
 | 
			
		||||
     */
 | 
			
		||||
    public function getBudgetElements(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->budgetElements;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Collection<int, Participation>
 | 
			
		||||
     */
 | 
			
		||||
@@ -199,26 +301,26 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated
 | 
			
		||||
     *
 | 
			
		||||
     * @return Scope
 | 
			
		||||
     */
 | 
			
		||||
    public function getScope()
 | 
			
		||||
    public function getScope(): Scope
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getCircle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return EventType
 | 
			
		||||
     */
 | 
			
		||||
    public function getType()
 | 
			
		||||
    public function getType(): ?EventType
 | 
			
		||||
    {
 | 
			
		||||
        return $this->type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeBudgetElement(EventBudgetElement $budgetElement): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->budgetElements->removeElement($budgetElement);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove participation.
 | 
			
		||||
     */
 | 
			
		||||
    public function removeParticipation(Participation $participation)
 | 
			
		||||
    public function removeParticipation(Participation $participation): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->participations->removeElement($participation);
 | 
			
		||||
    }
 | 
			
		||||
@@ -314,11 +416,17 @@ class Event implements HasCenterInterface, HasScopeInterface, TrackCreationInter
 | 
			
		||||
        $this->documents = $documents;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated
 | 
			
		||||
     */
 | 
			
		||||
    public function getOrganizationCost(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->organizationCost;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated
 | 
			
		||||
     */
 | 
			
		||||
    public function setOrganizationCost(string $organizationCost): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->organizationCost = $organizationCost;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										103
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetElement.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetElement.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,103 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Repository\EventThemeRepository;
 | 
			
		||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
 | 
			
		||||
use Doctrine\DBAL\Types\Types;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Symfony\Component\Validator\Constraints as Assert;
 | 
			
		||||
 | 
			
		||||
#[ORM\Entity(repositoryClass: EventThemeRepository::class)]
 | 
			
		||||
#[ORM\Table(name: 'chill_event_budget_element')]
 | 
			
		||||
class EventBudgetElement
 | 
			
		||||
{
 | 
			
		||||
    #[ORM\Column(name: 'id', type: Types::INTEGER)]
 | 
			
		||||
    #[ORM\Id]
 | 
			
		||||
    #[ORM\GeneratedValue(strategy: 'AUTO')]
 | 
			
		||||
    private ?int $id = null;
 | 
			
		||||
 | 
			
		||||
    #[Assert\GreaterThan(value: 0)]
 | 
			
		||||
    #[Assert\NotNull(message: 'The amount cannot be empty')]
 | 
			
		||||
    #[ORM\Column(name: 'amount', type: Types::DECIMAL, precision: 10, scale: 2)]
 | 
			
		||||
    private string $amount;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Embedded(class: CommentEmbeddable::class, columnPrefix: 'comment_budget_element_')]
 | 
			
		||||
    private ?CommentEmbeddable $comment = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: Event::class)]
 | 
			
		||||
    private Event $event;
 | 
			
		||||
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: EventBudgetKind::class, inversedBy: 'EventBudgetElement')]
 | 
			
		||||
    #[ORM\JoinColumn]
 | 
			
		||||
    private EventBudgetKind $kind;
 | 
			
		||||
 | 
			
		||||
    /* Getters and Setters */
 | 
			
		||||
 | 
			
		||||
    public function getId(): ?int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setId(?int $id): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->id = $id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAmount(): float
 | 
			
		||||
    {
 | 
			
		||||
        return (float) $this->amount;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getComment(): ?CommentEmbeddable
 | 
			
		||||
    {
 | 
			
		||||
        return $this->comment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getEvent(): Event
 | 
			
		||||
    {
 | 
			
		||||
        return $this->event;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getKind(): EventBudgetKind
 | 
			
		||||
    {
 | 
			
		||||
        return $this->kind;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setAmount(string $amount): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->amount = $amount;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setComment(?CommentEmbeddable $comment = null): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->comment = $comment;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setEvent(Event $event): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->event = $event;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setKind(EventBudgetKind $kind): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->kind = $kind;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetKind.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/Bundle/ChillEventBundle/Entity/EventBudgetKind.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Types\Types;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of event budget element.
 | 
			
		||||
 */
 | 
			
		||||
#[ORM\Entity]
 | 
			
		||||
#[ORM\Table(name: 'chill_event_budget_kind')]
 | 
			
		||||
class EventBudgetKind
 | 
			
		||||
{
 | 
			
		||||
    #[ORM\Id]
 | 
			
		||||
    #[ORM\GeneratedValue]
 | 
			
		||||
    #[ORM\Column(type: Types::INTEGER)]
 | 
			
		||||
    private ?int $id = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: Types::BOOLEAN, options: ['default' => true])]
 | 
			
		||||
    private bool $isActive = true;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(enumType: BudgetTypeEnum::class)]
 | 
			
		||||
    private BudgetTypeEnum $type;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: Types::JSON, length: 255, options: ['default' => '{}', 'jsonb' => true])]
 | 
			
		||||
    private array $name = [];
 | 
			
		||||
 | 
			
		||||
    public function getId(): ?int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getIsActive(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->isActive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getType(): BudgetTypeEnum
 | 
			
		||||
    {
 | 
			
		||||
        return $this->type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getName(): ?array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setIsActive(bool $isActive): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->isActive = $isActive;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setType(BudgetTypeEnum $type): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->type = $type;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setName(array $name): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->name = $name;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										158
									
								
								src/Bundle/ChillEventBundle/Entity/EventTheme.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								src/Bundle/ChillEventBundle/Entity/EventTheme.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Repository\EventThemeRepository;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\DBAL\Types\Types;
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class EventTheme.
 | 
			
		||||
 */
 | 
			
		||||
#[ORM\HasLifecycleCallbacks]
 | 
			
		||||
#[ORM\Entity(repositoryClass: EventThemeRepository::class)]
 | 
			
		||||
#[ORM\Table(name: 'chill_event_event_theme')]
 | 
			
		||||
class EventTheme
 | 
			
		||||
{
 | 
			
		||||
    #[ORM\Column(type: Types::BOOLEAN, nullable: false)]
 | 
			
		||||
    private bool $isActive = true;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Id]
 | 
			
		||||
    #[ORM\Column(name: 'id', type: Types::INTEGER)]
 | 
			
		||||
    #[ORM\GeneratedValue(strategy: 'AUTO')]
 | 
			
		||||
    private ?int $id = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: Types::JSON)]
 | 
			
		||||
    private array $name;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, EventTheme>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\OneToMany(mappedBy: 'parent', targetEntity: EventTheme::class)]
 | 
			
		||||
    private Collection $children;
 | 
			
		||||
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: EventTheme::class, inversedBy: 'children')]
 | 
			
		||||
    private ?EventTheme $parent = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(name: 'ordering', type: Types::FLOAT, options: ['default' => '0.0'])]
 | 
			
		||||
    private float $ordering = 0.0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor.
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->children = new ArrayCollection();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get active.
 | 
			
		||||
     */
 | 
			
		||||
    public function getIsActive(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->isActive;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get id.
 | 
			
		||||
     */
 | 
			
		||||
    public function getId(): ?int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get label.
 | 
			
		||||
     */
 | 
			
		||||
    public function getName(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setIsActive(bool $active): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->isActive = $active;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setName(array $label): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->name = $label;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addChild(self $child): self
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->children->contains($child)) {
 | 
			
		||||
            $this->children[] = $child;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeChild(self $child): self
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->children->removeElement($child)) {
 | 
			
		||||
            // set the owning side to null (unless already changed)
 | 
			
		||||
            if ($child->getParent() === $this) {
 | 
			
		||||
                $child->setParent(null);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getChildren(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->children;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasChildren(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return 0 < $this->getChildren()->count();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasParent(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return null !== $this->parent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getOrdering(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->ordering;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setOrdering(float $ordering): EventTheme
 | 
			
		||||
    {
 | 
			
		||||
        $this->ordering = $ordering;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getParent(): ?self
 | 
			
		||||
    {
 | 
			
		||||
        return $this->parent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setParent(?self $parent): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->parent = $parent;
 | 
			
		||||
 | 
			
		||||
        $parent?->addChild($this);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										377
									
								
								src/Bundle/ChillEventBundle/Export/Export/ListEvents.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										377
									
								
								src/Bundle/ChillEventBundle/Export/Export/ListEvents.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,377 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Export\Export;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
 | 
			
		||||
use Chill\EventBundle\Entity\Event;
 | 
			
		||||
use Chill\EventBundle\Export\Declarations;
 | 
			
		||||
use Chill\EventBundle\Repository\EventBudgetElementRepository;
 | 
			
		||||
use Chill\EventBundle\Repository\EventThemeRepository;
 | 
			
		||||
use Chill\EventBundle\Security\EventVoter;
 | 
			
		||||
use Chill\EventBundle\Templating\Entity\EventThemeRender;
 | 
			
		||||
use Chill\MainBundle\Export\ExportGenerationContext;
 | 
			
		||||
use Chill\MainBundle\Export\FormatterInterface;
 | 
			
		||||
use Chill\MainBundle\Export\GroupedExportInterface;
 | 
			
		||||
use Chill\MainBundle\Export\ListInterface;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
 | 
			
		||||
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Doctrine\ORM\NativeQuery;
 | 
			
		||||
use Doctrine\ORM\Query;
 | 
			
		||||
use Doctrine\ORM\QueryBuilder;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\Validator\Constraints\Callback;
 | 
			
		||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatableInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Render a list of events.
 | 
			
		||||
 */
 | 
			
		||||
class ListEvents implements ListInterface, GroupedExportInterface
 | 
			
		||||
{
 | 
			
		||||
    protected array $fields = [
 | 
			
		||||
        'event_id',
 | 
			
		||||
        'event_center',
 | 
			
		||||
        'event_name',
 | 
			
		||||
        'event_date',
 | 
			
		||||
        'event_location',
 | 
			
		||||
        'event_type',
 | 
			
		||||
        'event_themes',
 | 
			
		||||
        'event_moderator',
 | 
			
		||||
        'event_animators',
 | 
			
		||||
        'event_participants_count',
 | 
			
		||||
        'event_budget_resources',
 | 
			
		||||
        'event_budget_charges',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    private readonly bool $filterStatsByCenters;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected readonly EntityManagerInterface $entityManager,
 | 
			
		||||
        ParameterBagInterface $parameterBag,
 | 
			
		||||
        protected readonly TranslatableStringHelperInterface $translatableStringHelper,
 | 
			
		||||
        protected readonly EventThemeRender $eventThemeRender,
 | 
			
		||||
        protected readonly EventThemeRepository $eventThemeRepository,
 | 
			
		||||
        protected readonly LabelThirdPartyHelper $labelThirdPartyHelper,
 | 
			
		||||
        protected readonly EventBudgetElementRepository $eventBudgetElementRepository,
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder): void
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('fields', ChoiceType::class, [
 | 
			
		||||
                'multiple' => true,
 | 
			
		||||
                'expanded' => true,
 | 
			
		||||
                'choices' => array_combine($this->fields, $this->fields),
 | 
			
		||||
                'label' => 'Fields to include in export',
 | 
			
		||||
                'constraints' => [new Callback([
 | 
			
		||||
                    'callback' => static function ($selected, ExecutionContextInterface $context) {
 | 
			
		||||
                        if (0 === \count($selected)) {
 | 
			
		||||
                            $context->buildViolation('You must select at least one element')
 | 
			
		||||
                                ->atPath('fields')
 | 
			
		||||
                                ->addViolation();
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                ])],
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFormDefaultData(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'fields' => $this->fields,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAllowedFormattersTypes(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [FormatterInterface::TYPE_LIST];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'export.event.list.description';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getGroup(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Exports of events';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLabels($key, array $values, $data)
 | 
			
		||||
    {
 | 
			
		||||
        return match ($key) {
 | 
			
		||||
            'event_id' => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
            'event_name' => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
            'event_date' => function ($value) use ($key) {
 | 
			
		||||
                if ('_header' === $value) {
 | 
			
		||||
                    return $key;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($value instanceof \DateTime) {
 | 
			
		||||
                    return $value->format('Y-m-d');
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
 | 
			
		||||
 | 
			
		||||
                return $date ? $date->format('Y-m-d') : $value;
 | 
			
		||||
            },
 | 
			
		||||
            'event_type' => function ($value) use ($key) {
 | 
			
		||||
                if ('_header' === $value) {
 | 
			
		||||
                    return 'export.event.list.'.$key;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
 | 
			
		||||
            },
 | 
			
		||||
            'event_center' => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
            'event_moderator' => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
            'event_participants_count' => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
            'event_location' => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
            'event_animators' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, $key),
 | 
			
		||||
            'event_themes' => function ($value) use ($key) {
 | 
			
		||||
                if ('_header' === $value) {
 | 
			
		||||
                    return $key;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (null === $value) {
 | 
			
		||||
                    return '';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return implode(
 | 
			
		||||
                    '|',
 | 
			
		||||
                    array_map(
 | 
			
		||||
                        fn ($t) => $this->eventThemeRender->renderString($this->eventThemeRepository->find($t), []),
 | 
			
		||||
                        json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR)
 | 
			
		||||
                    )
 | 
			
		||||
                );
 | 
			
		||||
            },
 | 
			
		||||
            'event_budget_resources' => function ($value) use ($key) {
 | 
			
		||||
                if ('_header' === $value) {
 | 
			
		||||
                    return $key;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!$value) {
 | 
			
		||||
                    return '';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $ids = explode(',', $value);
 | 
			
		||||
                $ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
 | 
			
		||||
                $elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);
 | 
			
		||||
 | 
			
		||||
                return implode('|', array_map(function ($element) {
 | 
			
		||||
                    $name = $this->translatableStringHelper->localize($element->getKind()->getName());
 | 
			
		||||
                    $amount = number_format($element->getAmount(), 2, '.', '');
 | 
			
		||||
 | 
			
		||||
                    return $name.': '.$amount;
 | 
			
		||||
                }, $elements));
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            'event_budget_charges' => function ($value) use ($key) {
 | 
			
		||||
                if ('_header' === $value) {
 | 
			
		||||
                    return $key;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (!$value) {
 | 
			
		||||
                    return '';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                $ids = explode(',', $value);
 | 
			
		||||
                $ids = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
 | 
			
		||||
 | 
			
		||||
                $elements = $this->eventBudgetElementRepository->findBy(['id' => $ids]);
 | 
			
		||||
 | 
			
		||||
                return implode('|', array_map(function ($element) {
 | 
			
		||||
                    $name = $this->translatableStringHelper->localize($element->getKind()->getName());
 | 
			
		||||
                    $amount = number_format($element->getAmount(), 2, '.', '');
 | 
			
		||||
 | 
			
		||||
                    return $name.': '.$amount;
 | 
			
		||||
                }, $elements));
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            default => fn ($value) => '_header' === $value ? $key : $value,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getQueryKeys(array $data): array
 | 
			
		||||
    {
 | 
			
		||||
        return $data['fields'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getResult($query, $data, ExportGenerationContext $context): array
 | 
			
		||||
    {
 | 
			
		||||
        return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getTitle(): string|TranslatableInterface
 | 
			
		||||
    {
 | 
			
		||||
        return 'export.event.list.title';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getType(): string
 | 
			
		||||
    {
 | 
			
		||||
        return Declarations::EVENT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): NativeQuery|QueryBuilder
 | 
			
		||||
    {
 | 
			
		||||
        $centers = array_map(static fn ($el) => $el['center'], $acl);
 | 
			
		||||
 | 
			
		||||
        // Throw an error if no fields are present
 | 
			
		||||
        if (!\array_key_exists('fields', $data)) {
 | 
			
		||||
            throw new \InvalidArgumentException('No fields have been checked.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $qb = $this->entityManager->createQueryBuilder()
 | 
			
		||||
            ->from(Event::class, 'event');
 | 
			
		||||
 | 
			
		||||
        if ($this->filterStatsByCenters) {
 | 
			
		||||
            $qb
 | 
			
		||||
                ->andWhere('event.center IN (:authorized_centers)')
 | 
			
		||||
                ->setParameter('authorized_centers', $centers);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add fields based on selection
 | 
			
		||||
        foreach ($this->fields as $field) {
 | 
			
		||||
            if (\in_array($field, $data['fields'], true)) {
 | 
			
		||||
                switch ($field) {
 | 
			
		||||
                    case 'event_id':
 | 
			
		||||
                        $qb->addSelect('event.id AS event_id');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_name':
 | 
			
		||||
                        $qb->addSelect('event.name AS event_name');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_date':
 | 
			
		||||
                        $qb->addSelect('event.date AS event_date');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_type':
 | 
			
		||||
                        if (!$this->hasJoin($qb, 'event.type')) {
 | 
			
		||||
                            $qb->leftJoin('event.type', 'type');
 | 
			
		||||
                        }
 | 
			
		||||
                        $qb->addSelect('type.name AS event_type');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_center':
 | 
			
		||||
                        if (!$this->hasJoin($qb, 'event.center')) {
 | 
			
		||||
                            $qb->leftJoin('event.center', 'center');
 | 
			
		||||
                        }
 | 
			
		||||
                        $qb->addSelect('center.name AS event_center');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_moderator':
 | 
			
		||||
                        if (!$this->hasJoin($qb, 'event.moderator')) {
 | 
			
		||||
                            $qb->leftJoin('event.moderator', 'user');
 | 
			
		||||
                        }
 | 
			
		||||
                        $qb->addSelect('user.username AS event_moderator');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_participants_count':
 | 
			
		||||
                        $qb->addSelect('(SELECT COUNT(p.id) FROM Chill\EventBundle\Entity\Participation p WHERE p.event = event.id) AS event_participants_count');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_location':
 | 
			
		||||
                        if (!$this->hasJoin($qb, 'event.location')) {
 | 
			
		||||
                            $qb->leftJoin('event.location', 'location');
 | 
			
		||||
                        }
 | 
			
		||||
                        $qb->addSelect('location.name AS event_location');
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_animators':
 | 
			
		||||
                        $qb->addSelect(
 | 
			
		||||
                            '(SELECT AGGREGATE(tp.id) FROM Chill\ThirdPartyBundle\Entity\ThirdParty tp WHERE tp MEMBER OF event.animators) AS event_animators'
 | 
			
		||||
                        );
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_themes':
 | 
			
		||||
                        $qb->addSelect(
 | 
			
		||||
                            '(SELECT AGGREGATE(t.id) FROM Chill\EventBundle\Entity\EventTheme t WHERE t MEMBER OF event.themes) AS event_themes'
 | 
			
		||||
                        );
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_budget_resources':
 | 
			
		||||
                        $qb->addSelect(
 | 
			
		||||
                            '(SELECT AGGREGATE(ebr.id)
 | 
			
		||||
                          FROM Chill\EventBundle\Entity\EventBudgetElement ebr
 | 
			
		||||
                          JOIN ebr.kind kr
 | 
			
		||||
                          WHERE ebr.event = event.id AND kr.type = :resource_type) AS event_budget_resources'
 | 
			
		||||
                        );
 | 
			
		||||
                        $qb->setParameter('resource_type', BudgetTypeEnum::RESOURCE->value);
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case 'event_budget_charges':
 | 
			
		||||
                        $qb->addSelect(
 | 
			
		||||
                            '(SELECT AGGREGATE(ebc.id)
 | 
			
		||||
                          FROM Chill\EventBundle\Entity\EventBudgetElement ebc
 | 
			
		||||
                          JOIN ebc.kind kc
 | 
			
		||||
                          WHERE ebc.event = event.id AND kc.type = :charge_type) AS event_budget_charges'
 | 
			
		||||
                        );
 | 
			
		||||
                        $qb->setParameter('charge_type', BudgetTypeEnum::CHARGE->value);
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $qb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function requiredRole(): string
 | 
			
		||||
    {
 | 
			
		||||
        return EventVoter::STATS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsModifiers()
 | 
			
		||||
    {
 | 
			
		||||
        return [Declarations::EVENT];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method to check if a join already exists in the QueryBuilder.
 | 
			
		||||
     */
 | 
			
		||||
    private function hasJoin($queryBuilder, $joinPath): bool
 | 
			
		||||
    {
 | 
			
		||||
        $joins = $queryBuilder->getDQLPart('join');
 | 
			
		||||
        if (!isset($joins['event'])) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($joins['event'] as $join) {
 | 
			
		||||
            if ($join->getJoin() === $joinPath) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function normalizeFormData(array $formData): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['fields' => $formData['fields']];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalizeFormData(array $formData, int $fromVersion): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['fields' => $formData['fields']];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getNormalizationVersion(): int
 | 
			
		||||
    {
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
 | 
			
		||||
use Chill\EventBundle\Entity\EventBudgetElement;
 | 
			
		||||
use Chill\EventBundle\Entity\EventBudgetKind;
 | 
			
		||||
use Chill\EventBundle\Repository\EventBudgetKindRepository;
 | 
			
		||||
use Chill\MainBundle\Form\Type\CommentType;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
 | 
			
		||||
class AddEventBudgetElementType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private readonly EventBudgetKindRepository $kindRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
 | 
			
		||||
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options): void
 | 
			
		||||
    {
 | 
			
		||||
        $charges = $this->kindRepository->findByType(BudgetTypeEnum::CHARGE->value);
 | 
			
		||||
        $resources = $this->kindRepository->findByType(BudgetTypeEnum::RESOURCE->value);
 | 
			
		||||
 | 
			
		||||
        $builder->add('kind', ChoiceType::class, [
 | 
			
		||||
            'choices' => [
 | 
			
		||||
                'event.budget.charges' => $charges,
 | 
			
		||||
                'event.budget.resources' => $resources,
 | 
			
		||||
            ],
 | 
			
		||||
            'choice_label' => fn (EventBudgetKind $kind) => $this->translatableStringHelper->localize($kind->getName()),
 | 
			
		||||
            'choice_value' => fn (?EventBudgetKind $kind) => $kind?->getId(),
 | 
			
		||||
            'placeholder' => 'event.budget.Select a budget element kind',
 | 
			
		||||
        ])
 | 
			
		||||
            ->add('amount', NumberType::class, [
 | 
			
		||||
                'required' => true,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('comment', CommentType::class, [
 | 
			
		||||
                'label' => 'Comment',
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver): void
 | 
			
		||||
    {
 | 
			
		||||
        $resolver->setDefaults([
 | 
			
		||||
            'data_class' => EventBudgetElement::class,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/Bundle/ChillEventBundle/Form/EventBudgetKindType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/Bundle/ChillEventBundle/Form/EventBudgetKindType.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
 | 
			
		||||
use Chill\EventBundle\Entity\EventBudgetKind;
 | 
			
		||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
 | 
			
		||||
class EventBudgetKindType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private readonly TranslatorInterface $translator) {}
 | 
			
		||||
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options)
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('name', TranslatableStringFormType::class, [
 | 
			
		||||
                'label' => 'Title',
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('type', EnumType::class, [
 | 
			
		||||
                'class' => BudgetTypeEnum::class,
 | 
			
		||||
                'choice_label' => fn (BudgetTypeEnum $type): string => $this->translator->trans($type->value),
 | 
			
		||||
                'expanded' => true,
 | 
			
		||||
                'multiple' => false,
 | 
			
		||||
                'mapped' => true,
 | 
			
		||||
                'label' => 'event.admin.Select budget type',
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('isActive', CheckboxType::class, [
 | 
			
		||||
                'label' => 'Actif ?',
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver)
 | 
			
		||||
    {
 | 
			
		||||
        $resolver
 | 
			
		||||
            ->setDefault('class', EventBudgetKind::class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										67
									
								
								src/Bundle/ChillEventBundle/Form/EventThemeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/Bundle/ChillEventBundle/Form/EventThemeType.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\EventTheme;
 | 
			
		||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
 | 
			
		||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
 | 
			
		||||
class EventThemeType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper) {}
 | 
			
		||||
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options): void
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('name', TranslatableStringFormType::class, [
 | 
			
		||||
                'label' => 'Nom',
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        if ('create' === $options['step']) {
 | 
			
		||||
            $builder
 | 
			
		||||
                ->add('parent', EntityType::class, [
 | 
			
		||||
                    'class' => EventTheme::class,
 | 
			
		||||
                    'required' => false,
 | 
			
		||||
                    'choice_label' => fn (EventTheme $theme): ?string => $this->translatableStringHelper->localize($theme->getName()),
 | 
			
		||||
                    'mapped' => 'create' == $options['step'],
 | 
			
		||||
                ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('ordering', NumberType::class, [
 | 
			
		||||
                'required' => true,
 | 
			
		||||
                'scale' => 6,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('isActive', ChoiceType::class, [
 | 
			
		||||
                'choices' => [
 | 
			
		||||
                    'Yes' => true,
 | 
			
		||||
                    'No' => false,
 | 
			
		||||
                ],
 | 
			
		||||
                'expanded' => true,
 | 
			
		||||
            ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver): void
 | 
			
		||||
    {
 | 
			
		||||
        $resolver->setDefaults([
 | 
			
		||||
            'data_class' => EventTheme::class,
 | 
			
		||||
        ]);
 | 
			
		||||
        $resolver->setRequired('step')
 | 
			
		||||
            ->setAllowedValues('step', ['create', 'edit']);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,25 +13,39 @@ namespace Chill\EventBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\DocStoreBundle\Entity\StoredObject;
 | 
			
		||||
use Chill\DocStoreBundle\Form\StoredObjectType;
 | 
			
		||||
use Chill\EventBundle\Entity\BudgetTypeEnum;
 | 
			
		||||
use Chill\EventBundle\Entity\Event;
 | 
			
		||||
use Chill\EventBundle\Form\Type\PickEventThemeType;
 | 
			
		||||
use Chill\EventBundle\Form\Type\PickEventTypeType;
 | 
			
		||||
use Chill\EventBundle\Repository\EventBudgetKindRepository;
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillDateTimeType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\CommentType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserLocationType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ScopePickerType;
 | 
			
		||||
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
 | 
			
		||||
class EventType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options)
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private readonly IdToLocationDataTransformer $idToLocationDataTransformer,
 | 
			
		||||
        private readonly EventBudgetKindRepository $eventBudgetKindRepository,
 | 
			
		||||
        private readonly TranslatorInterface $translator,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options): void
 | 
			
		||||
    {
 | 
			
		||||
        $chargeKinds = $this->eventBudgetKindRepository->findByType(BudgetTypeEnum::CHARGE->value);
 | 
			
		||||
        $resourceKinds = $this->eventBudgetKindRepository->findByType(BudgetTypeEnum::RESOURCE->value);
 | 
			
		||||
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('name', TextType::class, [
 | 
			
		||||
                'required' => true,
 | 
			
		||||
@@ -49,11 +63,28 @@ class EventType extends AbstractType
 | 
			
		||||
                    'class' => '',
 | 
			
		||||
                ],
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('themes', PickEventThemeType::class, [
 | 
			
		||||
                'multiple' => true,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('moderator', PickUserDynamicType::class, [
 | 
			
		||||
                'label' => 'Pick a moderator',
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('location', PickUserLocationType::class, [
 | 
			
		||||
                'label' => 'event.fields.location',
 | 
			
		||||
            ->add('animatorsIntern', PickUserDynamicType::class, [
 | 
			
		||||
                'multiple' => true,
 | 
			
		||||
                'label' => $this->translator->trans('event.fields.internal animators'),
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('animatorsExtern', PickThirdpartyDynamicType::class, [
 | 
			
		||||
                'multiple' => true,
 | 
			
		||||
                'label' => $this->translator->trans('event.fields.external animators'),
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('budgetElements', ChillCollectionType::class, [
 | 
			
		||||
                'entry_type' => AddEventBudgetElementType::class,
 | 
			
		||||
                'entry_options' => ['label' => false],
 | 
			
		||||
                'allow_add' => true,
 | 
			
		||||
                'allow_delete' => true,
 | 
			
		||||
                'by_reference' => false,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('comment', CommentType::class, [
 | 
			
		||||
                'label' => 'Comment',
 | 
			
		||||
@@ -69,11 +100,11 @@ class EventType extends AbstractType
 | 
			
		||||
                'delete_empty' => fn (StoredObject $storedObject): bool => '' === $storedObject->getFilename(),
 | 
			
		||||
                'button_remove_label' => 'event.form.remove_document',
 | 
			
		||||
                'button_add_label' => 'event.form.add_document',
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('organizationCost', MoneyType::class, [
 | 
			
		||||
                'label' => 'event.fields.organizationCost',
 | 
			
		||||
                'help' => 'event.form.organisationCost_help',
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        $builder->add('location', HiddenType::class)
 | 
			
		||||
            ->get('location')
 | 
			
		||||
            ->addModelTransformer($this->idToLocationDataTransformer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver)
 | 
			
		||||
@@ -87,11 +118,9 @@ class EventType extends AbstractType
 | 
			
		||||
            ->setAllowedTypes('role', 'string');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function getBlockPrefix()
 | 
			
		||||
    public function getBlockPrefix(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'chill_eventbundle_event';
 | 
			
		||||
        // as the js shares some hardcoded items from the activity bundle, we have to rewrite block prefix
 | 
			
		||||
        return 'chill_activitybundle_activity';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/Bundle/ChillEventBundle/Form/Type/PickEventThemeType.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Form\Type;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\EventTheme;
 | 
			
		||||
use Chill\EventBundle\Repository\EventThemeRepository;
 | 
			
		||||
use Chill\EventBundle\Templating\Entity\EventThemeRender;
 | 
			
		||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
 | 
			
		||||
class PickEventThemeType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private readonly EventThemeRender $eventThemeRender, private readonly EventThemeRepository $eventThemeRepository) {}
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver)
 | 
			
		||||
    {
 | 
			
		||||
        $resolver
 | 
			
		||||
            ->setDefaults([
 | 
			
		||||
                'class' => EventTheme::class,
 | 
			
		||||
                'choices' => $this->eventThemeRepository->findByActiveOrdered(),
 | 
			
		||||
                'choice_label' => fn (EventTheme $et) => $this->eventThemeRender->renderString($et, []),
 | 
			
		||||
                'placeholder' => 'event.form.Select one or more themes',
 | 
			
		||||
                'required' => true,
 | 
			
		||||
                'attr' => ['class' => 'select2'],
 | 
			
		||||
                'label' => 'event.theme.label',
 | 
			
		||||
                'multiple' => false,
 | 
			
		||||
            ])
 | 
			
		||||
            ->setAllowedTypes('multiple', ['bool']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getParent(): string
 | 
			
		||||
    {
 | 
			
		||||
        return EntityType::class;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -23,15 +23,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
 */
 | 
			
		||||
class PickEventTypeType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var TranslatableStringHelper
 | 
			
		||||
     */
 | 
			
		||||
    protected $translatableStringHelper;
 | 
			
		||||
 | 
			
		||||
    public function __construct(TranslatableStringHelper $helper)
 | 
			
		||||
    {
 | 
			
		||||
        $this->translatableStringHelper = $helper;
 | 
			
		||||
    }
 | 
			
		||||
    public function __construct(protected TranslatableStringHelper $translatableStringHelper) {}
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,17 +17,9 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
 | 
			
		||||
 | 
			
		||||
class AdminMenuBuilder implements LocalMenuBuilderInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var AuthorizationCheckerInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $authorizationChecker;
 | 
			
		||||
    public function __construct(protected AuthorizationCheckerInterface $authorizationChecker) {}
 | 
			
		||||
 | 
			
		||||
    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorizationChecker = $authorizationChecker;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildMenu($menuId, MenuItem $menu, array $parameters)
 | 
			
		||||
    public function buildMenu($menuId, MenuItem $menu, array $parameters): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
 | 
			
		||||
            return;
 | 
			
		||||
@@ -52,6 +44,14 @@ class AdminMenuBuilder implements LocalMenuBuilderInterface
 | 
			
		||||
        $menu->addChild('Role', [
 | 
			
		||||
            'route' => 'chill_event_admin_role',
 | 
			
		||||
        ])->setExtras(['order' => 6530]);
 | 
			
		||||
 | 
			
		||||
        $menu->addChild('event.theme.label', [
 | 
			
		||||
            'route' => 'chill_crud_event_theme_index',
 | 
			
		||||
        ])->setExtras(['order' => 6540]);
 | 
			
		||||
 | 
			
		||||
        $menu->addChild('event.budget.label', [
 | 
			
		||||
            'route' => 'chill_crud_event_budget_kind_index',
 | 
			
		||||
        ])->setExtras(['order' => 6550]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function getMenuIds(): array
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,16 @@ final readonly class EventACLAwareRepository implements EventACLAwareRepositoryI
 | 
			
		||||
            $qb->andWhere('event.type IN (:event_types)');
 | 
			
		||||
            $qb->setParameter('event_types', $filters['event_types']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (0 < count($filters['centers'] ?? [])) {
 | 
			
		||||
            $qb->andWhere('event.center IN (:centers)');
 | 
			
		||||
            $qb->setParameter('centers', $filters['centers']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (0 < count($filters['responsables'] ?? [])) {
 | 
			
		||||
            $qb->andWhere('event.moderator IN (:responsables)');
 | 
			
		||||
            $qb->setParameter('responsables', $filters['responsables']);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildQueryByAllViewable(array $filters): QueryBuilder
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\EventBudgetElement;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @extends ServiceEntityRepository<EventBudgetElement>
 | 
			
		||||
 */
 | 
			
		||||
class EventBudgetElementRepository extends ServiceEntityRepository
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(ManagerRegistry $registry)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, EventBudgetElement::class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,46 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\EventBudgetKind;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @extends ServiceEntityRepository<EventBudgetKind>
 | 
			
		||||
 */
 | 
			
		||||
class EventBudgetKindRepository extends ServiceEntityRepository
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(ManagerRegistry $registry)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, EventBudgetKind::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByActive(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createQueryBuilder('e')
 | 
			
		||||
            ->select('e')
 | 
			
		||||
            ->where('e.active = True')
 | 
			
		||||
            ->getQuery()
 | 
			
		||||
            ->getResult();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByType(string $type): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createQueryBuilder('e')
 | 
			
		||||
            ->select('e')
 | 
			
		||||
            ->where('e.type = :type')
 | 
			
		||||
            ->setParameter('type', $type)
 | 
			
		||||
            ->getQuery()
 | 
			
		||||
            ->getResult();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\EventTheme;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @extends ServiceEntityRepository<EventTheme>
 | 
			
		||||
 */
 | 
			
		||||
class EventThemeRepository extends ServiceEntityRepository
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(ManagerRegistry $registry)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, EventTheme::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function findByActiveOrdered(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createQueryBuilder('t')
 | 
			
		||||
            ->select('t')
 | 
			
		||||
            ->where('t.isActive = True')
 | 
			
		||||
            ->orderBy('t.ordering', 'ASC')
 | 
			
		||||
            ->getQuery()
 | 
			
		||||
            ->getResult();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -55,3 +55,13 @@ form#export_tableur {
 | 
			
		||||
    -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none;
 | 
			
		||||
    transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.participation-list {
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.participations-wrapper {
 | 
			
		||||
    background-color: whitesmoke;
 | 
			
		||||
    padding: 1rem;
 | 
			
		||||
    margin-bottom: .5rem;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/App.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
<template>
 | 
			
		||||
    <location />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Location from "ChillActivityAssets/vuejs/Activity/components/Location.vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
    name: "App",
 | 
			
		||||
    components: {
 | 
			
		||||
        Location,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
import { createApp } from "vue";
 | 
			
		||||
 | 
			
		||||
import App from "./App.vue";
 | 
			
		||||
import store from "./store";
 | 
			
		||||
 | 
			
		||||
createApp(App).use(store).mount("#event");
 | 
			
		||||
							
								
								
									
										76
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/Bundle/ChillEventBundle/Resources/public/vuejs/store.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
			
		||||
import "es6-promise/auto";
 | 
			
		||||
import { createStore } from "vuex";
 | 
			
		||||
 | 
			
		||||
import prepareLocations from "ChillActivityAssets/vuejs/Activity/store.locations";
 | 
			
		||||
import { whoami } from "ChillMainAssets/lib/api/user";
 | 
			
		||||
import { postLocation } from "ChillActivityAssets/vuejs/Activity/api";
 | 
			
		||||
 | 
			
		||||
const debug = process.env.NODE_ENV !== "production";
 | 
			
		||||
 | 
			
		||||
const store = createStore({
 | 
			
		||||
  strict: debug,
 | 
			
		||||
  state: {
 | 
			
		||||
    activity: window.entity, // activity is the event entity in this case (re-using component from activity bundle)
 | 
			
		||||
    currentEvent: null,
 | 
			
		||||
    availableLocations: [],
 | 
			
		||||
    me: null,
 | 
			
		||||
  },
 | 
			
		||||
  getters: {},
 | 
			
		||||
  actions: {
 | 
			
		||||
    addAvailableLocationGroup({ commit }, payload) {
 | 
			
		||||
      commit("addAvailableLocationGroup", payload);
 | 
			
		||||
    },
 | 
			
		||||
    updateLocation({ commit }, value) {
 | 
			
		||||
      // console.log("### action: updateLocation", value);
 | 
			
		||||
      let hiddenLocation = document.getElementById(
 | 
			
		||||
        "chill_activitybundle_activity_location",
 | 
			
		||||
      );
 | 
			
		||||
      if (value.onthefly) {
 | 
			
		||||
        const body = {
 | 
			
		||||
          type: "location",
 | 
			
		||||
          name:
 | 
			
		||||
            value.name === "__AccompanyingCourseLocation__" ? null : value.name,
 | 
			
		||||
          locationType: {
 | 
			
		||||
            id: value.locationType.id,
 | 
			
		||||
            type: "location-type",
 | 
			
		||||
          },
 | 
			
		||||
        };
 | 
			
		||||
        if (value.address.id) {
 | 
			
		||||
          Object.assign(body, {
 | 
			
		||||
            address: {
 | 
			
		||||
              id: value.address.id,
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        postLocation(body)
 | 
			
		||||
          .then((location) => (hiddenLocation.value = location.id))
 | 
			
		||||
          .catch((err) => {
 | 
			
		||||
            console.log(err.message);
 | 
			
		||||
          });
 | 
			
		||||
      } else {
 | 
			
		||||
        hiddenLocation.value = value.id;
 | 
			
		||||
      }
 | 
			
		||||
      commit("updateLocation", value);
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  mutations: {
 | 
			
		||||
    setWhoAmiI(state, me) {
 | 
			
		||||
      state.me = me;
 | 
			
		||||
    },
 | 
			
		||||
    addAvailableLocationGroup(state, group) {
 | 
			
		||||
      state.availableLocations.push(group);
 | 
			
		||||
    },
 | 
			
		||||
    updateLocation(state, value) {
 | 
			
		||||
      // console.log("### mutation: updateLocation", value);
 | 
			
		||||
      state.activity.location = value;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
whoami().then((me) => {
 | 
			
		||||
  store.commit("setWhoAmiI", me);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
prepareLocations(store);
 | 
			
		||||
 | 
			
		||||
export default store;
 | 
			
		||||
@@ -0,0 +1,12 @@
 | 
			
		||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% include('@ChillMain/CRUD/_edit_title.html.twig') %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block admin_content %}
 | 
			
		||||
    {% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
 | 
			
		||||
        {% block content_form_actions_view %}{% endblock %}
 | 
			
		||||
        {% block content_form_actions_save_and_show %}{% endblock %}
 | 
			
		||||
    {% endembed %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ 'event.admin.title.Event budget element list'|trans }}{% endblock title %}
 | 
			
		||||
 | 
			
		||||
{% block admin_content %}
 | 
			
		||||
 | 
			
		||||
    <h1>{{ 'event.admin.title.Event budget element list'|trans }}</h1>
 | 
			
		||||
 | 
			
		||||
	<table class="records_list table table-bordered border-dark">
 | 
			
		||||
		<thead>
 | 
			
		||||
			<tr>
 | 
			
		||||
				<th>{{ 'Name'|trans }}</th>
 | 
			
		||||
                <th>{{ 'Type'|trans }}</th>
 | 
			
		||||
				<th>{{ 'Active'|trans }}</th>
 | 
			
		||||
                <th> </th>
 | 
			
		||||
			</tr>
 | 
			
		||||
		</thead>
 | 
			
		||||
		<tbody>
 | 
			
		||||
			{% for entity in entities %}
 | 
			
		||||
				<tr>
 | 
			
		||||
					<td>{{ entity.name|localize_translatable_string }}</td>
 | 
			
		||||
                    <td>{{ entity.type.value|trans }}</td>
 | 
			
		||||
					<td style="text-align:center;">
 | 
			
		||||
						{%- if entity.isActive -%}
 | 
			
		||||
							<i class="fa fa-check-square-o"></i>
 | 
			
		||||
						{%- else -%}
 | 
			
		||||
							<i class="fa fa-square-o"></i>
 | 
			
		||||
						{%- endif -%}
 | 
			
		||||
					</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
						<ul class="record_actions">
 | 
			
		||||
							<li>
 | 
			
		||||
								<a href="{{ path('chill_crud_event_budget_kind_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
 | 
			
		||||
							</li>
 | 
			
		||||
						</ul>
 | 
			
		||||
					</td>
 | 
			
		||||
				</tr>
 | 
			
		||||
			{% endfor %}
 | 
			
		||||
		</tbody>
 | 
			
		||||
	</table>
 | 
			
		||||
 | 
			
		||||
    {{ chill_pagination(paginator) }}
 | 
			
		||||
 | 
			
		||||
	<ul class="record_actions sticky-form-buttons">
 | 
			
		||||
		<li>
 | 
			
		||||
			<a href="{{ path('chill_crud_event_budget_kind_new') }}" class="btn btn-create">
 | 
			
		||||
				{{ 'event.admin.new.Create a new budget kind'|trans }}
 | 
			
		||||
			</a>
 | 
			
		||||
		</li>
 | 
			
		||||
	</ul>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% include('@ChillMain/CRUD/_new_title.html.twig') %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block admin_content %}
 | 
			
		||||
    {% embed '@ChillMain/CRUD/_new_content.html.twig' %}
 | 
			
		||||
        {% block content_form_actions_save_and_show %}{% endblock %}
 | 
			
		||||
    {% endembed %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% include('@ChillMain/CRUD/_edit_title.html.twig') %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block admin_content %}
 | 
			
		||||
    {% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
 | 
			
		||||
 | 
			
		||||
        {% block crud_content_form_rows %}
 | 
			
		||||
            {{ form_row(form.name) }}
 | 
			
		||||
            <div class="mb-3 row">
 | 
			
		||||
                <label class="col-form-label col-sm-4">
 | 
			
		||||
                    {{ 'Parent'|trans }}
 | 
			
		||||
                </label>
 | 
			
		||||
                <div class="col-sm-8">
 | 
			
		||||
                    {{ entity.parent|chill_entity_render_box }}
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {{ form_row(form.ordering) }}
 | 
			
		||||
            {{ form_row(form.isActive) }}
 | 
			
		||||
        {% endblock crud_content_form_rows %}
 | 
			
		||||
 | 
			
		||||
        {% block content_form_actions_save_and_show %}{% endblock %}
 | 
			
		||||
    {% endembed %}
 | 
			
		||||
{% endblock admin_content %}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block admin_content %}
 | 
			
		||||
    {% embed '@ChillMain/CRUD/_index.html.twig' %}
 | 
			
		||||
        {% block table_entities_thead_tr %}
 | 
			
		||||
            <th>{{ 'Id'|trans }}</th>
 | 
			
		||||
            <th>{{ 'Title'|trans }}</th>
 | 
			
		||||
            <th>{{ 'Ordering'|trans }}</th>
 | 
			
		||||
            <th>{{ 'active'|trans }}</th>
 | 
			
		||||
            <th> </th>
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
 | 
			
		||||
        {% block table_entities_tbody %}
 | 
			
		||||
            {% for entity in entities %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>{{ entity.id }}</td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        {{ entity|chill_entity_render_box }}
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>{{ entity.ordering }}</td>
 | 
			
		||||
                    <td style="text-align:center;">
 | 
			
		||||
                        {%- if entity.isActive -%}
 | 
			
		||||
                            <i class="fa fa-check-square-o"></i>
 | 
			
		||||
                        {%- else -%}
 | 
			
		||||
                            <i class="fa fa-square-o"></i>
 | 
			
		||||
                        {%- endif -%}
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <ul class="record_actions">
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ chill_path_add_return_path('chill_crud_event_theme_edit', { 'id': entity.id }) }}" class="btn btn-edit"></a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
 | 
			
		||||
        {% block actions_before %}
 | 
			
		||||
            <li class='cancel'>
 | 
			
		||||
                <a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
 | 
			
		||||
            </li>
 | 
			
		||||
        {% endblock %}
 | 
			
		||||
    {% endembed %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% block title %}
 | 
			
		||||
    {% include('@ChillMain/CRUD/_new_title.html.twig') %}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block admin_content %}
 | 
			
		||||
    {% embed '@ChillMain/CRUD/_new_content.html.twig' %}
 | 
			
		||||
        {% block content_form_actions_save_and_show %}{% endblock %}
 | 
			
		||||
    {% endembed %}
 | 
			
		||||
{% endblock admin_content %}
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
{% set reversed_parents = parents|reverse %}
 | 
			
		||||
<span class="chill-entity entity-event-theme">
 | 
			
		||||
  <span class="badge bg-chill-l-gray text-dark">
 | 
			
		||||
    {%- for p in reversed_parents %}
 | 
			
		||||
        <span class="parent-{{ loop.revindex0 }}">
 | 
			
		||||
      {{ p.name|localize_translatable_string }}{{ options['default.separator'] }}
 | 
			
		||||
    </span>
 | 
			
		||||
    {%- endfor -%}
 | 
			
		||||
    <span class="child">
 | 
			
		||||
      {{ eventTheme.name|localize_translatable_string }}
 | 
			
		||||
    </span>
 | 
			
		||||
  </span>
 | 
			
		||||
</span>
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
            'title'             : 'Delete event'|trans,
 | 
			
		||||
            'confirm_question'  : 'Are you sure you want to remove that event ?'|trans,
 | 
			
		||||
            'cancel_route'      : activeRouteKey,
 | 
			
		||||
            'cancel_parameters' : { 'event_id' : event_id },
 | 
			
		||||
            'cancel_parameters' : { 'id' : id },
 | 
			
		||||
            'form'              : delete_form
 | 
			
		||||
        }
 | 
			
		||||
    ) }}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,12 @@
 | 
			
		||||
    {{ form_row(edit_form.date) }}
 | 
			
		||||
 | 
			
		||||
    {{ form_row(edit_form.type, { label: "Event type" }) }}
 | 
			
		||||
    {{ form_row(edit_form.themes) }}
 | 
			
		||||
    {{ form_row(edit_form.moderator) }}
 | 
			
		||||
    {{ form_row(edit_form.animatorsIntern) }}
 | 
			
		||||
    {{ form_row(edit_form.animatorsExtern) }}
 | 
			
		||||
    {{ form_row(edit_form.location) }}
 | 
			
		||||
    {{ form_row(edit_form.organizationCost) }}
 | 
			
		||||
 | 
			
		||||
    {{ form_row(edit_form.budgetElements) }}
 | 
			
		||||
    {{ form_row(edit_form.comment) }}
 | 
			
		||||
    {{ form_row(edit_form.documents) }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
                    <ul class="record_actions">
 | 
			
		||||
                        <li>
 | 
			
		||||
                            {# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #}
 | 
			
		||||
                            <a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-view">
 | 
			
		||||
                            <a href="{{ path('chill_event__event_show', { 'id' : event.id } ) }}" class="btn btn-view">
 | 
			
		||||
                                {{ 'See'|trans }}
 | 
			
		||||
                            </a>
 | 
			
		||||
                            {# {% endif %} #}
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
                                {% set returnLabel = 'Back to %person% events'|trans({ '%person%' : currentPerson } ) %}
 | 
			
		||||
 | 
			
		||||
                                {% if is_granted('CHILL_EVENT_SEE_DETAILS', participation.event) %}
 | 
			
		||||
                                    <a  href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
 | 
			
		||||
                                    <a  href="{{ path('chill_event__event_show', { 'id' : participation.event.id, 'return_path' : currentPath, 'return_label' : returnLabel } ) }}"
 | 
			
		||||
                                    class="btn btn-primary btn-sm" title="{{ 'See details of the event'|trans }}">
 | 
			
		||||
                                    <i class="fa fa-fw fa-eye"></i>
 | 
			
		||||
                                </a>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,30 @@
 | 
			
		||||
{% extends '@ChillEvent/layout.html.twig' %} {% block js %}
 | 
			
		||||
{{ encore_entry_script_tags("mod_async_upload") }}
 | 
			
		||||
{{ encore_entry_script_tags("mod_pickentity_type") }}
 | 
			
		||||
{% extends '@ChillEvent/layout.html.twig' %}
 | 
			
		||||
 | 
			
		||||
{% endblock %} {% block css %}
 | 
			
		||||
{% block css %}
 | 
			
		||||
{{ encore_entry_link_tags("mod_async_upload") }}
 | 
			
		||||
{{ encore_entry_link_tags("mod_pickentity_type") }}
 | 
			
		||||
{{ encore_entry_link_tags('vue_event') }}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% endblock %} {% block title 'Event creation'|trans %} {% block event_content
 | 
			
		||||
-%}
 | 
			
		||||
{% block title 'Event creation'|trans %}
 | 
			
		||||
 | 
			
		||||
{% block event_content -%}
 | 
			
		||||
<div class="col-10">
 | 
			
		||||
    <h1>{{ "Event creation" | trans }}</h1>
 | 
			
		||||
 | 
			
		||||
    {{ form_start(form) }}
 | 
			
		||||
    {{ form_errors(form) }}
 | 
			
		||||
    {{ form_row(form.circle) }}
 | 
			
		||||
    {{ form_row(form.name) }}
 | 
			
		||||
    {{ form_row(form.circle) }}
 | 
			
		||||
    {{ form_row(form.date) }}
 | 
			
		||||
    {{ form_row(form.type, { label: "Event type" }) }}
 | 
			
		||||
    {{ form_row(form.themes) }}
 | 
			
		||||
    {{ form_row(form.moderator) }}
 | 
			
		||||
    {{ form_row(form.animatorsIntern) }}
 | 
			
		||||
    {{ form_row(form.animatorsExtern) }}
 | 
			
		||||
    {{ form_row(form.location) }}
 | 
			
		||||
    {{ form_row(form.organizationCost) }}
 | 
			
		||||
    <div id="location"></div>
 | 
			
		||||
    {{ form_row(form.budgetElements) }}
 | 
			
		||||
    {{ form_row(form.comment) }}
 | 
			
		||||
    {{ form_row(form.documents) }}
 | 
			
		||||
 | 
			
		||||
@@ -40,5 +45,18 @@
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    {{ form_end(form) }}
 | 
			
		||||
 | 
			
		||||
    <div id="event"></div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block js %}
 | 
			
		||||
    {{ encore_entry_script_tags("mod_async_upload") }}
 | 
			
		||||
    {{ encore_entry_script_tags("mod_pickentity_type") }}
 | 
			
		||||
    {{ encore_entry_script_tags('vue_event') }}
 | 
			
		||||
    <script type="text/javascript">
 | 
			
		||||
        window.entity = {{ entity_json|json_encode|raw }};
 | 
			
		||||
        {% if app.user.currentLocation is not null %}window.default_location_id = {{ app.user.currentLocation.id }};{% endif %}
 | 
			
		||||
    </script>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ block js %}
 | 
			
		||||
 | 
			
		||||
    {{ filter | chill_render_filter_order_helper }}
 | 
			
		||||
 | 
			
		||||
    {# {% if is_granted('CHILL_EVENT_CREATE') %} #}
 | 
			
		||||
    {% if is_granted('CHILL_EVENT_CREATE') %}
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
        <li>
 | 
			
		||||
            <a
 | 
			
		||||
@@ -25,7 +25,9 @@ block js %}
 | 
			
		||||
            >
 | 
			
		||||
        </li>
 | 
			
		||||
    </ul>
 | 
			
		||||
    {# {% endif %} #} {% if events|length > 0 %}
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    {% if events|length > 0 %}
 | 
			
		||||
    <div class="flex-table">
 | 
			
		||||
        {% for e in events %}
 | 
			
		||||
        <div class="item-bloc">
 | 
			
		||||
@@ -41,6 +43,12 @@ block js %}
 | 
			
		||||
                        {{ e.moderator | chill_entity_render_box }}
 | 
			
		||||
                    </p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
 | 
			
		||||
                    <div>
 | 
			
		||||
                        {% for t in e.themes %}
 | 
			
		||||
                            <span>{{ t|chill_entity_render_box }}</span>
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="item-col">
 | 
			
		||||
                    <div class="container" style="text-align: right">
 | 
			
		||||
@@ -48,20 +56,21 @@ block js %}
 | 
			
		||||
                        <p>
 | 
			
		||||
                            {{ 'count participations to this event'|trans({'count': e.participations|length}) }}
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p>{{ "center"|trans }}: {{ e.center.name }}</p>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% if e.participations|length > 0 %}
 | 
			
		||||
            <div class="item-row separator">
 | 
			
		||||
            <div class="participation-list item-row separator">
 | 
			
		||||
                <strong>{{ "Participations" | trans }} : </strong>
 | 
			
		||||
                {% for part in e.participations|slice(0, 20) %} {% include
 | 
			
		||||
                {% for part in e.participations|slice(0, 5) %} {% include
 | 
			
		||||
                '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
 | 
			
		||||
                targetEntity: { name: 'person', id: part.person.id }, action:
 | 
			
		||||
                'show', displayBadge: true, buttonText:
 | 
			
		||||
                part.person|chill_entity_render_string, isDead:
 | 
			
		||||
                part.person.deathdate is not null } %} {% endfor %} {% if
 | 
			
		||||
                e.participations|length > 20 %}
 | 
			
		||||
                {{ 'events.and_other_count_participants'|trans({'count': e.participations|length - 20}) }}
 | 
			
		||||
                part.person.deathdate is not null } %} {% endfor %}
 | 
			
		||||
                {% if e.participations|length > 5 %}
 | 
			
		||||
                {{ 'events.and_other_count_participants'|trans({'count': e.participations|length - 5}) }}
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
@@ -106,7 +115,7 @@ block js %}
 | 
			
		||||
                                href="{{
 | 
			
		||||
                                    chill_path_add_return_path(
 | 
			
		||||
                                        'chill_event__event_show',
 | 
			
		||||
                                        { event_id: e.id }
 | 
			
		||||
                                        { id: e.id }
 | 
			
		||||
                                    )
 | 
			
		||||
                                }}"
 | 
			
		||||
                                class="btn btn-show"
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,16 @@
 | 
			
		||||
    {{ encore_entry_link_tags('mod_document_action_buttons_group') }}
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% macro insert_onthefly(type, entity, parent = null) %}
 | 
			
		||||
    {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
 | 
			
		||||
        action: 'show', displayBadge: true,
 | 
			
		||||
        targetEntity: { name: type, id: entity.id },
 | 
			
		||||
        buttonText: entity|chill_entity_render_string,
 | 
			
		||||
        isDead: entity.deathdate is defined and entity.deathdate is not null,
 | 
			
		||||
        parent: parent
 | 
			
		||||
    } %}
 | 
			
		||||
{% endmacro %}
 | 
			
		||||
 | 
			
		||||
{% block event_content -%}
 | 
			
		||||
<div class="col-10">
 | 
			
		||||
    <h1>{{ 'Details of an event'|trans }}</h1>
 | 
			
		||||
@@ -26,6 +36,10 @@
 | 
			
		||||
                <th>{{ 'Circle'|trans }}</th>
 | 
			
		||||
                <td>{{ event.circle.name|localize_translatable_string }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'Center'|trans }}</th>
 | 
			
		||||
                <td>{{ event.center.name }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'Name'|trans }}</th>
 | 
			
		||||
                <td>{{ event.name }}</td>
 | 
			
		||||
@@ -39,12 +53,32 @@
 | 
			
		||||
                <td>{{ event.type.name|localize_translatable_string }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'Moderator'|trans }}</th>
 | 
			
		||||
                <td>{{ event.moderator|trans|default('-') }}</td>
 | 
			
		||||
                <th>{{ 'event.theme.label'|trans }}
 | 
			
		||||
                <td>
 | 
			
		||||
                {% for t in event.themes %}
 | 
			
		||||
                    {{ t|chill_entity_render_box }}
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'event.fields.organizationCost'|trans }}</th>
 | 
			
		||||
                <td>{{ event.organizationCost|format_currency('EUR') }}</td>
 | 
			
		||||
                <th>{{ 'Moderator'|trans }}</th>
 | 
			
		||||
                <td>{{ event.moderator|chill_entity_render_string }}</td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'event.animators.intern'|trans }}</th>
 | 
			
		||||
                <td>
 | 
			
		||||
                    {% for a in event.animatorsIntern %}
 | 
			
		||||
                        {{ _self.insert_onthefly('user', a) }}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'event.animators.extern'|trans }}</th>
 | 
			
		||||
                <td>
 | 
			
		||||
                    {% for a in event.animatorsExtern %}
 | 
			
		||||
                        {{ _self.insert_onthefly('thirdparty', a) }}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>{{ 'event.fields.location'|trans }}</th>
 | 
			
		||||
@@ -60,6 +94,77 @@
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <div class="budget-wrapper" style="background-color: whitesmoke; padding: 1rem; margin-bottom: .5rem;">
 | 
			
		||||
        {% set resources = event.budgetElements|filter(e => e.kind.type.value == 'Resource') %}
 | 
			
		||||
        {% set charges = event.budgetElements|filter(e => e.kind.type.value == 'Charge') %}
 | 
			
		||||
 | 
			
		||||
        <h2>Budget de l'événement</h2>
 | 
			
		||||
 | 
			
		||||
        <h3>Ressources</h3>
 | 
			
		||||
        {% if resources is not empty %}
 | 
			
		||||
            <table class="table table-bordered border-dark align-middle">
 | 
			
		||||
                <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>{{ 'event.budget.resource type'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'event.budget.amount'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'event.budget.comment'|trans }}</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                {% set totalResources = 0 %}
 | 
			
		||||
                {% for res in resources %}
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>{{ res.kind.name|localize_translatable_string }}</td>
 | 
			
		||||
                        <td>{{ res.amount|format_currency('EUR') }}</td>
 | 
			
		||||
                        <td>{{ res.comment.comment|chill_print_or_message(null, 'blockquote') }}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    {% set totalResources = totalResources + res.amount %}
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
                </tbody>
 | 
			
		||||
                <tfoot>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>Total</th>
 | 
			
		||||
                    <td>{{ totalResources|format_currency('EUR') }}</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </tfoot>
 | 
			
		||||
            </table>
 | 
			
		||||
        {% else %}
 | 
			
		||||
            <p class="chill-no-data-statement">{{ 'event.budget.no elements'|trans }}</p>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
 | 
			
		||||
        <h3>Charges</h3>
 | 
			
		||||
        {% if charges is not empty %}
 | 
			
		||||
            <table class="table table-bordered border-dark align-middle">
 | 
			
		||||
                <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>{{ 'event.budget.charge type'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'event.budget.amount'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'event.budget.comment'|trans }}</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                {% set totalCharges = 0 %}
 | 
			
		||||
                {% for chg in charges %}
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>{{ chg.kind.name|localize_translatable_string }}</td>
 | 
			
		||||
                        <td>{{ chg.amount|format_currency('EUR') }}</td>
 | 
			
		||||
                        <td>{{ chg.comment.comment|chill_print_or_message(null, 'blockquote') }}</td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                    {% set totalCharges = totalCharges + chg.amount %}
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
                </tbody>
 | 
			
		||||
                <tfoot>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>Total</th>
 | 
			
		||||
                    <td>{{ totalCharges|format_currency('EUR') }}</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                </tfoot>
 | 
			
		||||
            </table>
 | 
			
		||||
        {% else %}
 | 
			
		||||
            <p class="chill-no-data-statement">{{ 'event.budget.no elements'|trans }}</p>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    {% if event.documents|length > 0 %}
 | 
			
		||||
        <div>
 | 
			
		||||
            <p><strong>{{ 'event.fields.documents'|trans }}</strong></p>
 | 
			
		||||
@@ -80,6 +185,97 @@
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <div class="participations-wrapper">
 | 
			
		||||
        <h2>{{ 'Participations'|trans }}</h2>
 | 
			
		||||
        {% set count = event.participations|length %}
 | 
			
		||||
        <p class="chill-no-data-statement">{{ 'count participations to this event'|trans({'count': count}) }}</p>
 | 
			
		||||
 | 
			
		||||
        {% if count > 0 %}
 | 
			
		||||
            <table class="table table-bordered border-dark align-middle">
 | 
			
		||||
                <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th>{{ 'Person'|trans }}</th>
 | 
			
		||||
                        <th>{{ 'Role'|trans }}</th>
 | 
			
		||||
                        <th>{{ 'Status'|trans }}</th>
 | 
			
		||||
                        <th>{{ 'Last update'|trans }}</th>
 | 
			
		||||
                        <th> </th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                {% for participation in event.participations %}
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
 | 
			
		||||
                                targetEntity: { name: 'person', id: participation.person.id },
 | 
			
		||||
                                action: 'show',
 | 
			
		||||
                                displayBadge: true,
 | 
			
		||||
                                buttonText: participation.person|chill_entity_render_string,
 | 
			
		||||
                                isDead: participation.person.deathdate is not null
 | 
			
		||||
                            } %}
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>{{ participation.role.name|localize_translatable_string }}</td>
 | 
			
		||||
                        <td>{{ participation.status.name|localize_translatable_string }}</td>
 | 
			
		||||
                        <td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
 | 
			
		||||
                             alternative: knplabs/knp-time-bundle provide filter 'ago' #}
 | 
			
		||||
                            <i class="fa fa-info-circle" title="{{ participation.lastUpdate|format_date("long")|escape('html_attr') }}"></i>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                            <ul class="record_actions">
 | 
			
		||||
                                {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
 | 
			
		||||
                                <li>
 | 
			
		||||
                                    <a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
 | 
			
		||||
                                       class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                <li>
 | 
			
		||||
                                    <a href="{{ path('chill_event_participation_delete', {'participation_id' : participation.id } ) }}"
 | 
			
		||||
                                        class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
 | 
			
		||||
                                </li>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
                </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        {% endif %}
 | 
			
		||||
 | 
			
		||||
        <div class="row">
 | 
			
		||||
            <div class="col-8">
 | 
			
		||||
                {{ form_start(form_add_participation_by_person) }}
 | 
			
		||||
                <div class="input-group">
 | 
			
		||||
                    {{ form_widget(form_add_participation_by_person.person_id, { 'attr' : {
 | 
			
		||||
                        'class' : 'custom-select',
 | 
			
		||||
                        'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
 | 
			
		||||
                    }} ) }}
 | 
			
		||||
                </div>
 | 
			
		||||
                <input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
 | 
			
		||||
                {{ form_end(form_add_participation_by_person) }}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <ul class="record_actions">
 | 
			
		||||
            {% if count > 0 %}
 | 
			
		||||
                <li>
 | 
			
		||||
                    {{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }}
 | 
			
		||||
                    <div class="input-group">
 | 
			
		||||
                        {{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }}
 | 
			
		||||
                        <div class="input-group-append">
 | 
			
		||||
                            {{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }}
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <a class="bt-"></a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    {{ form_rest(form_export) }}
 | 
			
		||||
                    {{ form_end(form_export) }}
 | 
			
		||||
                </li>
 | 
			
		||||
                <li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </ul>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="post_show">
 | 
			
		||||
        {{ chill_delegated_block('block_footer_show', { 'event': event }) }}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
 | 
			
		||||
@@ -100,97 +296,5 @@
 | 
			
		||||
        </li>
 | 
			
		||||
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <h2>{{ 'Participations'|trans }}</h2>
 | 
			
		||||
    {% set count = event.participations|length %}
 | 
			
		||||
    <p>{{ 'count participations to this event'|trans({'count': count}) }}</p>
 | 
			
		||||
 | 
			
		||||
    {% if count > 0 %}
 | 
			
		||||
        <table class="table table-bordered border-dark align-middle">
 | 
			
		||||
            <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <th>{{ 'Person'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'Role'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'Status'|trans }}</th>
 | 
			
		||||
                    <th>{{ 'Last update'|trans }}</th>
 | 
			
		||||
                    <th> </th>
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
            {% for participation in event.participations %}
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
 | 
			
		||||
                            targetEntity: { name: 'person', id: participation.person.id },
 | 
			
		||||
                            action: 'show',
 | 
			
		||||
                            displayBadge: true,
 | 
			
		||||
                            buttonText: participation.person|chill_entity_render_string,
 | 
			
		||||
                            isDead: participation.person.deathdate is not null
 | 
			
		||||
                        } %}
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>{{ participation.role.name|localize_translatable_string }}</td>
 | 
			
		||||
                    <td>{{ participation.status.name|localize_translatable_string }}</td>
 | 
			
		||||
                    <td>{{ participation.lastUpdate|ago }} {# sf4 check: filter 'time_diff' is abandoned,
 | 
			
		||||
                         alternative: knplabs/knp-time-bundle provide filter 'ago' #}
 | 
			
		||||
                        <i class="fa fa-info-circle" title="{{ participation.lastUpdate|format_date("long")|escape('html_attr') }}"></i>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <ul class="record_actions">
 | 
			
		||||
                            {% if is_granted('CHILL_EVENT_PARTICIPATION_UPDATE', participation) %}
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ chill_path_add_return_path('chill_event_participation_edit', { 'participation_id' : participation.id }, false, 'See'|trans ) }}"
 | 
			
		||||
                                   class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{{ path('chill_event_participation_delete', {'participation_id' : participation.id } ) }}"
 | 
			
		||||
                                    class="btn btn-delete" title="{{ 'Delete'|trans }}"></a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
            </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
        {% if count > 0 %}
 | 
			
		||||
            <li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </ul>
 | 
			
		||||
 | 
			
		||||
    <div class="row" style="margin-bottom: 10em;">
 | 
			
		||||
        <div class="col-8">
 | 
			
		||||
            {{ form_start(form_add_participation_by_person) }}
 | 
			
		||||
            <div class="input-group">
 | 
			
		||||
                {{ form_widget(form_add_participation_by_person.person_id, { 'attr' : {
 | 
			
		||||
                    'class' : 'custom-select',
 | 
			
		||||
                    'style': 'min-width: 15em; max-width: 18em; display: inline-block;'
 | 
			
		||||
                }} ) }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <input type="hidden" name="returnPath" value="{{ app.request.requestUri }}" />
 | 
			
		||||
            {{ form_end(form_add_participation_by_person) }}
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="col-4">
 | 
			
		||||
            {{ form_start(form_export, {'attr': {'id': 'export_tableur'}}) }}
 | 
			
		||||
            <div class="input-group">
 | 
			
		||||
                {{ form_widget(form_export.format, { 'attr' : { 'class': 'custom-select' } }) }}
 | 
			
		||||
                <div class="input-group-append">
 | 
			
		||||
                    {{ form_widget(form_export.submit, { 'attr' : { 'class': 'btn btn-save' } }) }}
 | 
			
		||||
                </div>
 | 
			
		||||
                <a class="bt-"></a>
 | 
			
		||||
            </div>
 | 
			
		||||
            {{ form_rest(form_export) }}
 | 
			
		||||
            {{ form_end(form_export) }}
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="post_show">
 | 
			
		||||
        {{ chill_delegated_block('block_footer_show', { 'event': event }) }}
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
            'title'             : 'Remove participation'|trans,
 | 
			
		||||
            'confirm_question'  : 'Are you sure you want to remove that participation ?'|trans,
 | 
			
		||||
            'cancel_route'      : activeRouteKey,
 | 
			
		||||
            'cancel_parameters' : { 'event_id' : event_id },
 | 
			
		||||
            'cancel_parameters' : { 'id' : event_id },
 | 
			
		||||
            'form'              : delete_form
 | 
			
		||||
        }
 | 
			
		||||
    ) }}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
            {% set returnPath = app.request.get('return_path') %}
 | 
			
		||||
            {% set returnLabel = app.request.get('return_label') %}
 | 
			
		||||
 | 
			
		||||
            <a href="{{ returnPath |default( path('chill_event__event_show', { 'event_id' : participation.event.id } )) }}" class="btn btn-cancel">
 | 
			
		||||
            <a href="{{ returnPath |default( path('chill_event__event_show', { 'id' : participation.event.id } )) }}" class="btn btn-cancel">
 | 
			
		||||
                {{ returnLabel |default('Back to the event'|trans) }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@
 | 
			
		||||
 | 
			
		||||
    <ul class="record_actions sticky-form-buttons">
 | 
			
		||||
        <li class="cancel">
 | 
			
		||||
            <a href="{{ path('chill_event__event_show', { 'event_id' : participation.event.id } ) }}" class="btn btn-cancel">
 | 
			
		||||
            <a href="{{ path('chill_event__event_show', { 'id' : participation.event.id } ) }}" class="btn btn-cancel">
 | 
			
		||||
                {{ 'Back to the event'|trans }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -54,9 +54,9 @@ class EventVoter extends AbstractChillVoter implements ProvideRoleHierarchyInter
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->voterHelper = $voterHelperFactory
 | 
			
		||||
            ->generate(self::class)
 | 
			
		||||
            ->addCheckFor(null, [self::SEE])
 | 
			
		||||
            ->addCheckFor(null, [self::SEE, self::CREATE])
 | 
			
		||||
            ->addCheckFor(Event::class, [...self::ROLES])
 | 
			
		||||
            ->addCheckFor(Person::class, [self::SEE, self::CREATE])
 | 
			
		||||
            ->addCheckFor(Person::class, [self::SEE])
 | 
			
		||||
            ->addCheckFor(Center::class, [self::STATS])
 | 
			
		||||
            ->build();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,109 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\EventBundle\Templating\Entity;
 | 
			
		||||
 | 
			
		||||
use Chill\EventBundle\Entity\EventTheme;
 | 
			
		||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
 | 
			
		||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
use Twig\Error\LoaderError;
 | 
			
		||||
use Twig\Error\RuntimeError;
 | 
			
		||||
use Twig\Error\SyntaxError;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @implements ChillEntityRenderInterface<EventTheme>
 | 
			
		||||
 */
 | 
			
		||||
class EventThemeRender implements ChillEntityRenderInterface
 | 
			
		||||
{
 | 
			
		||||
    public const AND_CHILDREN_MENTION = 'show_and_children_mention';
 | 
			
		||||
 | 
			
		||||
    public const DEFAULT_ARGS = [
 | 
			
		||||
        self::SEPARATOR_KEY => ' > ',
 | 
			
		||||
        self::SHOW_AND_CHILDREN => false,
 | 
			
		||||
        self::AND_CHILDREN_MENTION => 'event_theme.and children',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public const SEPARATOR_KEY = 'default.separator';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show a mention "and children" on each EventTheme, if the event theme
 | 
			
		||||
     * has at least one child.
 | 
			
		||||
     */
 | 
			
		||||
    public const SHOW_AND_CHILDREN = 'show_and_children';
 | 
			
		||||
 | 
			
		||||
    public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws RuntimeError
 | 
			
		||||
     * @throws SyntaxError
 | 
			
		||||
     * @throws LoaderError
 | 
			
		||||
     */
 | 
			
		||||
    public function renderBox($entity, array $options): string
 | 
			
		||||
    {
 | 
			
		||||
        $options = array_merge(self::DEFAULT_ARGS, $options);
 | 
			
		||||
        // give some help to twig: an array of parents
 | 
			
		||||
        $parents = $this->buildParents($entity);
 | 
			
		||||
 | 
			
		||||
        return $this
 | 
			
		||||
            ->engine
 | 
			
		||||
            ->render(
 | 
			
		||||
                '@ChillEvent/Entity/event_theme.html.twig',
 | 
			
		||||
                [
 | 
			
		||||
                    'eventTheme' => $entity,
 | 
			
		||||
                    'parents' => $parents,
 | 
			
		||||
                    'options' => $options,
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function renderString($entity, array $options): string
 | 
			
		||||
    {
 | 
			
		||||
        /** @var EventTheme $entity */
 | 
			
		||||
        $options = array_merge(self::DEFAULT_ARGS, $options);
 | 
			
		||||
 | 
			
		||||
        $titles = [$this->translatableStringHelper->localize($entity->getName())];
 | 
			
		||||
 | 
			
		||||
        // loop to parent, until root
 | 
			
		||||
        while ($entity->hasParent()) {
 | 
			
		||||
            $entity = $entity->getParent();
 | 
			
		||||
            $titles[] = $this->translatableStringHelper->localize(
 | 
			
		||||
                $entity->getName()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $titles = \array_reverse($titles);
 | 
			
		||||
 | 
			
		||||
        $title = \implode($options[self::SEPARATOR_KEY], $titles);
 | 
			
		||||
 | 
			
		||||
        if ($options[self::SHOW_AND_CHILDREN] && $entity->hasChildren()) {
 | 
			
		||||
            $title .= ' ('.$this->translator->trans($options[self::AND_CHILDREN_MENTION]).')';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $title;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supports($entity, array $options): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $entity instanceof EventTheme;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function buildParents(EventTheme $entity): array
 | 
			
		||||
    {
 | 
			
		||||
        $parents = [];
 | 
			
		||||
 | 
			
		||||
        while ($entity->hasParent()) {
 | 
			
		||||
            $entity = $parents[] = $entity->getParent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $parents;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,10 @@
 | 
			
		||||
module.exports = function (encore, entries) {
 | 
			
		||||
  entries.push(__dirname + "/Resources/public/chill/index.js");
 | 
			
		||||
 | 
			
		||||
  encore.addEntry(
 | 
			
		||||
    "vue_event",
 | 
			
		||||
    __dirname + "/Resources/public/vuejs/index.js",
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
services:
 | 
			
		||||
    Chill\EventBundle\Controller\:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        resource: '../Controller'
 | 
			
		||||
        tags: ['controller.service_arguments']
 | 
			
		||||
 | 
			
		||||
@@ -8,4 +9,11 @@ services:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        resource: '../Menu/'
 | 
			
		||||
        tags: ['chill.menu_builder']
 | 
			
		||||
        tags: ['chill.menu_builder']
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Templating\Entity\:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        resource: '../Templating/Entity'
 | 
			
		||||
        tags:
 | 
			
		||||
            - 'chill.render_entity'
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
services:
 | 
			
		||||
    _defaults:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,9 @@ services:
 | 
			
		||||
    Chill\EventBundle\Export\Export\CountEvents:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.export, alias: 'count_events' }
 | 
			
		||||
    Chill\EventBundle\Export\Export\ListEvents:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.export, alias: 'list_events' }
 | 
			
		||||
    Chill\EventBundle\Export\Export\CountEventParticipations:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.export, alias: 'count_event_participants' }
 | 
			
		||||
 
 | 
			
		||||
@@ -31,3 +31,29 @@ services:
 | 
			
		||||
    Chill\EventBundle\Form\Type\PickEventType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Form\EventThemeType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Form\Type\PickEventThemeType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Form\EventType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Form\EventBudgetKindType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Form\AddEventBudgetElementType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
    Chill\EventBundle\Form\Type\PickAnimatorType:
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: form.type }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,40 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\Migrations\Event;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Add event theme entity.
 | 
			
		||||
 */
 | 
			
		||||
final class Version20250428092611 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Add an event theme entity';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql('CREATE SEQUENCE chill_event_event_theme_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
 | 
			
		||||
        $this->addSql('CREATE TABLE chill_event_event_theme (id INT NOT NULL, parent_id INT DEFAULT NULL, isActive BOOLEAN NOT NULL, name JSON NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0.0\' NOT NULL, PRIMARY KEY(id))');
 | 
			
		||||
        $this->addSql('CREATE INDEX IDX_80D7C6B0727ACA70 ON chill_event_event_theme (parent_id)');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_event_event_theme ADD CONSTRAINT FK_80D7C6B0727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_event_event_theme (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_event_event_theme DROP CONSTRAINT FK_80D7C6B0727ACA70');
 | 
			
		||||
        $this->addSql('DROP TABLE chill_event_event_theme');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\Migrations\Event;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Auto-generated Migration: Please modify to your needs!
 | 
			
		||||
 */
 | 
			
		||||
final class Version20250429062911 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Add themes property to event';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql('CREATE TABLE chill_event_eventtheme (event_id INT NOT NULL, eventtheme_id INT NOT NULL, PRIMARY KEY(event_id, eventtheme_id))');
 | 
			
		||||
        $this->addSql('CREATE INDEX IDX_8D75029771F7E88B ON chill_event_eventtheme (event_id)');
 | 
			
		||||
        $this->addSql('CREATE INDEX IDX_8D750297A81D3C55 ON chill_event_eventtheme (eventtheme_id)');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D75029771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_event_eventtheme ADD CONSTRAINT FK_8D750297A81D3C55 FOREIGN KEY (eventtheme_id) REFERENCES chill_event_event_theme (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D75029771F7E88B');
 | 
			
		||||
        $this->addSql('ALTER TABLE chill_event_eventtheme DROP CONSTRAINT FK_8D750297A81D3C55');
 | 
			
		||||
        $this->addSql('DROP TABLE chill_event_eventtheme');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\Migrations\Event;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
final class Version20250505120818 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Add budget kind admin entity';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE SEQUENCE chill_event_budget_kind_id_seq INCREMENT BY 1 MINVALUE 1 START 1
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE TABLE chill_event_budget_kind (id INT NOT NULL, isActive BOOLEAN DEFAULT true NOT NULL, type VARCHAR(255) NOT NULL, name JSONB DEFAULT '{}' NOT NULL, PRIMARY KEY(id))
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP SEQUENCE chill_event_budget_kind_id_seq CASCADE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP TABLE chill_event_budget_kind
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\Migrations\Event;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
final class Version20250506114531 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Create event budget element entity';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE SEQUENCE chill_event_budget_element_id_seq INCREMENT BY 1 MINVALUE 1 START 1
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE TABLE chill_event_budget_element (id INT NOT NULL, event_id INT DEFAULT NULL, kind_id INT DEFAULT NULL, amount NUMERIC(10, 2) NOT NULL, comment_budget_element_comment TEXT DEFAULT NULL, comment_budget_element_date TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, comment_budget_element_userId INT DEFAULT NULL, PRIMARY KEY(id))
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_BA25859071F7E88B ON chill_event_budget_element (event_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_BA25859030602CA9 ON chill_event_budget_element (kind_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_budget_element ADD CONSTRAINT FK_BA25859071F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_budget_element ADD CONSTRAINT FK_BA25859030602CA9 FOREIGN KEY (kind_id) REFERENCES chill_event_budget_kind (id) NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP SEQUENCE chill_event_budget_element_id_seq CASCADE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_budget_element DROP CONSTRAINT FK_BA25859071F7E88B
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_budget_element DROP CONSTRAINT FK_BA25859030602CA9
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP TABLE chill_event_budget_element
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\Migrations\Event;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
final class Version20250507073301 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Add animators field to an event';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE TABLE chill_event_thirdparty (event_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(event_id, thirdparty_id))
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_9946573E71F7E88B ON chill_event_thirdparty (event_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_9946573EC7D3A8E6 ON chill_event_thirdparty (thirdparty_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_thirdparty ADD CONSTRAINT FK_9946573E71F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_thirdparty ADD CONSTRAINT FK_9946573EC7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_thirdparty DROP CONSTRAINT FK_9946573E71F7E88B
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_thirdparty DROP CONSTRAINT FK_9946573EC7D3A8E6
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP TABLE chill_event_thirdparty
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\Migrations\Event;
 | 
			
		||||
 | 
			
		||||
use Doctrine\DBAL\Schema\Schema;
 | 
			
		||||
use Doctrine\Migrations\AbstractMigration;
 | 
			
		||||
 | 
			
		||||
final class Version20250702144312 extends AbstractMigration
 | 
			
		||||
{
 | 
			
		||||
    public function getDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'Add internal and external animators to event entity';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function up(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE TABLE chill_event_animatorsintern (event_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(event_id, user_id))
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_E699558771F7E88B ON chill_event_animatorsintern (event_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_E6995587A76ED395 ON chill_event_animatorsintern (user_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE TABLE chill_event_animatorsextern (event_id INT NOT NULL, thirdparty_id INT NOT NULL, PRIMARY KEY(event_id, thirdparty_id))
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_7EFBF7DE71F7E88B ON chill_event_animatorsextern (event_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            CREATE INDEX IDX_7EFBF7DEC7D3A8E6 ON chill_event_animatorsextern (thirdparty_id)
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsintern ADD CONSTRAINT FK_E699558771F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsintern ADD CONSTRAINT FK_E6995587A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsextern ADD CONSTRAINT FK_7EFBF7DE71F7E88B FOREIGN KEY (event_id) REFERENCES chill_event_event (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsextern ADD CONSTRAINT FK_7EFBF7DEC7D3A8E6 FOREIGN KEY (thirdparty_id) REFERENCES chill_3party.third_party (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function down(Schema $schema): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsintern DROP CONSTRAINT FK_E699558771F7E88B
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsintern DROP CONSTRAINT FK_E6995587A76ED395
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsextern DROP CONSTRAINT FK_7EFBF7DE71F7E88B
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            ALTER TABLE chill_event_animatorsextern DROP CONSTRAINT FK_7EFBF7DEC7D3A8E6
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP TABLE chill_event_animatorsintern
 | 
			
		||||
        SQL);
 | 
			
		||||
        $this->addSql(<<<'SQL'
 | 
			
		||||
            DROP TABLE chill_event_animatorsextern
 | 
			
		||||
        SQL);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,8 @@ Participation: Participation
 | 
			
		||||
Participations: Participations
 | 
			
		||||
Status: Statut
 | 
			
		||||
Last update: Dernière mise à jour
 | 
			
		||||
Moderator: Animateur
 | 
			
		||||
Moderator: Responsable
 | 
			
		||||
Animators: Animateurs
 | 
			
		||||
 | 
			
		||||
#CRUD event
 | 
			
		||||
Details of an event: Détails d'un événement
 | 
			
		||||
@@ -74,7 +75,7 @@ Show the event: Voir l'événement
 | 
			
		||||
Subscribe an event: Ajouter un événement
 | 
			
		||||
Pick an event: Choisir un événement
 | 
			
		||||
Pick a type of event: Choisir un type d'événement
 | 
			
		||||
Pick a moderator: Choisir un animateur
 | 
			
		||||
Pick a moderator: Choisir un responsable
 | 
			
		||||
 | 
			
		||||
# exports
 | 
			
		||||
Select a format: Choisir un format
 | 
			
		||||
@@ -128,15 +129,70 @@ Create a new type: Créer un nouveau type
 | 
			
		||||
Create a new status: Créer un nouveau statut
 | 
			
		||||
 | 
			
		||||
event:
 | 
			
		||||
    admin:
 | 
			
		||||
        title:
 | 
			
		||||
            Event budget element list: Liste des elements du budget pour un évenement
 | 
			
		||||
        Select budget type: Selectionner le type d'element du budget
 | 
			
		||||
        new:
 | 
			
		||||
            Create a new budget kind: Créér un nouveau element de budget
 | 
			
		||||
    theme:
 | 
			
		||||
        label: Thématiques
 | 
			
		||||
    fields:
 | 
			
		||||
        organizationCost: Coût d'organisation
 | 
			
		||||
        location: Localisation
 | 
			
		||||
        documents: Documents
 | 
			
		||||
        internal animators: Animateurs internes
 | 
			
		||||
        external animators: Animateurs externes
 | 
			
		||||
    form:
 | 
			
		||||
        organisationCost_help: Coût d'organisation pour la structure. Utile pour les statistiques.
 | 
			
		||||
        add_document: Ajouter un document
 | 
			
		||||
        remove_document: Supprimer le document
 | 
			
		||||
        Select one or more themes: Selectionnez une ou plusieurs thématiques
 | 
			
		||||
    filter:
 | 
			
		||||
        event_types: Par types d'événement
 | 
			
		||||
        event_dates: Par date d'événement
 | 
			
		||||
        center: Par centre
 | 
			
		||||
        by_responsable: Par responsable
 | 
			
		||||
        pick_responsable: Filtrer par responsables
 | 
			
		||||
    budget:
 | 
			
		||||
        resources: Ressources
 | 
			
		||||
        charges: Charges
 | 
			
		||||
        label: Elements de budget d'un évenement
 | 
			
		||||
        Select a budget element kind: Selectionner un element de budget
 | 
			
		||||
        no elements: Il y a aucun element
 | 
			
		||||
        resource type: Ressource
 | 
			
		||||
        charge type: Charge
 | 
			
		||||
        amount: Montant
 | 
			
		||||
        comment: Commentaire
 | 
			
		||||
    animators:
 | 
			
		||||
        intern: Animateurs internes
 | 
			
		||||
        extern: Animateurs externes
 | 
			
		||||
 | 
			
		||||
crud:
 | 
			
		||||
    event_theme:
 | 
			
		||||
        title_new: Créér une nouvelle thématique
 | 
			
		||||
        title_edit: Modifier la thématique
 | 
			
		||||
        index:
 | 
			
		||||
            title: Liste des thématiques
 | 
			
		||||
            add_new: Créér une nouvelle thématique
 | 
			
		||||
    event_budget_kind:
 | 
			
		||||
        title_new: Créér un nouveau element de budget
 | 
			
		||||
 | 
			
		||||
export:
 | 
			
		||||
    event:
 | 
			
		||||
        list:
 | 
			
		||||
            title: Liste des évenements
 | 
			
		||||
            description: Crée la liste des évenements en fonction de différents paramètres.
 | 
			
		||||
 | 
			
		||||
event_id: Identifiant
 | 
			
		||||
event_name: Nom
 | 
			
		||||
event_date: Date
 | 
			
		||||
event_type: Type d'évenement
 | 
			
		||||
event_center: Centre
 | 
			
		||||
event_moderator: Responsable
 | 
			
		||||
event_participants_count: Nombre de participants
 | 
			
		||||
event_location: Localisation
 | 
			
		||||
event_budget_resources: Ressources
 | 
			
		||||
event_budget_charges: Charges
 | 
			
		||||
event_animators: Animateurs
 | 
			
		||||
event_themes: Thématiques
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,12 @@ namespace Chill\MainBundle;
 | 
			
		||||
use Chill\MainBundle\Cron\CronJobInterface;
 | 
			
		||||
use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\TimelineCompilerClass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\WidgetsCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\ConfigConsistencyCompilerPass;
 | 
			
		||||
use Chill\MainBundle\Notification\FlagProviders\NotificationFlagProviderInterface;
 | 
			
		||||
use Chill\MainBundle\Notification\NotificationHandlerInterface;
 | 
			
		||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
 | 
			
		||||
use Chill\MainBundle\Search\SearchApiInterface;
 | 
			
		||||
@@ -61,13 +61,14 @@ class ChillMainBundle extends Bundle
 | 
			
		||||
            ->addTag('chill_main.entity_info_provider');
 | 
			
		||||
        $container->registerForAutoconfiguration(ProvideRoleInterface::class)
 | 
			
		||||
            ->addTag('chill_main.provide_role');
 | 
			
		||||
        $container->registerForAutoconfiguration(NotificationFlagProviderInterface::class)
 | 
			
		||||
            ->addTag('chill_main.notification_flag_provider');
 | 
			
		||||
 | 
			
		||||
        $container->addCompilerPass(new SearchableServicesCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new ConfigConsistencyCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new TimelineCompilerClass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new WidgetsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new NotificationCounterCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new MenuCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new ACLFlagsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
        $container->addCompilerPass(new CRUDControllerCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,35 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Command;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Security\RoleDumper;
 | 
			
		||||
use Symfony\Component\Console\Attribute\AsCommand;
 | 
			
		||||
use Symfony\Component\Console\Command\Command;
 | 
			
		||||
use Symfony\Component\Console\Input\InputInterface;
 | 
			
		||||
use Symfony\Component\Console\Output\OutputInterface;
 | 
			
		||||
 | 
			
		||||
#[AsCommand(name: 'chill:main:dump-list-permissions', description: 'Print a markdown reference of permissions (roles) grouped by title with dependencies).')]
 | 
			
		||||
final class DumpListPermissionsCommand extends Command
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private readonly RoleDumper $roleDumper)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function execute(InputInterface $input, OutputInterface $output): int
 | 
			
		||||
    {
 | 
			
		||||
        $markdown = $this->roleDumper->dumpAsMarkdown();
 | 
			
		||||
        $output->writeln($markdown);
 | 
			
		||||
 | 
			
		||||
        return Command::SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,115 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Command;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Console\Command\Command;
 | 
			
		||||
use Symfony\Component\Console\Input\InputArgument;
 | 
			
		||||
use Symfony\Component\Console\Input\InputInterface;
 | 
			
		||||
use Symfony\Component\Console\Output\OutputInterface;
 | 
			
		||||
use Symfony\Component\HttpKernel\KernelInterface;
 | 
			
		||||
use Symfony\Component\Translation\MessageCatalogue;
 | 
			
		||||
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
 | 
			
		||||
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
 | 
			
		||||
use Symfony\Component\Yaml\Yaml;
 | 
			
		||||
 | 
			
		||||
class OverrideTranslationCommand extends Command
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private readonly TranslationReaderInterface $reader,
 | 
			
		||||
        private readonly TranslationWriterInterface $writer,
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->setName('chill:main:override_translation');
 | 
			
		||||
        parent::__construct();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function configure(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this
 | 
			
		||||
            ->setDescription('Generate a translation catalogue with translation remplacements based on replacements provided in a YAML file.')
 | 
			
		||||
            ->addArgument('locale', InputArgument::REQUIRED, 'The locale to process (e.g. fr, en).')
 | 
			
		||||
            ->addArgument('overrides', InputArgument::REQUIRED, 'Path to the overrides YAML file (list of {from, to}).');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function execute(InputInterface $input, OutputInterface $output): int
 | 
			
		||||
    {
 | 
			
		||||
        $locale = (string) $input->getArgument('locale');
 | 
			
		||||
        $overridesPath = (string) $input->getArgument('overrides');
 | 
			
		||||
 | 
			
		||||
        $catalogue = $this->loadCatalogue($locale);
 | 
			
		||||
        $overrides = $this->loadOverrides($overridesPath);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $toOverrideCatalogue = new MessageCatalogue($locale);
 | 
			
		||||
        foreach ($catalogue->getDomains() as $domain) {
 | 
			
		||||
            // hack: we have to replace the suffix ".intl-icu" by "+intl-ic"
 | 
			
		||||
            $domain = str_replace('.intl-icu', '+intl-icu', $domain);
 | 
			
		||||
            foreach ($catalogue->all($domain) as $key => $translation) {
 | 
			
		||||
                foreach ($overrides as $changes) {
 | 
			
		||||
                    $from = $changes['from'];
 | 
			
		||||
                    $to = $changes['to'];
 | 
			
		||||
 | 
			
		||||
                    if (is_string($translation) && str_contains($translation, $from)) {
 | 
			
		||||
                        $newTranslation = strtr($translation, [$from => $to]);
 | 
			
		||||
                        $toOverrideCatalogue->set($key, $newTranslation, $domain);
 | 
			
		||||
                        $translation = $newTranslation;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var KernelInterface $kernel */
 | 
			
		||||
        /* @phpstan-ignore-next-line */
 | 
			
		||||
        $kernel = $this->getApplication()->getKernel();
 | 
			
		||||
        $outputDir = rtrim($kernel->getProjectDir(), '/').'/translations';
 | 
			
		||||
        if (!is_dir($outputDir)) {
 | 
			
		||||
            @mkdir($outputDir, 0775, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Writer expects the 'path' option to be a directory; it will create the proper file name
 | 
			
		||||
        $this->writer->write($toOverrideCatalogue, 'yaml', ['path' => $outputDir]);
 | 
			
		||||
 | 
			
		||||
        $output->writeln(sprintf('Override catalogue written to %s (domain: messages, locale: %s).', $outputDir, $locale));
 | 
			
		||||
 | 
			
		||||
        return Command::SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return list<array{from: string, to: string}>
 | 
			
		||||
     */
 | 
			
		||||
    private function loadOverrides(string $path): array
 | 
			
		||||
    {
 | 
			
		||||
        return Yaml::parseFile($path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function loadCatalogue(string $locale): MessageCatalogue
 | 
			
		||||
    {
 | 
			
		||||
        /** @var KernelInterface $kernel */
 | 
			
		||||
        /* @phpstan-ignore-next-line */
 | 
			
		||||
        $kernel = $this->getApplication()->getKernel();
 | 
			
		||||
 | 
			
		||||
        // collect path for translations
 | 
			
		||||
        $transPaths = [];
 | 
			
		||||
        foreach ($kernel->getBundles() as $bundle) {
 | 
			
		||||
            $bundleDir = $bundle->getPath();
 | 
			
		||||
            $transPaths[] = is_dir($bundleDir.'/Resources/translations') ? $bundleDir.'/Resources/translations' : $bundle->getPath().'/translations';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $currentCatalogue = new MessageCatalogue($locale);
 | 
			
		||||
        foreach ($transPaths as $path) {
 | 
			
		||||
            if (is_dir($path)) {
 | 
			
		||||
                $this->reader->read($path, $currentCatalogue);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $currentCatalogue;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -345,7 +345,7 @@ class ExportController extends AbstractController
 | 
			
		||||
     * @param array  $dataExport    Raw data from export step
 | 
			
		||||
     * @param array  $dataFormatter Raw data from formatter step
 | 
			
		||||
     */
 | 
			
		||||
    private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, array $dataFormatter, ?SavedExport $savedExport): array
 | 
			
		||||
    private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, ?array $dataFormatter, ?SavedExport $savedExport): array
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->filterStatsByCenters) {
 | 
			
		||||
            $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null);
 | 
			
		||||
@@ -365,7 +365,7 @@ class ExportController extends AbstractController
 | 
			
		||||
        $formExport->submit($dataExport);
 | 
			
		||||
        $dataExport = $formExport->getData();
 | 
			
		||||
 | 
			
		||||
        if (\count($dataFormatter) > 0) {
 | 
			
		||||
        if (is_array($dataFormatter) && \count($dataFormatter) > 0) {
 | 
			
		||||
            $formFormatter = $this->createCreateFormExport(
 | 
			
		||||
                $alias,
 | 
			
		||||
                'generate_formatter',
 | 
			
		||||
@@ -381,7 +381,7 @@ class ExportController extends AbstractController
 | 
			
		||||
            'export' => $dataExport['export']['export'] ?? [],
 | 
			
		||||
            'filters' => $dataExport['export']['filters'] ?? [],
 | 
			
		||||
            'aggregators' => $dataExport['export']['aggregators'] ?? [],
 | 
			
		||||
            'pick_formatter' => $dataExport['export']['pick_formatter']['alias'],
 | 
			
		||||
            'pick_formatter' => ($dataExport['export']['pick_formatter'] ?? [])['alias'] ?? '',
 | 
			
		||||
            'formatter' => $dataFormatter['formatter'] ?? [],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\NotificationComment;
 | 
			
		||||
use Chill\MainBundle\Form\NotificationCommentType;
 | 
			
		||||
use Chill\MainBundle\Form\NotificationType;
 | 
			
		||||
use Chill\MainBundle\Notification\Exception\NotificationHandlerNotFound;
 | 
			
		||||
use Chill\MainBundle\Notification\FlagProviders\NotificationByUserFlagProvider;
 | 
			
		||||
use Chill\MainBundle\Notification\NotificationHandlerManager;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorFactory;
 | 
			
		||||
use Chill\MainBundle\Repository\NotificationRepository;
 | 
			
		||||
@@ -57,7 +58,8 @@ class NotificationController extends AbstractController
 | 
			
		||||
        $notification
 | 
			
		||||
            ->setRelatedEntityClass($request->query->get('entityClass'))
 | 
			
		||||
            ->setRelatedEntityId($request->query->getInt('entityId'))
 | 
			
		||||
            ->setSender($this->security->getUser());
 | 
			
		||||
            ->setSender($this->security->getUser())
 | 
			
		||||
            ->setType(NotificationByUserFlagProvider::FLAG);
 | 
			
		||||
 | 
			
		||||
        $tos = $request->query->all('tos');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,14 +11,11 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Form\UserPhonenumberType;
 | 
			
		||||
use Chill\MainBundle\Form\UserProfileType;
 | 
			
		||||
use Chill\MainBundle\Security\ChillSecurity;
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 | 
			
		||||
use Symfony\Component\Form\FormInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 | 
			
		||||
use Symfony\Component\Security\Core\User\UserInterface;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
use Symfony\Component\Routing\Annotation\Route;
 | 
			
		||||
 | 
			
		||||
@@ -41,16 +38,19 @@ final class UserProfileController extends AbstractController
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $user = $this->security->getUser();
 | 
			
		||||
        $editForm = $this->createPhonenumberEditForm($user);
 | 
			
		||||
        $editForm = $this->createForm(UserProfileType::class, $user);
 | 
			
		||||
 | 
			
		||||
        $editForm->get('notificationFlags')->setData($user->getNotificationFlags());
 | 
			
		||||
 | 
			
		||||
        $editForm->handleRequest($request);
 | 
			
		||||
 | 
			
		||||
        if ($editForm->isSubmitted() && $editForm->isValid()) {
 | 
			
		||||
            $phonenumber = $editForm->get('phonenumber')->getData();
 | 
			
		||||
            $notificationFlagsData = $editForm->get('notificationFlags')->getData();
 | 
			
		||||
            $user->setNotificationFlags($notificationFlagsData);
 | 
			
		||||
 | 
			
		||||
            $user->setPhonenumber($phonenumber);
 | 
			
		||||
 | 
			
		||||
            $this->managerRegistry->getManager()->flush();
 | 
			
		||||
            $this->addFlash('success', $this->translator->trans('user.profile.Phonenumber successfully updated!'));
 | 
			
		||||
            $em = $this->managerRegistry->getManager();
 | 
			
		||||
            $em->flush();
 | 
			
		||||
            $this->addFlash('success', $this->translator->trans('user.profile.Profile successfully updated!'));
 | 
			
		||||
 | 
			
		||||
            return $this->redirectToRoute('chill_main_user_profile');
 | 
			
		||||
        }
 | 
			
		||||
@@ -60,13 +60,4 @@ final class UserProfileController extends AbstractController
 | 
			
		||||
            'form' => $editForm->createView(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createPhonenumberEditForm(UserInterface $user): FormInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createForm(
 | 
			
		||||
            UserPhonenumberType::class,
 | 
			
		||||
            $user,
 | 
			
		||||
        )
 | 
			
		||||
            ->add('submit', SubmitType::class, ['label' => $this->translator->trans('Save')]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Entity;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
 | 
			
		||||
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\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
@@ -21,10 +22,10 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
#[ORM\Entity]
 | 
			
		||||
#[ORM\HasLifecycleCallbacks]
 | 
			
		||||
#[ORM\Table(name: 'chill_main_notification')]
 | 
			
		||||
#[ORM\Index(name: 'chill_main_notification_related_entity_idx', columns: ['relatedentityclass', 'relatedentityid'])]
 | 
			
		||||
#[ORM\Index(columns: ['relatedentityclass', 'relatedentityid'], name: 'chill_main_notification_related_entity_idx')]
 | 
			
		||||
class Notification implements TrackUpdateInterface
 | 
			
		||||
{
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)]
 | 
			
		||||
    #[ORM\Column(type: Types::TEXT, nullable: false)]
 | 
			
		||||
    private string $accessKey;
 | 
			
		||||
 | 
			
		||||
    private array $addedAddresses = [];
 | 
			
		||||
@@ -36,12 +37,19 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
    #[ORM\JoinTable(name: 'chill_main_notification_addresses_user')]
 | 
			
		||||
    private Collection $addressees;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Collection<int, UserGroup>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\ManyToMany(targetEntity: UserGroup::class)]
 | 
			
		||||
    #[ORM\JoinTable(name: 'chill_main_notification_addressee_user_group')]
 | 
			
		||||
    private Collection $addresseeUserGroups;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * a list of destinee which will receive notifications.
 | 
			
		||||
     *
 | 
			
		||||
     * @var array|string[]
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
 | 
			
		||||
    #[ORM\Column(type: Types::JSON, options: ['default' => '[]', 'jsonb' => true])]
 | 
			
		||||
    private array $addressesEmails = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -60,21 +68,21 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
    #[ORM\OrderBy(['createdAt' => \Doctrine\Common\Collections\Criteria::ASC])]
 | 
			
		||||
    private Collection $comments;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE)]
 | 
			
		||||
    #[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
 | 
			
		||||
    private \DateTimeImmutable $date;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Id]
 | 
			
		||||
    #[ORM\GeneratedValue]
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
 | 
			
		||||
    #[ORM\Column(type: Types::INTEGER)]
 | 
			
		||||
    private ?int $id = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
 | 
			
		||||
    #[ORM\Column(type: Types::TEXT)]
 | 
			
		||||
    private string $message = '';
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
 | 
			
		||||
    #[ORM\Column(type: Types::STRING, length: 255)]
 | 
			
		||||
    private string $relatedEntityClass = '';
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
 | 
			
		||||
    #[ORM\Column(type: Types::INTEGER)]
 | 
			
		||||
    private int $relatedEntityId;
 | 
			
		||||
 | 
			
		||||
    private array $removedAddresses = [];
 | 
			
		||||
@@ -84,7 +92,7 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
    private ?User $sender = null;
 | 
			
		||||
 | 
			
		||||
    #[Assert\NotBlank(message: 'notification.Title must be defined')]
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
 | 
			
		||||
    #[ORM\Column(type: Types::TEXT, options: ['default' => ''])]
 | 
			
		||||
    private string $title = '';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -94,31 +102,46 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
    #[ORM\JoinTable(name: 'chill_main_notification_addresses_unread')]
 | 
			
		||||
    private Collection $unreadBy;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE)]
 | 
			
		||||
    #[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
 | 
			
		||||
    private ?\DateTimeImmutable $updatedAt = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\ManyToOne(targetEntity: User::class)]
 | 
			
		||||
    private ?User $updatedBy = null;
 | 
			
		||||
 | 
			
		||||
    #[ORM\Column(name: 'type', type: Types::STRING, nullable: true)]
 | 
			
		||||
    private string $type = '';
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->addressees = new ArrayCollection();
 | 
			
		||||
        $this->addresseeUserGroups = new ArrayCollection();
 | 
			
		||||
        $this->unreadBy = new ArrayCollection();
 | 
			
		||||
        $this->comments = new ArrayCollection();
 | 
			
		||||
        $this->setDate(new \DateTimeImmutable());
 | 
			
		||||
        $this->accessKey = bin2hex(openssl_random_pseudo_bytes(24));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function addAddressee(User $addressee): self
 | 
			
		||||
    public function addAddressee(User|UserGroup $addressee): self
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->addressees->contains($addressee)) {
 | 
			
		||||
            $this->addressees[] = $addressee;
 | 
			
		||||
            $this->addedAddresses[] = $addressee;
 | 
			
		||||
        if ($addressee instanceof User) {
 | 
			
		||||
            if (!$this->addressees->contains($addressee)) {
 | 
			
		||||
                $this->addressees->add($addressee);
 | 
			
		||||
                $this->addedAddresses[] = $addressee;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return $this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$this->addresseeUserGroups->contains($addressee)) {
 | 
			
		||||
            $this->addresseeUserGroups->add($addressee);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @deprecated
 | 
			
		||||
     */
 | 
			
		||||
    public function addAddressesEmail(string $email)
 | 
			
		||||
    {
 | 
			
		||||
        if (!\in_array($email, $this->addressesEmails, true)) {
 | 
			
		||||
@@ -152,13 +175,23 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
    #[Assert\Callback]
 | 
			
		||||
    public function assertCountAddresses(ExecutionContextInterface $context, $payload): void
 | 
			
		||||
    {
 | 
			
		||||
        if (0 === (\count($this->getAddressesEmails()) + \count($this->getAddressees()))) {
 | 
			
		||||
        if (0 === (\count($this->getAddresseeUserGroups()) + \count($this->getAddressees()))) {
 | 
			
		||||
            $context->buildViolation('notification.At least one addressee')
 | 
			
		||||
                ->atPath('addressees')
 | 
			
		||||
                ->addViolation();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAddresseeUserGroups(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        return $this->addresseeUserGroups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setAddresseeUserGroups(Collection $addresseeUserGroups): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->addresseeUserGroups = $addresseeUserGroups;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAccessKey(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->accessKey;
 | 
			
		||||
@@ -182,6 +215,23 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
        return $this->addressees;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getAllAddressees(): array
 | 
			
		||||
    {
 | 
			
		||||
        $allUsers = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($this->getAddressees() as $user) {
 | 
			
		||||
            $allUsers[$user->getId()] = $user;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($this->getAddresseeUserGroups() as $userGroup) {
 | 
			
		||||
            foreach ($userGroup->getUsers() as $user) {
 | 
			
		||||
                $allUsers[$user->getId()] = $user;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return array_values($allUsers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array|string[]
 | 
			
		||||
     */
 | 
			
		||||
@@ -303,12 +353,18 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
        $this->addressesOnLoad = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function removeAddressee(User $addressee): self
 | 
			
		||||
    public function removeAddressee(User|UserGroup $addressee): self
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->addressees->removeElement($addressee)) {
 | 
			
		||||
            $this->removedAddresses[] = $addressee;
 | 
			
		||||
        if ($addressee instanceof User) {
 | 
			
		||||
            if ($this->addressees->contains($addressee)) {
 | 
			
		||||
                $this->addressees->removeElement($addressee);
 | 
			
		||||
 | 
			
		||||
                return $this;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->addresseeUserGroups->removeElement($addressee);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -378,7 +434,7 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
 | 
			
		||||
    public function setUpdatedAt(\DateTimeInterface $datetime): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->updatedAt = $datetime;
 | 
			
		||||
        $this->updatedAt = \DateTimeImmutable::createFromInterface($datetime);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
@@ -389,4 +445,16 @@ class Notification implements TrackUpdateInterface
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setType(string $type): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->type = $type;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getType(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->type;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,9 @@ use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
 | 
			
		||||
#[ORM\Table(name: 'users')]
 | 
			
		||||
class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInterface
 | 
			
		||||
{
 | 
			
		||||
    public const NOTIF_FLAG_IMMEDIATE_EMAIL = 'immediate-email';
 | 
			
		||||
    public const NOTIF_FLAG_DAILY_DIGEST = 'daily-digest';
 | 
			
		||||
 | 
			
		||||
    #[ORM\Id]
 | 
			
		||||
    #[ORM\Column(name: 'id', type: \Doctrine\DBAL\Types\Types::INTEGER)]
 | 
			
		||||
    #[ORM\GeneratedValue(strategy: 'AUTO')]
 | 
			
		||||
@@ -116,6 +119,12 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
 | 
			
		||||
    #[PhonenumberConstraint]
 | 
			
		||||
    private ?PhoneNumber $phonenumber = null;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var array<string, list<string>>
 | 
			
		||||
     */
 | 
			
		||||
    #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]', 'jsonb' => true])]
 | 
			
		||||
    private array $notificationFlags = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * User constructor.
 | 
			
		||||
     */
 | 
			
		||||
@@ -623,4 +632,47 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getNotificationFlags(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->notificationFlags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setNotificationFlags(array $notificationFlags)
 | 
			
		||||
    {
 | 
			
		||||
        $this->notificationFlags = $notificationFlags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getNotificationFlagData(string $flag): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->notificationFlags[$flag] ?? [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setNotificationFlagData(string $flag, array $data): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->notificationFlags[$flag] = $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isNotificationSendImmediately(string $type): bool
 | 
			
		||||
    {
 | 
			
		||||
        if ([] === $this->getNotificationFlagData($type) || in_array(User::NOTIF_FLAG_IMMEDIATE_EMAIL, $this->getNotificationFlagData($type), true)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isNotificationDailyDigest(string $type): bool
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array(User::NOTIF_FLAG_DAILY_DIGEST, $this->getNotificationFlagData($type), true)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLocale(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'fr';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ use Chill\MainBundle\Repository\CenterRepositoryInterface;
 | 
			
		||||
use Chill\MainBundle\Repository\RegroupmentRepositoryInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, version: int}}
 | 
			
		||||
 * @phpstan-type NormalizedData array{centers: array{centers: list<int>, regroupments: list<int>}, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter?: string, formatter: array{form: array<string, mixed>, version: int}}
 | 
			
		||||
 */
 | 
			
		||||
class ExportConfigNormalizer
 | 
			
		||||
{
 | 
			
		||||
@@ -72,10 +72,14 @@ class ExportConfigNormalizer
 | 
			
		||||
        }
 | 
			
		||||
        $serialized['aggregators'] = $aggregatorsSerialized;
 | 
			
		||||
 | 
			
		||||
        $serialized['pick_formatter'] = $formData['pick_formatter'];
 | 
			
		||||
        $formatter = $this->exportManager->getFormatter($formData['pick_formatter']);
 | 
			
		||||
        $serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']);
 | 
			
		||||
        $serialized['formatter']['version'] = $formatter->getNormalizationVersion();
 | 
			
		||||
        if ($export instanceof ExportInterface) {
 | 
			
		||||
            $serialized['pick_formatter'] = $formData['pick_formatter'];
 | 
			
		||||
            $formatter = $this->exportManager->getFormatter($formData['pick_formatter']);
 | 
			
		||||
            $serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']);
 | 
			
		||||
            $serialized['formatter']['version'] = $formatter->getNormalizationVersion();
 | 
			
		||||
        } elseif ($export instanceof DirectExportInterface) {
 | 
			
		||||
            $serialized['formatter'] = ['form' => [], 'version' => 0];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $serialized;
 | 
			
		||||
    }
 | 
			
		||||
@@ -87,7 +91,12 @@ class ExportConfigNormalizer
 | 
			
		||||
    public function denormalizeConfig(string $exportAlias, array $serializedData, bool $replaceDisabledByDefaultData = false): array
 | 
			
		||||
    {
 | 
			
		||||
        $export = $this->exportManager->getExport($exportAlias);
 | 
			
		||||
        $formater = $this->exportManager->getFormatter($serializedData['pick_formatter']);
 | 
			
		||||
 | 
			
		||||
        if ($export instanceof ExportInterface) {
 | 
			
		||||
            $formatter = $this->exportManager->getFormatter($serializedData['pick_formatter']);
 | 
			
		||||
        } else {
 | 
			
		||||
            $formatter = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $filtersConfig = [];
 | 
			
		||||
        foreach ($serializedData['filters'] as $alias => $filterData) {
 | 
			
		||||
@@ -117,8 +126,8 @@ class ExportConfigNormalizer
 | 
			
		||||
            'export' => $export->denormalizeFormData($serializedData['export']['form'], $serializedData['export']['version']),
 | 
			
		||||
            'filters' => $filtersConfig,
 | 
			
		||||
            'aggregators' => $aggregatorsConfig,
 | 
			
		||||
            'pick_formatter' => $serializedData['pick_formatter'],
 | 
			
		||||
            'formatter' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
 | 
			
		||||
            'pick_formatter' => $serializedData['pick_formatter'] ?? '',
 | 
			
		||||
            'formatter' => $formatter?->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']),
 | 
			
		||||
            'centers' => [
 | 
			
		||||
                'centers' => array_values(array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']['centers']), fn ($item) => null !== $item)),
 | 
			
		||||
                'regroupments' => array_values(array_filter(array_map(fn (int $id) => $this->regroupmentRepository->find($id), $serializedData['centers']['regroupments']), fn ($item) => null !== $item)),
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Form\DataMapper;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Symfony\Component\Form\DataMapperInterface;
 | 
			
		||||
 | 
			
		||||
final readonly class NotificationFlagDataMapper implements DataMapperInterface
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(private array $notificationFlagProviders) {}
 | 
			
		||||
 | 
			
		||||
    public function mapDataToForms($viewData, $forms): void
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $viewData) {
 | 
			
		||||
            $viewData = [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $formsArray = iterator_to_array($forms);
 | 
			
		||||
 | 
			
		||||
        foreach ($this->notificationFlagProviders as $flagProvider) {
 | 
			
		||||
            $flag = $flagProvider->getFlag();
 | 
			
		||||
 | 
			
		||||
            if (isset($formsArray[$flag])) {
 | 
			
		||||
                $flagForm = $formsArray[$flag];
 | 
			
		||||
 | 
			
		||||
                $immediateEmailChecked = in_array(User::NOTIF_FLAG_IMMEDIATE_EMAIL, $viewData[$flag] ?? [], true)
 | 
			
		||||
                    || !array_key_exists($flag, $viewData);
 | 
			
		||||
                $dailyEmailChecked = in_array(User::NOTIF_FLAG_DAILY_DIGEST, $viewData[$flag] ?? [], true);
 | 
			
		||||
 | 
			
		||||
                if ($flagForm->has('immediate_email')) {
 | 
			
		||||
                    $flagForm->get('immediate_email')->setData($immediateEmailChecked);
 | 
			
		||||
                }
 | 
			
		||||
                if ($flagForm->has('daily_email')) {
 | 
			
		||||
                    $flagForm->get('daily_email')->setData($dailyEmailChecked);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function mapFormsToData($forms, &$viewData): void
 | 
			
		||||
    {
 | 
			
		||||
        $formsArray = iterator_to_array($forms);
 | 
			
		||||
        $viewData = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($this->notificationFlagProviders as $flagProvider) {
 | 
			
		||||
            $flag = $flagProvider->getFlag();
 | 
			
		||||
 | 
			
		||||
            if (isset($formsArray[$flag])) {
 | 
			
		||||
                $flagForm = $formsArray[$flag];
 | 
			
		||||
                $viewData[$flag] = [];
 | 
			
		||||
 | 
			
		||||
                if (true === $flagForm['immediate_email']->getData()) {
 | 
			
		||||
                    $viewData[$flag][] = User::NOTIF_FLAG_IMMEDIATE_EMAIL;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (true === $flagForm['daily_email']->getData()) {
 | 
			
		||||
                    $viewData[$flag][] = User::NOTIF_FLAG_DAILY_DIGEST;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ([] === $viewData[$flag]) {
 | 
			
		||||
                    $viewData[$flag][] = User::NOTIF_FLAG_IMMEDIATE_EMAIL;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,17 +12,12 @@ declare(strict_types=1);
 | 
			
		||||
namespace Chill\MainBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\Notification;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\PickUserGroupOrUserDynamicType;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
use Symfony\Component\Validator\Constraints\Email;
 | 
			
		||||
use Symfony\Component\Validator\Constraints\NotBlank;
 | 
			
		||||
use Symfony\Component\Validator\Constraints\NotNull;
 | 
			
		||||
 | 
			
		||||
class NotificationType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
@@ -33,29 +28,14 @@ class NotificationType extends AbstractType
 | 
			
		||||
                'label' => 'Title',
 | 
			
		||||
                'required' => true,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('addressees', PickUserDynamicType::class, [
 | 
			
		||||
            ->add('addressees', PickUserGroupOrUserDynamicType::class, [
 | 
			
		||||
                'multiple' => true,
 | 
			
		||||
                'required' => false,
 | 
			
		||||
                'label' => 'notification.Pick user or user group',
 | 
			
		||||
                'empty_data' => '[]',
 | 
			
		||||
                'required' => true,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('message', ChillTextareaType::class, [
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('addressesEmails', ChillCollectionType::class, [
 | 
			
		||||
                'label' => 'notification.dest by email',
 | 
			
		||||
                'help' => 'notification.dest by email help',
 | 
			
		||||
                'by_reference' => false,
 | 
			
		||||
                'allow_add' => true,
 | 
			
		||||
                'allow_delete' => true,
 | 
			
		||||
                'entry_type' => EmailType::class,
 | 
			
		||||
                'button_add_label' => 'notification.Add an email',
 | 
			
		||||
                'button_remove_label' => 'notification.Remove an email',
 | 
			
		||||
                'empty_collection_explain' => 'notification.Any email',
 | 
			
		||||
                'entry_options' => [
 | 
			
		||||
                    'constraints' => [
 | 
			
		||||
                        new NotNull(), new NotBlank(), new Email(),
 | 
			
		||||
                    ],
 | 
			
		||||
                    'label' => 'Email',
 | 
			
		||||
                ],
 | 
			
		||||
            ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,6 +55,10 @@ class DateIntervalType extends AbstractType
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('n', IntegerType::class, [
 | 
			
		||||
                'attr' => [
 | 
			
		||||
                    'min' => 0,
 | 
			
		||||
                    'step' => 1,
 | 
			
		||||
                ],
 | 
			
		||||
                'constraints' => [
 | 
			
		||||
                    new GreaterThan([
 | 
			
		||||
                        'value' => 0,
 | 
			
		||||
 
 | 
			
		||||
@@ -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\MainBundle\Form\Type;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Form\DataMapper\NotificationFlagDataMapper;
 | 
			
		||||
use Chill\MainBundle\Notification\NotificationFlagManager;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
 | 
			
		||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
 | 
			
		||||
class NotificationFlagsType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    private readonly array $notificationFlagProviders;
 | 
			
		||||
 | 
			
		||||
    public function __construct(NotificationFlagManager $notificationFlagManager)
 | 
			
		||||
    {
 | 
			
		||||
        $this->notificationFlagProviders = $notificationFlagManager->getAllNotificationFlagProviders();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options): void
 | 
			
		||||
    {
 | 
			
		||||
        $builder->setDataMapper(new NotificationFlagDataMapper($this->notificationFlagProviders));
 | 
			
		||||
 | 
			
		||||
        foreach ($this->notificationFlagProviders as $flagProvider) {
 | 
			
		||||
            $flag = $flagProvider->getFlag();
 | 
			
		||||
            $builder->add($flag, FormType::class, [
 | 
			
		||||
                'label' => $flagProvider->getLabel(),
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            $builder->get($flag)
 | 
			
		||||
                ->add('immediate_email', CheckboxType::class, [
 | 
			
		||||
                    'label' => false,
 | 
			
		||||
                    'required' => false,
 | 
			
		||||
                    'mapped' => false,
 | 
			
		||||
                ])
 | 
			
		||||
                ->add('daily_email', CheckboxType::class, [
 | 
			
		||||
                    'label' => false,
 | 
			
		||||
                    'required' => false,
 | 
			
		||||
                    'mapped' => false,
 | 
			
		||||
                ])
 | 
			
		||||
            ;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver): void
 | 
			
		||||
    {
 | 
			
		||||
        $resolver->setDefaults([
 | 
			
		||||
            'data_class' => null,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/Bundle/ChillMainBundle/Form/UserProfileType.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/Bundle/ChillMainBundle/Form/UserProfileType.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Form;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
 | 
			
		||||
use Chill\MainBundle\Form\Type\NotificationFlagsType;
 | 
			
		||||
use Symfony\Component\Form\AbstractType;
 | 
			
		||||
use Symfony\Component\Form\FormBuilderInterface;
 | 
			
		||||
use Symfony\Component\OptionsResolver\OptionsResolver;
 | 
			
		||||
 | 
			
		||||
class UserProfileType extends AbstractType
 | 
			
		||||
{
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options)
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
            ->add('phonenumber', ChillPhoneNumberType::class, [
 | 
			
		||||
                'required' => false,
 | 
			
		||||
            ])
 | 
			
		||||
            ->add('notificationFlags', NotificationFlagsType::class, [
 | 
			
		||||
                'label' => false,
 | 
			
		||||
                'mapped' => false,
 | 
			
		||||
            ])
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver)
 | 
			
		||||
    {
 | 
			
		||||
        $resolver->setDefaults([
 | 
			
		||||
            'data_class' => \Chill\MainBundle\Entity\User::class,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,102 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Notification\Email;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Cron\CronJobInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\CronJobExecution;
 | 
			
		||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage;
 | 
			
		||||
use Doctrine\DBAL\Connection;
 | 
			
		||||
use Doctrine\DBAL\Exception;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\Clock\ClockInterface;
 | 
			
		||||
use Symfony\Component\Messenger\MessageBusInterface;
 | 
			
		||||
 | 
			
		||||
readonly class DailyNotificationDigestCronjob implements CronJobInterface
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private ClockInterface $clock,
 | 
			
		||||
        private Connection $connection,
 | 
			
		||||
        private MessageBusInterface $messageBus,
 | 
			
		||||
        private LoggerInterface $logger,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function canRun(?CronJobExecution $cronJobExecution): bool
 | 
			
		||||
    {
 | 
			
		||||
        $now = $this->clock->now();
 | 
			
		||||
 | 
			
		||||
        if (null !== $cronJobExecution && $now->sub(new \DateInterval('PT23H45M')) < $cronJobExecution->getLastStart()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Run between 6 and 9 AM
 | 
			
		||||
        return in_array((int) $now->format('H'), [6, 7, 8], true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getKey(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'daily-notification-digest';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws \DateInvalidOperationException
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function run(array $lastExecutionData): ?array
 | 
			
		||||
    {
 | 
			
		||||
        $now = $this->clock->now();
 | 
			
		||||
        if (isset($lastExecutionData['last_execution'])) {
 | 
			
		||||
            $lastExecution = \DateTimeImmutable::createFromFormat(
 | 
			
		||||
                \DateTimeImmutable::ATOM,
 | 
			
		||||
                $lastExecutionData['last_execution']
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            $lastExecution = $now->sub(new \DateInterval('P1D'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get distinct users who received notifications since the last execution
 | 
			
		||||
        $sql = <<<'SQL'
 | 
			
		||||
        SELECT DISTINCT cmnau.user_id
 | 
			
		||||
        FROM chill_main_notification cmn
 | 
			
		||||
        JOIN chill_main_notification_addresses_user cmnau ON cmnau.notification_id = cmn.id
 | 
			
		||||
        WHERE cmn.date >= :lastExecution AND cmn.date <= :now
 | 
			
		||||
        SQL;
 | 
			
		||||
 | 
			
		||||
        $sqlStatement = $this->connection->prepare($sql);
 | 
			
		||||
        $sqlStatement->bindValue('lastExecution', $lastExecution->format(\DateTimeInterface::RFC3339));
 | 
			
		||||
        $sqlStatement->bindValue('now', $now->format(\DateTimeInterface::RFC3339));
 | 
			
		||||
        $result = $sqlStatement->executeQuery();
 | 
			
		||||
 | 
			
		||||
        $count = 0;
 | 
			
		||||
        foreach ($result->fetchAllAssociative() as $row) {
 | 
			
		||||
            $userId = (int) $row['user_id'];
 | 
			
		||||
 | 
			
		||||
            $message = new ScheduleDailyNotificationDigestMessage(
 | 
			
		||||
                $userId,
 | 
			
		||||
                $lastExecution,
 | 
			
		||||
                $now
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            $this->messageBus->dispatch($message);
 | 
			
		||||
            ++$count;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->logger->info('[DailyNotificationDigestCronjob] Dispatched daily digest messages', [
 | 
			
		||||
            'user_count' => $count,
 | 
			
		||||
            'last_execution' => $lastExecution->format('Y-m-d-H:i:s.u e'),
 | 
			
		||||
            'current_time' => $now->format('Y-m-d-H:i:s.u e'),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            'last_execution' => $now->format('Y-m-d-H:i:s.u e'),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * For the full copyright and license information, please view
 | 
			
		||||
 * the LICENSE file that was distributed with this source code.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Notification\Email\NotificationEmailHandlers;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage;
 | 
			
		||||
use Chill\MainBundle\Notification\Email\NotificationMailer;
 | 
			
		||||
use Chill\MainBundle\Repository\NotificationRepository;
 | 
			
		||||
use Chill\MainBundle\Repository\UserRepository;
 | 
			
		||||
use Psr\Log\LoggerInterface;
 | 
			
		||||
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
 | 
			
		||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
 | 
			
		||||
 | 
			
		||||
#[AsMessageHandler]
 | 
			
		||||
readonly class ScheduleDailyNotificationDigestHandler
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        private NotificationRepository $notificationRepository,
 | 
			
		||||
        private UserRepository $userRepository,
 | 
			
		||||
        private NotificationMailer $notificationMailer,
 | 
			
		||||
        private LoggerInterface $logger,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws TransportExceptionInterface
 | 
			
		||||
     */
 | 
			
		||||
    public function __invoke(ScheduleDailyNotificationDigestMessage $message): void
 | 
			
		||||
    {
 | 
			
		||||
        $userId = $message->getUserId();
 | 
			
		||||
        $lastExecutionDate = $message->getLastExecutionDateTime();
 | 
			
		||||
        $currentDate = $message->getCurrentDateTime();
 | 
			
		||||
 | 
			
		||||
        $user = $this->userRepository->find($userId);
 | 
			
		||||
        if (null === $user) {
 | 
			
		||||
            $this->logger->warning('[ScheduleDailyNotificationDigestHandler] User not found', [
 | 
			
		||||
                'user_id' => $userId,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            throw new \InvalidArgumentException(sprintf('User with ID %s not found', $userId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get all notifications for this user between last execution and current date
 | 
			
		||||
        $notifications = $this->notificationRepository->findNotificationsForUserBetweenDates(
 | 
			
		||||
            $userId,
 | 
			
		||||
            $lastExecutionDate,
 | 
			
		||||
            $currentDate
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Filter out notifications that should be sent in a daily digest
 | 
			
		||||
        $dailyNotifications = array_filter($notifications, fn ($notification) => $user->isNotificationDailyDigest($notification->getType()));
 | 
			
		||||
 | 
			
		||||
        if ([] === $dailyNotifications) {
 | 
			
		||||
            $this->logger->info('[ScheduleDailyNotificationDigestHandler] No daily notifications found for user', [
 | 
			
		||||
                'user_id' => $userId,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->notificationMailer->sendDailyDigest($user, $dailyNotifications);
 | 
			
		||||
 | 
			
		||||
        $this->logger->info('[ScheduleDailyNotificationDigestHandler] Sent daily digest', [
 | 
			
		||||
            'user_id' => $userId,
 | 
			
		||||
            'notification_count' => count($dailyNotifications),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user