diff --git a/.changes/unreleased/DX-20250401-144728.yaml b/.changes/unreleased/DX-20250401-144728.yaml new file mode 100644 index 000000000..a08ba1760 --- /dev/null +++ b/.changes/unreleased/DX-20250401-144728.yaml @@ -0,0 +1,6 @@ +kind: DX +body: Allow TranslatableMessage in flash messages +time: 2025-04-01T14:47:28.814268801+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/.changes/unreleased/DX-20250407-121010.yaml b/.changes/unreleased/DX-20250407-121010.yaml new file mode 100644 index 000000000..1f8a0496a --- /dev/null +++ b/.changes/unreleased/DX-20250407-121010.yaml @@ -0,0 +1,71 @@ +kind: DX +body: | + Rewrite exports to run them asynchronously + +changelog: | + - Add new methods to serialize data using the rector rule + - Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`. + - Refactor manually the initializeQuery method + - Remove the injection of ExportManager into the constructor of each export element: + + ```diff + + - class MyFormatter implements FormatterInterface + + class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface + { + + use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; + + - public function __construct(private ExportManager $exportmanager) {} + + public function MyMethod(): void + { + - $this->exportManager->getFilter('alias'); + + $this->getExportManager()->getFilter('alias'); + } + } + ``` + - configure messenger to handle export in a queue: + + ```diff + # config/packages/messenger.yaml + framework: + messenger: + routing: + + 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority + ``` + + - add missing methods to exports, aggregators, filters, formatter: + + ```php + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + ``` + + There are rector rules to generate those methods: + + - `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector` + + See: + + ```php + // upgrade chill exports + $rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]); + ``` + + This rule will create most of the work necessary, but some manuals changes are still necessary: + + - we must set manually the correct repository for method `denormalizeDoctrineEntity`; + - when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`) + + + + + + + + + time: 2025-04-07T12:10:10.682561327+02:00 +custom: + Issue: "" + SchemaChange: Add columns or tables diff --git a/config/packages/chill.yaml b/config/packages/chill.yaml index 76578a2c7..6ed6b6984 100644 --- a/config/packages/chill.yaml +++ b/config/packages/chill.yaml @@ -17,6 +17,7 @@ chill_main: acl: form_show_scopes: true form_show_centers: true + filter_stats_by_center: true access_global_history: false access_user_change_password: true access_permissions_group_list: true diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index b460fd60c..39eab3875 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -5,7 +5,6 @@ framework: # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. failure_transport: failed - transports: # those transports are added by chill-bundles recipes sync: sync:// @@ -19,7 +18,9 @@ framework: async: ~ auto_setup: true - priority: '%env(MESSENGER_TRANSPORT_DSN)%/priority' + priority: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%/priority' + # end of transports added by chill-bundles recipes # https://symfony.com/doc/current/messenger.html#transport-configuration failed: 'doctrine://default?queue_name=failed' @@ -61,6 +62,8 @@ framework: 'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority 'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async 'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async + 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority + 'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async # end of routes added by chill-bundles recipes # Route your messages to the transports # 'App\Message\YourMessage': async diff --git a/docs/source/_static/code/exports/BirthdateFilter.php b/docs/source/_static/code/exports/BirthdateFilter.php index fca768ab3..872d48001 100644 --- a/docs/source/_static/code/exports/BirthdateFilter.php +++ b/docs/source/_static/code/exports/BirthdateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use DateTime; use Doctrine\ORM\Query\Expr; @@ -20,6 +21,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; // add specific role for this filter public function addRole(): ?string { @@ -28,7 +30,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac } // here, we alter the query created by Export - public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); // we create the clause here @@ -52,13 +54,13 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac } // we give information on which type of export this filter applies - public function applyOn() + public function applyOn(): string { return 'person'; } // we build a form to collect some parameters from the users - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('date_from', DateType::class, [ 'label' => 'Born after this date', @@ -74,6 +76,18 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac 'format' => 'dd-MM-yyyy', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['date_from' => $this->normalizeDate($formData['date_from']), 'date_to' => $this->normalizeDate($formData['date_to'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => $this->denormalizeDate($formData['date_from']), 'date_to' => $this->denormalizeDate($formData['date_to'])]; + } public function getFormDefaultData(): array { return ['date_from' => new DateTime(), 'date_to' => new DateTime()]; @@ -81,7 +95,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac // here, we create a simple string which will describe the action of // the filter in the Response - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s birtdate: ' . 'between %date_from% and %date_to%', [ @@ -90,7 +104,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s birthdate'; } @@ -99,7 +113,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac // is executed here. This function is added by the interface // `ExportElementValidatedInterface`, and can be ignore if there is // no need for a validation - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $date_from = $data['date_from']; $date_to = $data['date_to']; diff --git a/docs/source/_static/code/exports/CountPerson.php b/docs/source/_static/code/exports/CountPerson.php index a0f6931ac..ce4fb8017 100644 --- a/docs/source/_static/code/exports/CountPerson.php +++ b/docs/source/_static/code/exports/CountPerson.php @@ -36,6 +36,18 @@ class CountPerson implements ExportInterface { // this export does not add any form } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return []; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; @@ -60,29 +72,29 @@ class CountPerson implements ExportInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { // this array match the result keys in the query. We have only // one column. return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count peoples'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // we gather all center the user choose. $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/docs/source/development/export-sequence.puml b/docs/source/development/export-sequence.puml new file mode 100644 index 000000000..0d0c77c14 --- /dev/null +++ b/docs/source/development/export-sequence.puml @@ -0,0 +1,84 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +User -> ExportController: configure export using form +activate ExportController +ExportController -> ExportForm: build form +activate ExportForm + +loop for every ExportElement (Filter, Aggregator) + ExportForm -> ExportElement: `buildForm` + activate ExportElement + ExportElement -> ExportForm: add form to builders + deactivate ExportElement +end + +ExportForm -> ExportController +deactivate ExportForm + +ExportController -> User: show form +deactivate ExportController + +note left of User: Configure the export:\ncheck filters, aggregators, … + +User -> ExportController: post configuration of the export +activate ExportController + +ExportController -> ExportForm: `getData` +activate ExportForm +ExportForm -> ExportController: return data: list of entities, etc. +deactivate ExportForm + +loop for every ExportElement (Filter, Aggregator) + ExportController -> ExportElement: serializeData (data) + activate ExportElement + ExportElement -> ExportController: return serializedData (simple array with string, int, …) + deactivate ExportElement +end + +ExportController -> Database: `INSERT INTO RequestGeneration_table` (insert new entity) +ExportController -> MessageQueue: warn about a new request +activate MessageQueue +ExportController -> User: "ok, generation is in process" +deactivate ExportController + +note left of User: The user see a waiting screen + +MessageQueue -> MessengerConsumer: forward the message to the MessengerConsumer +deactivate MessageQueue +activate MessengerConsumer +MessengerConsumer -> Database: `SELECT * FROM RequestGeneration_table WHERE id = %s` +activate Database +Database -> MessengerConsumer: return RequestGeneration with serializedData +deactivate Database + +loop for every ExportElement (Filter, Aggregator) + MessengerConsumer -> ExportElement: deserializeData + activate ExportElement + ExportElement -> MessengerConsumer: return data (list of entities, etc.) from the serialized array + deactivate ExportElement + MessengerConsumer -> ExportElement: alter the sql query (`ExportElement::alterQuery`) + activate ExportElement + ExportElement -> MessengerConsumer: return the query with WHERE and GROUP BY clauses + deactivate ExportElement +end + +MessengerConsumer -> MessengerConsumer: prepare the export +MessengerConsumer -> MessengerConsumer: save the export as a stored object +MessengerConsumer -> Database: `UPDATE RequestGeneration_table SET ready = true` +deactivate MessengerConsumer + +User -> ExportController: pull every 5s to know if the export is generated +activate ExportController +ExportController -> User: warn the export is generated +deactivate ExportController + +User -> ExportController: download the export from object storage + + + + + +@enduml diff --git a/docs/source/development/messages-to-users.rst b/docs/source/development/messages-to-users.rst index a935ee855..244638d0b 100644 --- a/docs/source/development/messages-to-users.rst +++ b/docs/source/development/messages-to-users.rst @@ -15,24 +15,31 @@ Messages to users, flashbags and buttons Flashbags ========== -The four following levels are defined : +The four following levels are defined : +-----------+----------------------------------------------------------------------------------------------+ |Key |Intent | +===========+==============================================================================================+ -|alert |A message not linked with the user action, but which should require an action or a | -| |correction. | -+-----------+----------------------------------------------------------------------------------------------+ |success |The user action succeeds. | +-----------+----------------------------------------------------------------------------------------------+ |notice |A simple message to give information to the user. The message may be linked or not linked with| | |the user action. | +-----------+----------------------------------------------------------------------------------------------+ -|warning |A message linked with an action, the user should correct. | -+-----------+----------------------------------------------------------------------------------------------+ |error |The user's action failed: he must correct something to process the action. | +-----------+----------------------------------------------------------------------------------------------+ +We can use :code:`TranslatableMessage` (and other :code:`TranslatableMessageInterface` instances) into the controller: + +.. code-block:: php + + // in a controller action: + if (($session = $request->getSession()) instanceof Session) { + $session->getFlashBag()->add( + 'success', + new TranslatableMessage('saved_export.Saved export is saved!') + ); + } + .. seealso:: `Flash Messages on Symfony documentation `_ @@ -66,7 +73,7 @@ To add the action on button, use them as class along with ``sc-button`` : | | | - Submitting this form will remove the entity | +-----------+----------------+------------------------------------------------------------------------------+ | Edit | ``bt-edit`` or | Link to a form to edit an entity | -| | ``bt-update`` | | +| | ``bt-update`` | | +-----------+----------------+------------------------------------------------------------------------------+ | Save | ``bt-save`` | Submitting this form will save change on the entity | +-----------+----------------+------------------------------------------------------------------------------+ diff --git a/package.json b/package.json index fc50ba29c..73fa14d86 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@hotwired/stimulus": "^3.0.0", "@luminateone/eslint-baseline": "^1.0.9", "@symfony/stimulus-bridge": "^3.2.0", + "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@types/dompurify": "^3.0.5", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7c969f941..1a66e5be2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2154,11 +2154,6 @@ parameters: count: 1 path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php - - - message: "#^Instanceof between string and DateTimeInterface will always evaluate to false\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php - - message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Export\\\\Helper\\\\ExportAddressHelper\\:\\:\\$unitNamesKeysCache contains unresolvable type\\.$#" count: 1 diff --git a/phpstan.dist.neon b/phpstan.dist.neon index feb453c81..60c36771e 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -3,7 +3,7 @@ parameters: paths: - src/ - utils/ - tmpDir: .cache/ + tmpDir: var/cache/phpstan reportUnmatchedIgnoredErrors: false excludePaths: - .php_cs* diff --git a/phpunit.rector.xml b/phpunit.rector.xml index 25e0d25aa..73fbee8e5 100644 --- a/phpunit.rector.xml +++ b/phpunit.rector.xml @@ -1,13 +1,24 @@ - - - - utils/rector/tests - - - - - utils/rector/src - - + + + + utils/rector/tests + + + + + utils/rector/src + + diff --git a/rector.php b/rector.php index 3923d37e4..2b8180208 100644 --- a/rector.php +++ b/rector.php @@ -18,14 +18,15 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/docs', __DIR__ . '/src', + __DIR__ . '/rector.php', ]); $rectorConfig->skip([ \Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::class => __DIR__ . 'src/Bundle/ChillMainBundle/Service/Notifier/LegacyOvhCloudFactory.php' ]); - $rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml '); - $rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); + //$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml '); + //$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); //$rectorConfig->cacheClass(\Rector\Caching\ValueObject\Storage\FileCacheStorage::class); //$rectorConfig->cacheDirectory(__DIR__ . '/.cache/rector'); @@ -39,16 +40,11 @@ return static function (RectorConfig $rectorConfig): void { // part of the symfony 54 rules $rectorConfig->rule(\Rector\Symfony\Symfony53\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector::class); $rectorConfig->rule(\Rector\Symfony\Symfony60\Rector\MethodCall\GetHelperControllerToServiceRector::class); - $rectorConfig->disableParallel(); + //$rectorConfig->disableParallel(); //define sets of rules $rectorConfig->sets([ LevelSetList::UP_TO_PHP_82, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_40, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_41, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_42, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_43, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_44, \Rector\Doctrine\Set\DoctrineSetList::DOCTRINE_CODE_QUALITY, \Rector\PHPUnit\Set\PHPUnitSetList::PHPUNIT_90, ]); @@ -66,11 +62,6 @@ return static function (RectorConfig $rectorConfig): void { // some routes are added twice if it remains activated // $rectorConfig->rule(\Rector\Symfony\Configs\Rector\ClassMethod\AddRouteAnnotationRector::class); - // chill rules - $rectorConfig->rules([ - \Chill\Utils\Rector\Rector\ChillBundleMakeDataProviderStaticForAbstractExportTestRector::class, - ]); - // skip some path... $rectorConfig->skip([ // waiting for fixing this bug: https://github.com/rectorphp/rector-doctrine/issues/342 @@ -94,4 +85,6 @@ return static function (RectorConfig $rectorConfig): void { new \Rector\Php80\ValueObject\AnnotationToAttribute('Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency'), new \Rector\Php80\ValueObject\AnnotationToAttribute('Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation'), ]); + + }; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php index e9e8fb474..e304a9d7f 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php @@ -24,7 +24,7 @@ class ByActivityNumberAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('(SELECT COUNT(activity.id) FROM '.Activity::class.' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator') @@ -41,12 +41,27 @@ class ByActivityNumberAggregator implements AggregatorInterface // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php index 0097854d3..79af0d8fe 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php @@ -33,7 +33,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface private TranslatableStringHelperInterface $translatableStringHelper, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('after_date', PickRollingDateType::class, [ @@ -46,6 +46,21 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['after_date' => $formData['after_date']->normalize(), 'before_date' => $formData['before_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['after_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['after_date']), 'before_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['before_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -54,7 +69,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -69,12 +84,12 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_actype_id']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.acp.by_activity_type.title'; } @@ -84,7 +99,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -115,7 +130,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface ->addGroupBy("{$p}_actype_id"); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php index 9282f92e4..8585f79e5 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php @@ -27,7 +27,7 @@ class BySocialActionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.socialActions', 'actsocialaction'); @@ -42,17 +42,32 @@ class BySocialActionAggregator implements AggregatorInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php index bbdadf4d6..8932abaf0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php @@ -27,7 +27,7 @@ class BySocialIssueAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.socialIssues', 'actsocialissue'); @@ -42,17 +42,32 @@ class BySocialIssueAggregator implements AggregatorInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php index ab07afca7..007b42fdb 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php @@ -25,7 +25,7 @@ final readonly class ActivityLocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.location', 'actloc'); @@ -39,17 +39,32 @@ final readonly class ActivityLocationAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -69,7 +84,7 @@ final readonly class ActivityLocationAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.activity.by_location.Title'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php index 56a6d0249..1a161840a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php @@ -22,14 +22,29 @@ final readonly class ActivityPresenceAggregator implements AggregatorInterface { public function __construct(private ActivityPresenceRepositoryInterface $activityPresenceRepository, private TranslatableStringHelperInterface $translatableStringHelper) {} - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -44,7 +59,7 @@ final readonly class ActivityPresenceAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_presence_aggregator_attendee']; } @@ -59,13 +74,13 @@ final readonly class ActivityPresenceAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(activity.attendee) AS activity_presence_aggregator_attendee'); $qb->addGroupBy('activity_presence_aggregator_attendee'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php index ad3937a6e..58b8788cc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php @@ -36,7 +36,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add select element if ('reasons' === $data['level']) { @@ -72,7 +72,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add( 'level', @@ -89,6 +89,21 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['level' => $formData['level']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['level' => $formData['level']]; + } + public function getFormDefaultData(): array { return [ @@ -96,7 +111,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) use ($data) { if ('_header' === $value) { @@ -125,7 +140,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { // add select element if ('reasons' === $data['level']) { @@ -139,12 +154,12 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali throw new \RuntimeException('The data provided are not recognised.'); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by activity reason'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['level']) { $context diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php index dbdc982b6..b7cb73021 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php @@ -29,7 +29,7 @@ class ActivityTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acttype', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.activityType', 'acttype'); @@ -44,17 +44,32 @@ class ActivityTypeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class ActivityTypeAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by activity type'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php index 61452af22..6a8f1875b 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php @@ -29,7 +29,7 @@ class ActivityUserAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add select element $qb->addSelect(sprintf('IDENTITY(activity.user) AS %s', self::KEY)); @@ -43,17 +43,32 @@ class ActivityUserAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, $values, $data): \Closure + public function getLabels($key, $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -70,7 +85,7 @@ class ActivityUserAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::KEY]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php index cc4ab9d14..62594b0c0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php @@ -27,7 +27,7 @@ class ActivityUsersAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actusers', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.users', 'actusers'); @@ -43,17 +43,32 @@ class ActivityUsersAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add on the form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -70,12 +85,12 @@ class ActivityUsersAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_users_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by activity users'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php index b90c2b0cc..14ffff521 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php @@ -34,7 +34,7 @@ class ActivityUsersJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class ActivityUsersJobAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php index a93a2a875..260f4cd10 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php @@ -34,7 +34,7 @@ class ActivityUsersScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class ActivityUsersScopeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php index 09bdab89e..8da8019e9 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php @@ -27,7 +27,7 @@ class ByCreatorAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(activity.createdBy) AS creator_aggregator'); $qb->addGroupBy('creator_aggregator'); @@ -38,17 +38,32 @@ class ByCreatorAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php index 13224bade..ed4cc699e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php @@ -27,7 +27,7 @@ class ByThirdpartyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acttparty', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.thirdParties', 'acttparty'); @@ -42,17 +42,32 @@ class ByThirdpartyAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php index ef8dd8c50..d42fd48b4 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php @@ -34,7 +34,7 @@ class CreatorJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class CreatorJobAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php index ddf90dfbc..b50457a02 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php @@ -34,7 +34,7 @@ class CreatorScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class CreatorScopeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php index 9d9e5ed61..58321383e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php @@ -32,7 +32,7 @@ class DateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $order = null; @@ -67,7 +67,7 @@ class DateAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -76,12 +76,27 @@ class DateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) use ($data): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php index da2d74f64..3497313df 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php @@ -27,7 +27,7 @@ class LocationTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.location', 'actloc'); @@ -42,17 +42,32 @@ class LocationTypeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php index ab32c278e..8eb3054c7 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php @@ -24,17 +24,32 @@ final readonly class HouseholdAggregator implements AggregatorInterface { public function __construct(private HouseholdRepository $householdRepository) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string|int { if ('_header' === $value) { @@ -49,12 +64,12 @@ final readonly class HouseholdAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_household_agg']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_household.title'; } @@ -64,7 +79,7 @@ final readonly class HouseholdAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->join( HouseholdMember::class, @@ -92,7 +107,7 @@ final readonly class HouseholdAggregator implements AggregatorInterface ->addGroupBy('activity_household_agg'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY_PERSON; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php index c7a3bd6b5..cff7e26d2 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php @@ -21,27 +21,42 @@ final readonly class PersonAggregator implements AggregatorInterface { public function __construct(private LabelPersonHelper $labelPersonHelper) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.person.by_person.person'); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_by_person_agg']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_person.title'; } @@ -51,14 +66,14 @@ final readonly class PersonAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('IDENTITY(activity.person) AS activity_by_person_agg') ->addGroupBy('activity_by_person_agg'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY_PERSON; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php index 8459741c5..b9189001c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php @@ -27,17 +27,32 @@ final readonly class PersonsAggregator implements AggregatorInterface public function __construct(private LabelPersonHelper $labelPersonHelper) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { if ($key !== self::PREFIX.'_pid') { throw new \UnexpectedValueException('this key should not be handled: '.$key); @@ -46,12 +61,12 @@ final readonly class PersonsAggregator implements AggregatorInterface return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons'); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_pid']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.activity.by_persons.Group activity by persons'; } @@ -61,7 +76,7 @@ final readonly class PersonsAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -71,7 +86,7 @@ final readonly class PersonsAggregator implements AggregatorInterface ->addGroupBy("{$p}_pid"); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php index 774968544..5b17e52ee 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php @@ -26,7 +26,7 @@ class SentReceivedAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('activity.sentReceived AS activity_sentreceived_aggregator') ->addGroupBy('activity_sentreceived_aggregator'); @@ -42,6 +42,21 @@ class SentReceivedAggregator implements AggregatorInterface // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php index d8b204e8c..756f81d57 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -38,6 +39,21 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -72,7 +88,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface return ['export_avg_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -87,7 +103,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php index acfb073f5..5240dec3a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -46,6 +47,21 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac // TODO: Implement buildForm() method. } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -80,7 +96,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac return ['export_avg_activity_visit_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -95,7 +111,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php index dfd5d966f..a1f0a30d8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php @@ -43,6 +43,21 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -77,7 +92,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -92,7 +107,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php index 41c05494c..fa3598258 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php @@ -44,6 +44,21 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -78,7 +93,7 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php index c49b9d4ad..0ce5f722e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php @@ -43,6 +43,21 @@ class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -77,7 +92,7 @@ class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -92,7 +107,7 @@ class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php index aab2d7db8..fa09380ff 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php @@ -17,6 +17,7 @@ use Chill\ActivityBundle\Export\Export\ListActivityHelper; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper; use Chill\MainBundle\Export\ListInterface; @@ -38,6 +39,21 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa $this->helper->buildForm($builder); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -73,7 +89,7 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return array_merge( @@ -85,25 +101,23 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa ); } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $this->helper->getResult($query, $data); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return ListActivityHelper::MSG_KEY.'List activity linked to a course'; } - public function getType() + public function getType(): string { return $this->helper->getType(); } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { - $centers = array_map(static fn ($el) => $el['center'], $acl); - $qb = $this->entityManager->createQueryBuilder(); $qb @@ -114,7 +128,7 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa ->leftJoin('acppart.person', 'person') ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL'); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); $qb // some grouping are necessary diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php index 159fcf78d..151018907 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php @@ -40,9 +40,21 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getNormalizationVersion(): int { - // TODO: Implement buildForm() method. + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; } public function getFormDefaultData(): array @@ -79,7 +91,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface return ['export_sum_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -94,7 +106,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php index 27502e2b0..735d55182 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php @@ -40,9 +40,21 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getNormalizationVersion(): int { - // TODO: Implement buildForm() method. + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; } public function getFormDefaultData(): array @@ -79,7 +91,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac return ['export_sum_activity_visit_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -94,7 +106,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php index e0a95b1f5..0239aa481 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php @@ -35,6 +35,21 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -64,17 +79,17 @@ class CountActivity implements ExportInterface, GroupedExportInterface return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count activities linked to a person'; } @@ -84,7 +99,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php index 31111a083..95a5aff2d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php @@ -36,6 +36,21 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -65,17 +80,17 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity_person.header' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.export.count_household_on_activity_person.title'; } @@ -85,7 +100,7 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php index de1713542..295b8e3d3 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php @@ -78,6 +78,21 @@ class ListActivity implements ListInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields']]; + } + public function getFormDefaultData(): array { return []; @@ -167,17 +182,17 @@ class ListActivity implements ListInterface, GroupedExportInterface } } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return $data['fields']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'List activity linked to a person'; } @@ -187,7 +202,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php index 547dbf6bc..e971fe129 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php @@ -49,6 +49,21 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -84,17 +99,17 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return static fn (string $value) => '_header' === $value ? $header : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_stat_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { if (self::SUM === $this->action) { return 'Sum activity linked to a person duration'; @@ -108,7 +123,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map( static fn (array $el): Center => $el['center'], diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php index 7c6eb0bb2..5953f95d8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class ActivityTypeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const BASE_EXISTS = 'SELECT 1 FROM '.Activity::class.' act_type_filter_activity WHERE act_type_filter_activity.accompanyingPeriod = acp'; public function __construct( @@ -38,7 +40,7 @@ final readonly class ActivityTypeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $exists = self::BASE_EXISTS; @@ -62,12 +64,12 @@ final readonly class ActivityTypeFilter implements FilterInterface } } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_activitytypes', EntityType::class, [ 'class' => ActivityType::class, @@ -92,6 +94,21 @@ final readonly class ActivityTypeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_activitytypes' => $this->normalizeDoctrineEntity($formData['accepted_activitytypes']), 'date_after' => $formData['date_after']?->normalize(), 'date_before' => $formData['date_before']?->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_activitytypes' => $this->denormalizeDoctrineEntity($formData['accepted_activitytypes'], $this->activityTypeRepository), 'date_after' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['date_after']), 'date_before' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['date_before'])]; + } + public function getFormDefaultData(): array { return [ @@ -101,7 +118,7 @@ final readonly class ActivityTypeFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $types = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php index 13349baa5..37011fb25 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php @@ -12,23 +12,28 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Form\Type\PickSocialActionType; +use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class BySocialActionFilter implements FilterInterface { - public function __construct(private readonly SocialActionRender $actionRender) {} + use ExportDataNormalizerTrait; + + public function __construct(private readonly SocialActionRender $actionRender, private readonly SocialActionRepository $socialActionRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) { $qb->join('activity.socialActions', 'actsocialaction'); @@ -48,19 +53,36 @@ class BySocialActionFilter implements FilterInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_socialactions', PickSocialActionType::class, [ 'multiple' => true, ]); } - public function getFormDefaultData(): array + public function getNormalizationVersion(): int { - return []; + return 1; } - public function describeAction($data, $format = 'string'): array + public function normalizeFormData(array $formData): array + { + return ['accepted_socialactions' => $this->normalizeDoctrineEntity($formData['accepted_socialactions'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialactions' => $this->denormalizeDoctrineEntity($formData['accepted_socialactions'], $this->socialActionRepository)]; + } + + public function getFormDefaultData(): array + { + return [ + 'accepted_socialactions' => [], + ]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $actions = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php index bef40290e..15a3e7e2e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php @@ -12,23 +12,28 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Form\Type\PickSocialIssueType; +use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class BySocialIssueFilter implements FilterInterface { - public function __construct(private readonly SocialIssueRender $issueRender) {} + use ExportDataNormalizerTrait; + + public function __construct(private readonly SocialIssueRender $issueRender, private readonly SocialIssueRepository $issueRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) { $qb->join('activity.socialIssues', 'actsocialissue'); @@ -48,19 +53,34 @@ class BySocialIssueFilter implements FilterInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_socialissues', PickSocialIssueType::class, [ 'multiple' => true, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_socialissues' => $this->normalizeDoctrineEntity($formData['accepted_socialissues'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialissues' => $this->denormalizeDoctrineEntity($formData['accepted_socialissues'], $this->issueRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $issues = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php index afd708d33..15c517e51 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -27,7 +28,7 @@ class HasNoActivityFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere(' @@ -43,17 +44,32 @@ class HasNoActivityFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered acp which has no activities', []]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php index 2d3282ad1..da2dfb3c3 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -25,12 +26,12 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt private RollingDateConverterInterface $rollingDateConverter, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.activity.course_having_activity_between_date.Title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -41,6 +42,21 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -49,7 +65,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.activity.course_having_activity_between_date.Only course having an activity between from and to', @@ -65,7 +81,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $alias = 'act_period_having_act_betw_date_alias'; $from = 'act_period_having_act_betw_date_start'; @@ -82,7 +98,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt ->setParameter($to, $this->rollingDateConverter->convert($data['end_date'])); } - public function applyOn() + public function applyOn(): string { return \Chill\PersonBundle\Export\Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php index e3d7796d3..bc254abae 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class ActivityDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -61,7 +62,7 @@ class ActivityDateFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, [ @@ -72,12 +73,27 @@ class ActivityDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'Filtered by date of activity: only between %date_from% and %date_to%', @@ -88,7 +104,7 @@ class ActivityDateFilter implements FilterInterface ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filtered by date activity'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php index c9eab9066..d431fa260 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php @@ -13,6 +13,8 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Export\Declarations; +use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\Common\Collections\Collection; @@ -23,17 +25,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; final readonly class ActivityPresenceFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private TranslatableStringHelperInterface $translatableStringHelper, private TranslatorInterface $translator, + private ActivityPresenceRepositoryInterface $activityPresenceRepository, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.activity.by_presence.Filter activity by activity presence'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('presences', EntityType::class, [ 'class' => ActivityPresence::class, @@ -45,12 +50,27 @@ final readonly class ActivityPresenceFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['presences' => $this->normalizeDoctrineEntity($formData['presences'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['presences' => $this->denormalizeDoctrineEntity($formData['presences'], $this->activityPresenceRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $presences = array_map( fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName()), @@ -68,14 +88,14 @@ final readonly class ActivityPresenceFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('activity.attendee IN (:activity_presence_filter_presences)') ->setParameter('activity_presence_filter_presences', $data['presences']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php index 96e42c3e6..99d10cf4d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -24,6 +25,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, protected ActivityTypeRepositoryInterface $activityTypeRepository, @@ -34,7 +37,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('activity.activityType', ':selected_activity_types'); @@ -47,7 +50,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('types', EntityType::class, [ 'choices' => $this->activityTypeRepository->findAllActive(), @@ -70,12 +73,27 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['types' => $this->normalizeDoctrineEntity($formData['types'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['types' => $this->denormalizeDoctrineEntity($formData['types'], $this->activityTypeRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( @@ -88,12 +106,12 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by activity type'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['types'] || 0 === \count($data['types'])) { $context diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php index 56285c026..a3c70f9fe 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php @@ -12,26 +12,30 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class ActivityUsersFilter implements FilterInterface +final readonly class ActivityUsersFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $orX = $qb->expr()->orX(); - foreach ($data['accepted_users'] as $key => $user) { + foreach ($this->userOrMe($data['accepted_users'], $exportGenerationContext) as $key => $user) { $orX->add($qb->expr()->isMemberOf(':activity_users_filter_u'.$key, 'activity.users')); $qb->setParameter('activity_users_filter_u'.$key, $user); } @@ -39,29 +43,44 @@ class ActivityUsersFilter implements FilterInterface $qb->andWhere($orX); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'Users', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php index f75c5a817..bdad045c8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php @@ -12,28 +12,32 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class ByCreatorFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private readonly UserRender $userRender, private readonly UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere( $qb->expr()->in('activity.createdBy', ':users') ) - ->setParameter('users', $data['accepted_users']); + ->setParameter('users', $this->userOrMe($data['accepted_users'], $exportGenerationContext)); } public function applyOn(): string @@ -41,23 +45,38 @@ class ByCreatorFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php index 3b805d4ff..dd87200c3 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -26,6 +27,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; final readonly class CreatorJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_act_filter_creator_job'; public function __construct( @@ -39,7 +41,7 @@ final readonly class CreatorJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -75,7 +77,7 @@ final readonly class CreatorJobFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -90,7 +92,22 @@ final readonly class CreatorJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $jobs = array_map( fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php index 36b827e9b..bb695d68c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; class CreatorScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_act_filter_creator_scope'; public function __construct( @@ -36,7 +38,7 @@ class CreatorScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,7 +74,7 @@ class CreatorScopeFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -86,7 +88,22 @@ class CreatorScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $scopes = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php index b74be2552..60923ad41 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; @@ -35,7 +36,7 @@ class EmergencyFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -56,7 +57,7 @@ class EmergencyFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_emergency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -66,12 +67,27 @@ class EmergencyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + public function getFormDefaultData(): array { return ['accepted_emergency' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by emergency: only %emergency%', [ diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php index 77b4ce20d..cb1f18bf8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php @@ -12,19 +12,27 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; +use Chill\MainBundle\Repository\LocationRepository; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class LocationFilter implements FilterInterface +final readonly class LocationFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private LocationRepository $locationRepository, + ) {} + public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere( $qb->expr()->in('activity.location', ':location') @@ -38,7 +46,7 @@ class LocationFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_location', PickUserLocationType::class, [ 'multiple' => true, @@ -46,12 +54,27 @@ class LocationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_location' => $this->normalizeDoctrineEntity($formData['accepted_location'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_location' => $this->denormalizeDoctrineEntity($formData['accepted_location'], $this->locationRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $locations = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php index 771dfca30..3a9fff0bc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php @@ -12,8 +12,11 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickLocationTypeType; +use Chill\MainBundle\Repository\LocationTypeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; @@ -21,14 +24,16 @@ use Symfony\Component\Form\FormBuilderInterface; class LocationTypeFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use ExportDataNormalizerTrait; + + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private LocationTypeRepository $locationTypeRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->join('activity.location', 'actloc'); @@ -52,7 +57,7 @@ class LocationTypeFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_locationtype', PickLocationTypeType::class, [ 'multiple' => true, @@ -60,12 +65,27 @@ class LocationTypeFilter implements FilterInterface ]); } - public function getFormDefaultData(): array + public function getNormalizationVersion(): int { - return []; + return 1; } - public function describeAction($data, $format = 'string'): array + public function normalizeFormData(array $formData): array + { + return ['accepted_locationtype' => $this->normalizeDoctrineEntity($formData['accepted_locationtype'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_locationtype' => $this->denormalizeDoctrineEntity($formData['accepted_locationtype'], $this->locationTypeRepository)]; + } + + public function getFormDefaultData(): array + { + return ['accepted_locationtype' => []]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $types = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php index b8ce3259f..4802fc7ac 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\Common\Collections\Collection; @@ -26,6 +27,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct(protected TranslatableStringHelper $translatableStringHelper, protected ActivityReasonRepository $activityReasonRepository) {} public function addRole(): ?string @@ -33,7 +36,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $join = $qb->getDQLPart('join'); @@ -58,7 +61,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt return Declarations::ACTIVITY_PERSON; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('reasons', EntityType::class, [ 'class' => ActivityReason::class, @@ -70,12 +73,27 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['reasons' => $this->normalizeDoctrineEntity($formData['reasons'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['reasons' => $this->denormalizeDoctrineEntity($formData['reasons'], $this->activityReasonRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( @@ -91,12 +109,12 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by reason'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['reasons'] || 0 === \count($data['reasons'])) { $context diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php index 2fd628edb..673aac25e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -39,7 +40,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { // create a subquery for activity $sqb = $qb->getEntityManager()->createQueryBuilder(); @@ -92,7 +93,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_from_rolling', PickRollingDateType::class, [ 'label' => 'export.filter.activity.person_between_dates.Implied in an activity after this date', @@ -116,6 +117,21 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem } } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from_rolling' => $formData['date_from_rolling']->normalize(), 'date_to_rolling' => $formData['date_to_rolling']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']), 'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling'])]; + } + public function getFormDefaultData(): array { return [ @@ -125,7 +141,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ [] === $data['reasons'] ? @@ -150,7 +166,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem return 'export.filter.activity.person_between_dates.title'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if ($this->rollingDateConverter->convert($data['date_from_rolling']) >= $this->rollingDateConverter->convert($data['date_to_rolling'])) { diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php index 8bdefeb24..820830e23 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php @@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Tests\Export\Filter\PersonsFilterTest; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Form\Type\PickPersonDynamicType; use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; @@ -33,7 +34,7 @@ final readonly class PersonsFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -47,12 +48,12 @@ final readonly class PersonsFilter implements FilterInterface $qb->andWhere($orX); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_persons', PickPersonDynamicType::class, [ 'multiple' => true, @@ -60,6 +61,21 @@ final readonly class PersonsFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_persons' => $formData['accepted_persons']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_persons' => $formData['accepted_persons']]; + } + public function getFormDefaultData(): array { return [ @@ -67,7 +83,7 @@ final readonly class PersonsFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $users = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php index 3011627e8..ba85ff731 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php @@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; @@ -36,7 +37,7 @@ class SentReceivedFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -57,7 +58,7 @@ class SentReceivedFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_sentreceived', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -68,12 +69,27 @@ class SentReceivedFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_sentreceived' => $formData['accepted_sentreceived']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_sentreceived' => $formData['accepted_sentreceived']]; + } + public function getFormDefaultData(): array { return ['accepted_sentreceived' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $sentreceived = array_flip(self::CHOICES)[$data['accepted_sentreceived']]; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php index 6e6b745b9..2a63be54a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php @@ -12,23 +12,27 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class UserFilter implements FilterInterface +final readonly class UserFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -41,7 +45,7 @@ class UserFilter implements FilterInterface } $qb->add('where', $where); - $qb->setParameter('users', $data['accepted_users']); + $qb->setParameter('users', $this->userOrMe($data['accepted_users'], $exportGenerationContext)); } public function applyOn(): string @@ -49,24 +53,39 @@ class UserFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'Creators', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php index cf2787359..f5bf9f2ae 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class UsersJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'act_filter_user_job'; public function __construct( @@ -37,7 +39,7 @@ class UsersJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -60,12 +62,12 @@ class UsersJobFilter implements FilterInterface ); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -77,7 +79,22 @@ class UsersJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.activity.by_users_job.Filtered activity by users job: only %jobs%', [ '%jobs%' => implode( @@ -97,7 +114,7 @@ class UsersJobFilter implements FilterInterface ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.activity.by_users_job.Filter by users job'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php index bbf1630c4..83af6701c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class UsersScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'act_filter_user_scope'; public function __construct( @@ -37,7 +39,7 @@ class UsersScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class UsersScopeFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -77,7 +79,22 @@ class UsersScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return ['export.filter.activity.by_users_scope.Filtered activity by users scope: only %scopes%', [ '%scopes%' => implode( diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php index 228d70856..0efc8d056 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php @@ -12,8 +12,9 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\ActivityPresence; +use Doctrine\Persistence\ObjectRepository; -interface ActivityPresenceRepositoryInterface +interface ActivityPresenceRepositoryInterface extends ObjectRepository { public function find($id): ?ActivityPresence; diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php index cbd168ce8..670492695 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php @@ -45,14 +45,20 @@ final class ByCreatorFilterTest extends AbstractFilterTest ->from(User::class, 'u') ->select('u') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; + foreach ($array as $a) { $data[] = [ 'accepted_users' => $a, ]; } + $data[] = [ + 'accepted_users' => 'me', + ]; + return $data; } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php index 569fb5403..976ab97e6 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php @@ -55,6 +55,10 @@ final class UserFilterTest extends AbstractFilterTest ]; } + $data[] = [ + 'accepted_users' => 'me', + ]; + return $data; } diff --git a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php index bea38c768..ca85f2687 100644 --- a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php +++ b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php @@ -30,7 +30,7 @@ class ActivityValidity extends Constraint public $socialIssuesMessage = 'For this type of activity, you must add at least one social issue'; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php index cc311d470..fff522ef1 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php @@ -30,7 +30,7 @@ class ByActivityTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(aside.type) AS by_aside_activity_type_aggregator') ->addGroupBy('by_aside_activity_type_aggregator'); @@ -41,17 +41,32 @@ class ByActivityTypeAggregator implements AggregatorInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php index b5ca1022b..1c9ae467e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php @@ -26,12 +26,27 @@ class ByLocationAggregator implements AggregatorInterface // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -60,7 +75,7 @@ class ByLocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(aside.location) AS by_aside_activity_location_aggregator') ->addGroupBy('by_aside_activity_location_aggregator'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php index 58d5584b4..6a34acf81 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -34,7 +34,7 @@ class ByUserJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class ByUserJobAggregator implements AggregatorInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php index a0277a37b..f5d74490a 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -34,7 +34,7 @@ class ByUserScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -64,14 +64,29 @@ class ByUserScopeAggregator implements AggregatorInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php index 70922b6ae..6cf542d2d 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php @@ -26,6 +26,21 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -60,7 +75,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac return ['export_avg_aside_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -75,7 +90,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->repository->createQueryBuilder('aside'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php index 6d1eed5fe..4bd7659ee 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php @@ -26,6 +26,21 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -63,7 +78,7 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -78,7 +93,7 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->repository->createQueryBuilder('aside'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php index 816af5771..0f6802104 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -46,6 +46,21 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -142,7 +157,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ 'id', @@ -160,12 +175,12 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn ]; } - public function getResult($query, $data): array + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aside_activity.List of aside activities'; } @@ -175,7 +190,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->em->createQueryBuilder() ->from(AsideActivity::class, 'aside') diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php index 0fd318902..18f78f3f8 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php @@ -26,6 +26,21 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -60,7 +75,7 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac return ['export_sum_aside_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -75,7 +90,7 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->repository ->createQueryBuilder('aside'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php index bfb8fe0b1..d96f74b7f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -15,6 +15,7 @@ use Chill\AsideActivityBundle\Entity\AsideActivityCategory; use Chill\AsideActivityBundle\Export\Declarations; use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository; use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\Common\Collections\Collection; @@ -24,10 +25,13 @@ use Symfony\Component\Form\FormBuilderInterface; class ByActivityTypeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private readonly CategoryRender $categoryRender, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly AsideActivityCategoryRepository $asideActivityTypeRepository, + private readonly AsideActivityCategoryRepository $asideActivityCategoryRepository, ) {} public function addRole(): ?string @@ -35,7 +39,7 @@ class ByActivityTypeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('aside.type', ':types'); @@ -48,7 +52,7 @@ class ByActivityTypeFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('types', EntityType::class, [ @@ -68,12 +72,27 @@ class ByActivityTypeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['types' => $this->normalizeDoctrineEntity($formData['types'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['types' => $this->denormalizeDoctrineEntity($formData['types'], $this->asideActivityCategoryRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $types = array_map( fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()), diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php index b8d77d942..4521d7a94 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class ByDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->between( 'aside.date', @@ -53,7 +54,7 @@ class ByDateFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, [ @@ -64,6 +65,21 @@ class ByDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return [ @@ -72,7 +88,7 @@ class ByDateFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['export.filter.Filtered by aside activities between %dateFrom% and %dateTo%', [ '%dateFrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php index 23c141a06..9c231782e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php @@ -14,8 +14,10 @@ namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; +use Chill\MainBundle\Repository\LocationRepository; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -23,8 +25,11 @@ use Symfony\Component\Security\Core\Security; final readonly class ByLocationFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private Security $security, + private LocationRepository $locationRepository, ) {} public function getTitle(): string @@ -38,6 +43,21 @@ final readonly class ByLocationFilter implements FilterInterface ->add('locations', PickUserLocationType::class); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['locations' => $this->normalizeDoctrineEntity($formData['locations'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['locations' => $this->denormalizeDoctrineEntity($formData['locations'], $this->locationRepository)]; + } + public function getFormDefaultData(): array { $user = $this->security->getUser(); @@ -53,7 +73,7 @@ final readonly class ByLocationFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $extractFunction = fn (Location $l): string => $l->getName(); if ($data['locations'] instanceof Collection) { @@ -72,7 +92,7 @@ final readonly class ByLocationFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('aside.location', ':locations'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php index 8dd1a8eac..04e02d3ff 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php @@ -12,28 +12,32 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class ByUserFilter implements FilterInterface +final readonly class ByUserFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepository $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('aside.agent', ':users'); $qb ->andWhere($clause) - ->setParameter('users', $data['accepted_users']); + ->setParameter('users', $this->userOrMe($data['accepted_users'], $exportGenerationContext)); } public function applyOn(): string @@ -41,24 +45,39 @@ class ByUserFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'Creators', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php index 551e91cd8..3cf8d8322 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -15,6 +15,7 @@ use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ByUserJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'aside_act_filter_user_job'; public function __construct( @@ -37,7 +39,7 @@ class ByUserJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class ByUserJobFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -77,7 +79,22 @@ class ByUserJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return ['export.filter.by_user_job.Filtered aside activities by user jobs: only %jobs%', [ '%jobs%' => implode( diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php index 5257495aa..387cc11da 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -15,6 +15,7 @@ use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ByUserScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'aside_act_filter_user_scope'; public function __construct( @@ -37,7 +39,7 @@ class ByUserScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class ByUserScopeFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -77,7 +79,22 @@ class ByUserScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.by_user_scope.Filtered aside activities by user scope: only %scopes%', [ '%scopes%' => implode( diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php index aae93c17b..25f93bc3e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Tests\Export\Export; use Chill\AsideActivityBundle\Export\Export\ListAsideActivity; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Doctrine\ORM\AbstractQuery; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -33,7 +35,7 @@ class ListAsideActivityTest extends KernelTestCase public function testExecuteQuery(): void { - $qb = $this->listAsideActivity->initiateQuery([], [], []) + $qb = $this->listAsideActivity->initiateQuery([], [], [], new ExportGenerationContext(new User())) ->setMaxResults(1); $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php index 5e2091fac..40be6542d 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php @@ -27,7 +27,7 @@ final readonly class AgentAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('caluser', $qb->getAllAliases(), true)) { $qb->join('cal.mainUser', 'caluser'); @@ -42,17 +42,32 @@ final readonly class AgentAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php index 7c84653d2..718fcd8bd 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php @@ -27,7 +27,7 @@ class CancelReasonAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // TODO: still needs to take into account calendars without a cancel reason somehow if (!\in_array('calcancel', $qb->getAllAliases(), true)) { @@ -43,17 +43,32 @@ class CancelReasonAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php index 1182ea374..420ff8d2f 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php @@ -34,7 +34,7 @@ final readonly class JobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ final readonly class JobAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php index 6481f95b4..94bc0fdea 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php @@ -26,7 +26,7 @@ final readonly class LocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('calloc', $qb->getAllAliases(), true)) { $qb->join('cal.location', 'calloc'); @@ -40,17 +40,32 @@ final readonly class LocationAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php index be9406cfa..f84c6b3d7 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php @@ -27,7 +27,7 @@ final readonly class LocationTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('calloc', $qb->getAllAliases(), true)) { $qb->join('cal.location', 'calloc'); @@ -42,17 +42,32 @@ final readonly class LocationTypeAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php index 6bf65b8ef..ea899ef54 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php @@ -23,7 +23,7 @@ class MonthYearAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect("to_char(cal.startDate, 'MM-YYYY') AS month_year_aggregator"); // $qb->addSelect("extract(month from age(cal.startDate, cal.endDate)) AS month_aggregator"); @@ -35,17 +35,32 @@ class MonthYearAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php index ff24baa8c..85dac1f3e 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php @@ -34,7 +34,7 @@ final readonly class ScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ final readonly class ScopeAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php index e9213d3cb..cc5114a4f 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php @@ -33,7 +33,7 @@ class UrgencyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('cal.urgent AS urgency_aggregator'); $qb->addGroupBy('urgency_aggregator'); @@ -44,17 +44,32 @@ class UrgencyAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php b/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php index f643eaa68..8b14a1628 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php @@ -34,6 +34,21 @@ class CountCalendars implements ExportInterface, GroupedExportInterface // No form necessary } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -71,7 +86,7 @@ class CountCalendars implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -89,7 +104,7 @@ class CountCalendars implements ExportInterface, GroupedExportInterface /** * Initiate the query. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php index b69185a17..afda99ee9 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php @@ -31,6 +31,21 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface // no form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -68,7 +83,7 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -83,7 +98,7 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface return Declarations::CALENDAR_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $qb = $this->calendarRepository->createQueryBuilder('cal'); diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php index 8ea23014c..80369e354 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php @@ -31,6 +31,21 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface // no form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -68,7 +83,7 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -83,7 +98,7 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface return Declarations::CALENDAR_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $qb = $this->calendarRepository->createQueryBuilder('cal'); diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php index c16c148fc..b19ce3596 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php @@ -13,23 +13,27 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class AgentFilter implements FilterInterface +final readonly class AgentFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('cal.mainUser', ':agents'); @@ -49,7 +53,7 @@ class AgentFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_agents', EntityType::class, [ 'class' => User::class, @@ -59,12 +63,27 @@ class AgentFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_agents' => $this->normalizeDoctrineEntity($formData['accepted_agents'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_agents' => $this->denormalizeDoctrineEntity($formData['accepted_agents'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php index 90a004388..dfb450ea9 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class BetweenDatesFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->andX( $qb->expr()->gte('cal.startDate', ':dateFrom'), @@ -52,19 +53,34 @@ class BetweenDatesFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by calendars between %dateFrom% and %dateTo%', [ '%dateFrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php index 63149509f..dbdc297a8 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php @@ -19,6 +19,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -41,7 +42,7 @@ class CalendarRangeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (null !== $data['hasCalendarRange']) { $qb->andWhere($qb->expr()->isNotNull('cal.calendarRange')); @@ -55,7 +56,7 @@ class CalendarRangeFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('hasCalendarRange', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -66,12 +67,27 @@ class CalendarRangeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['hasCalendarRange' => $formData['hasCalendarRange']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['hasCalendarRange' => $formData['hasCalendarRange']]; + } + public function getFormDefaultData(): array { return ['hasCalendarRange' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $choice = ''; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php index 3c87397e4..1885d6dcb 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php @@ -14,6 +14,7 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class JobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'cal_filter_job'; public function __construct( @@ -36,7 +38,7 @@ final readonly class JobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -70,7 +72,7 @@ final readonly class JobFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('job', EntityType::class, [ @@ -84,7 +86,22 @@ final readonly class JobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['job' => $this->normalizeDoctrineEntity($formData['job'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['job' => $this->denormalizeDoctrineEntity($formData['job'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $userJobs = []; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php index 3a1c5a35a..447d3750e 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php @@ -14,6 +14,7 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -25,6 +26,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; class ScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'cal_filter_scope'; public function __construct( @@ -38,7 +40,7 @@ class ScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,7 +74,7 @@ class ScopeFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scope', EntityType::class, [ @@ -86,7 +88,22 @@ class ScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scope' => $this->normalizeDoctrineEntity($formData['scope'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scope' => $this->denormalizeDoctrineEntity($formData['scope'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $scopes = []; diff --git a/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php index 81d230e67..e90164a2a 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php @@ -13,7 +13,13 @@ namespace Chill\DocStoreBundle\Repository; use Chill\DocStoreBundle\Entity\StoredObject; +/** + * @template T of object + */ interface AssociatedEntityToStoredObjectInterface { + /** + * @return T|null + */ public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object; } diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php index 1e519997a..bdb28f9c3 100644 --- a/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php +++ b/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php @@ -32,7 +32,7 @@ class EventDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $order = null; @@ -67,7 +67,7 @@ class EventDateAggregator implements AggregatorInterface return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -76,12 +76,27 @@ class EventDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) use ($data): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php index db757cac9..7aafb3791 100644 --- a/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php +++ b/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php @@ -29,7 +29,7 @@ class EventTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('eventtype', $qb->getAllAliases(), true)) { $qb->leftJoin('event.type', 'eventtype'); @@ -44,17 +44,32 @@ class EventTypeAggregator implements AggregatorInterface return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class EventTypeAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group by event type'; } diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php index c02483db2..20f663d4e 100644 --- a/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php +++ b/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php @@ -29,7 +29,7 @@ class RoleAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('event_part', $qb->getAllAliases(), true)) { $qb->leftJoin('event_part.role', 'role'); @@ -44,17 +44,32 @@ class RoleAggregator implements AggregatorInterface return Declarations::EVENT_PARTICIPANTS; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class RoleAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group by participant role'; } diff --git a/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php b/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php index 5c5d5bf05..d95fb4ecb 100644 --- a/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php +++ b/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php @@ -36,6 +36,21 @@ readonly class CountEventParticipations implements ExportInterface, GroupedExpor public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -65,17 +80,17 @@ readonly class CountEventParticipations implements ExportInterface, GroupedExpor return static fn ($value) => '_header' === $value ? 'Count event participants' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_event_participants']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count event participants'; } @@ -85,7 +100,7 @@ readonly class CountEventParticipations implements ExportInterface, GroupedExpor return Declarations::EVENT_PARTICIPANTS; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php b/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php index 7cb98b5a6..fe51fdfd8 100644 --- a/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php +++ b/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php @@ -36,6 +36,21 @@ readonly class CountEvents implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -65,17 +80,17 @@ readonly class CountEvents implements ExportInterface, GroupedExportInterface return static fn ($value) => '_header' === $value ? 'Number of events' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_event']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count events'; } @@ -85,7 +100,7 @@ readonly class CountEvents implements ExportInterface, GroupedExportInterface return Declarations::EVENT; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php index 10f1dbd81..e7b245fbc 100644 --- a/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php +++ b/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\EventBundle\Export\Filter; use Chill\EventBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class EventDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -61,7 +62,7 @@ class EventDateFilter implements FilterInterface return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, [ @@ -72,12 +73,27 @@ class EventDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'Filtered by date of event: only between %date_from% and %date_to%', @@ -88,7 +104,7 @@ class EventDateFilter implements FilterInterface ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filtered by event date'; } diff --git a/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php index 4b64022ea..5314a0bc6 100644 --- a/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php +++ b/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php @@ -15,6 +15,7 @@ use Chill\EventBundle\Entity\EventType; use Chill\EventBundle\Export\Declarations; use Chill\EventBundle\Repository\EventTypeRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -24,6 +25,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class EventTypeFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, protected EventTypeRepository $eventTypeRepository, @@ -34,7 +37,7 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('event.type', ':selected_event_types'); @@ -47,7 +50,7 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('types', EntityType::class, [ 'choices' => $this->eventTypeRepository->findAllActive(), @@ -61,12 +64,27 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['types' => $this->normalizeDoctrineEntity($formData['types'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['types' => $this->denormalizeDoctrineEntity($formData['types'], $this->eventTypeRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $typeNames = array_map( fn (EventType $t): string => $this->translatableStringHelper->localize($t->getName()), @@ -78,12 +96,12 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filtered by event type'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['types'] || 0 === \count($data['types'])) { $context diff --git a/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php index 612cea84e..4ef2ec2aa 100644 --- a/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php +++ b/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php @@ -15,6 +15,7 @@ use Chill\EventBundle\Entity\Role; use Chill\EventBundle\Export\Declarations; use Chill\EventBundle\Repository\RoleRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -24,6 +25,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class RoleFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, protected RoleRepository $roleRepository, @@ -34,7 +37,7 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('event_part.role', ':selected_part_roles'); @@ -47,7 +50,7 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface return Declarations::EVENT_PARTICIPANTS; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('part_roles', EntityType::class, [ 'choices' => $this->roleRepository->findAllActive(), @@ -61,12 +64,27 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['part_roles' => $this->normalizeDoctrineEntity($formData['part_roles'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['part_roles' => $this->denormalizeDoctrineEntity($formData['part_roles'], $this->roleRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $roleNames = array_map( fn (Role $r): string => $this->translatableStringHelper->localize($r->getName()), @@ -78,12 +96,12 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by participant roles'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['part_roles'] || 0 === \count($data['part_roles'])) { $context diff --git a/src/Bundle/ChillJobBundle/src/Export/ListCV.php b/src/Bundle/ChillJobBundle/src/Export/ListCV.php index 896c7db01..7fd23abf0 100644 --- a/src/Bundle/ChillJobBundle/src/Export/ListCV.php +++ b/src/Bundle/ChillJobBundle/src/Export/ListCV.php @@ -33,6 +33,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class ListCV implements ListInterface, ExportElementValidatedInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; /** * @var array */ @@ -71,14 +72,14 @@ class ListCV implements ListInterface, ExportElementValidatedInterface * * @param mixed $data the data, as returned by the user */ - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} /** * get a title, which will be used in UI (and translated). * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Liste des CVs par personne'; } @@ -132,15 +133,28 @@ class ListCV implements ListInterface, ExportElementValidatedInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->normalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->normalizeDate($formData['reportdate_max'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->denormalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->denormalizeDate($formData['reportdate_max'])]; + } + /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType() + public function getType(): string { return Person::class; } @@ -156,22 +170,7 @@ class ListCV implements ListInterface, ExportElementValidatedInterface return 'Crée une liste des CVs en fonction de différents paramètres.'; } - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { return $this->entityManager->createQueryBuilder() ->from('ChillPersonBundle:Person', 'person'); @@ -215,10 +214,8 @@ class ListCV implements ListInterface, ExportElementValidatedInterface * this function will return `array('count_id')`. * * @param mixed[] $data the data from the export's form (added by self::buildForm) - * - * @return array */ - public function getQueryKeys($data) + public function getQueryKeys($data): array { return array_keys($data['fields']); } @@ -239,7 +236,7 @@ class ListCV implements ListInterface, ExportElementValidatedInterface * @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb * @param mixed[] $data the data from the export's form (added by self::buildForm) */ - public function getResult($qb, $data) + public function getResult($qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { $qb->select('person.id'); diff --git a/src/Bundle/ChillJobBundle/src/Export/ListFrein.php b/src/Bundle/ChillJobBundle/src/Export/ListFrein.php index 8e3a64294..9517bbdfb 100644 --- a/src/Bundle/ChillJobBundle/src/Export/ListFrein.php +++ b/src/Bundle/ChillJobBundle/src/Export/ListFrein.php @@ -34,6 +34,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class ListFrein implements ListInterface, ExportElementValidatedInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; /** * @var array */ @@ -84,14 +85,14 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface * * @param mixed $data the data, as returned by the user */ - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} /** * get a title, which will be used in UI (and translated). * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Liste des freins identifiés par personne'; } @@ -145,15 +146,28 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->normalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->normalizeDate($formData['reportdate_max'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->denormalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->denormalizeDate($formData['reportdate_max'])]; + } + /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType() + public function getType(): string { return Person::class; } @@ -169,22 +183,7 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface return 'Crée une liste des personnes et de leurs freins identifiés en fonction de différents paramètres.'; } - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { return $this->entityManager->createQueryBuilder() ->from(Person::class, 'person'); @@ -238,10 +237,8 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface * this function will return `array('count_id')`. * * @param mixed[] $data the data from the export's form (added by self::buildForm) - * - * @return array */ - public function getQueryKeys($data) + public function getQueryKeys($data): array { $freins = self::FREINS; $fields = []; @@ -332,7 +329,7 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface * @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb * @param mixed[] $data the data from the export's form (added by self::buildForm) */ - public function getResult($qb, $data) + public function getResult($qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { $qb->select('person.id'); diff --git a/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php b/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php index 0a1eeb753..c4a525c7a 100644 --- a/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php +++ b/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php @@ -34,6 +34,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; /** * @var array */ @@ -89,14 +90,14 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn * * @param mixed $data the data, as returned by the user */ - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} /** * get a title, which will be used in UI (and translated). * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Liste des projets professionnels par personne'; } @@ -151,15 +152,28 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->normalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->normalizeDate($formData['reportdate_max'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->denormalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->denormalizeDate($formData['reportdate_max'])]; + } + /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType() + public function getType(): string { return Person::class; } @@ -175,22 +189,7 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn return 'Crée une liste des personnes et de leur projet professionnel en fonction de différents paramètres.'; } - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { return $this->entityManager->createQueryBuilder() ->from('ChillPersonBundle:Person', 'person'); @@ -244,10 +243,8 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn * this function will return `array('count_id')`. * * @param mixed[] $data the data from the export's form (added by self::buildForm) - * - * @return array */ - public function getQueryKeys($data) + public function getQueryKeys($data): array { $projet_professionnel = self::PPROF; @@ -370,7 +367,7 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn * @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb * @param mixed[] $data the data from the export's form (added by self::buildForm) */ - public function getResult($qb, $data) + public function getResult($qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { $qb->select('person.id'); diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php index bca92b85c..07d794ee5 100644 --- a/src/Bundle/ChillMainBundle/ChillMainBundle.php +++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php @@ -14,7 +14,6 @@ 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\ExportsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass; @@ -66,7 +65,6 @@ class ChillMainBundle extends Bundle $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 ExportsCompilerPass(), \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); diff --git a/src/Bundle/ChillMainBundle/Controller/ExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportController.php index 08b89e4bb..693c0e55e 100644 --- a/src/Bundle/ChillMainBundle/Controller/ExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/ExportController.php @@ -11,35 +11,39 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; +use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\DirectExportInterface; +use Chill\MainBundle\Export\ExportConfigNormalizer; +use Chill\MainBundle\Export\ExportConfigProcessor; use Chill\MainBundle\Export\ExportFormHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Form\SavedExportType; +use Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\FormatterType; use Chill\MainBundle\Form\Type\Export\PickCenterType; -use Chill\MainBundle\Redis\ChillRedis; -use Chill\MainBundle\Repository\SavedExportRepositoryInterface; +use Chill\MainBundle\Repository\SavedExportOrExportGenerationRepository; +use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; -use Symfony\Contracts\Translation\TranslatorInterface; /** * Class ExportController @@ -50,117 +54,23 @@ class ExportController extends AbstractController private readonly bool $filterStatsByCenters; public function __construct( - private readonly ChillRedis $redis, private readonly ExportManager $exportManager, private readonly FormFactoryInterface $formFactory, private readonly LoggerInterface $logger, private readonly SessionInterface $session, - private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $entityManager, private readonly ExportFormHelper $exportFormHelper, - private readonly SavedExportRepositoryInterface $savedExportRepository, private readonly Security $security, ParameterBagInterface $parameterBag, + private readonly MessageBusInterface $messageBus, + private readonly ClockInterface $clock, + private readonly ExportConfigNormalizer $exportConfigNormalizer, + private readonly SavedExportOrExportGenerationRepository $savedExportOrExportGenerationRepository, + private readonly ExportConfigProcessor $exportConfigProcessor, ) { $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - #[Route(path: '/{_locale}/exports/download/{alias}', name: 'chill_main_export_download', methods: ['GET'])] - public function downloadResultAction(Request $request, mixed $alias) - { - /** @var ExportManager $exportManager */ - $exportManager = $this->exportManager; - $export = $exportManager->getExport($alias); - $key = $request->query->get('key', null); - $savedExport = $this->getSavedExportFromRequest($request); - - [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key, $savedExport); - - $formatterAlias = $exportManager->getFormatterAlias($dataExport['export']); - - if (null !== $formatterAlias) { - $formater = $exportManager->getFormatter($formatterAlias); - } else { - $formater = null; - } - - $viewVariables = [ - 'alias' => $alias, - 'export' => $export, - 'export_group' => $this->getExportGroup($export), - 'saved_export' => $savedExport, - ]; - - if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { - // due to a bug in php, we add the mime type in the download view - $viewVariables['mime_type'] = 'text/csv'; - } - - return $this->render('@ChillMain/Export/download.html.twig', $viewVariables); - } - - /** - * Generate a report. - * - * This action must work with GET queries. - * - * @param string $alias - */ - #[Route(path: '/{_locale}/exports/generate/{alias}', name: 'chill_main_export_generate', methods: ['GET'])] - public function generateAction(Request $request, $alias): Response - { - /** @var ExportManager $exportManager */ - $exportManager = $this->exportManager; - $key = $request->query->get('key', null); - $savedExport = $this->getSavedExportFromRequest($request); - - [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key, $savedExport); - - return $exportManager->generate( - $alias, - $dataCenters['centers'], - $dataExport['export'], - null !== $dataFormatter ? $dataFormatter['formatter'] : [] - ); - } - - /** - * @throws \RedisException - */ - #[Route(path: '/{_locale}/exports/generate-from-saved/{id}', name: 'chill_main_export_generate_from_saved')] - public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse - { - $this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport); - - $key = md5(uniqid((string) random_int(0, mt_getrandmax()), false)); - - $this->redis->setEx($key, 3600, \serialize($savedExport->getOptions())); - - return $this->redirectToRoute( - 'chill_main_export_download', - [ - 'alias' => $savedExport->getExportAlias(), - 'key' => $key, 'prevent_save' => true, - 'returnPath' => $this->generateUrl('chill_main_export_saved_list_my'), - ] - ); - } - - /** - * Render the list of available exports. - */ - #[Route(path: '/{_locale}/exports/', name: 'chill_main_export_index')] - public function indexAction(): Response - { - $exportManager = $this->exportManager; - - $exports = $exportManager->getExportsGrouped(true); - - return $this->render('@ChillMain/Export/layout.html.twig', [ - 'grouped_exports' => $exports, - ]); - } - /** * handle the step to build a query for an export. * @@ -197,64 +107,6 @@ class ExportController extends AbstractController }; } - #[Route(path: '/{_locale}/export/saved/update-from-key/{id}/{key}', name: 'chill_main_export_saved_edit_options_from_key')] - public function editSavedExportOptionsFromKey(SavedExport $savedExport, string $key): Response - { - $this->denyAccessUnlessGranted('ROLE_USER'); - $user = $this->getUser(); - - if (!$user instanceof User) { - throw new AccessDeniedHttpException(); - } - - $data = $this->rebuildRawData($key); - - $savedExport - ->setOptions($data); - - $this->entityManager->flush(); - - return $this->redirectToRoute('chill_main_export_saved_edit', ['id' => $savedExport->getId()]); - } - - #[Route(path: '/{_locale}/export/save-from-key/{alias}/{key}', name: 'chill_main_export_save_from_key')] - public function saveFromKey(string $alias, string $key, Request $request): Response - { - $this->denyAccessUnlessGranted('ROLE_USER'); - $user = $this->getUser(); - - if (!$user instanceof User) { - throw new AccessDeniedHttpException(); - } - - $data = $this->rebuildRawData($key); - - $savedExport = new SavedExport(); - $savedExport - ->setOptions($data) - ->setExportAlias($alias) - ->setUser($user); - - $form = $this->createForm(SavedExportType::class, $savedExport); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $this->entityManager->persist($savedExport); - $this->entityManager->flush(); - - return $this->redirectToRoute('chill_main_export_index'); - } - - return $this->render( - '@ChillMain/SavedExport/new.html.twig', - [ - 'form' => $form->createView(), - 'saved_export' => $savedExport, - ] - ); - } - /** * create a form to show on different steps. * @@ -262,19 +114,26 @@ class ExportController extends AbstractController */ protected function createCreateFormExport(string $alias, string $step, array $data, ?SavedExport $savedExport): FormInterface { - /** @var ExportManager $exportManager */ $exportManager = $this->exportManager; $isGenerate = str_starts_with($step, 'generate_'); + $canEditFull = $this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT); + + if (!$canEditFull && null === $savedExport) { + throw new AccessDeniedHttpException('The user is not allowed to edit all filter, it should edit only SavedExport'); + } $options = match ($step) { 'export', 'generate_export' => [ 'export_alias' => $alias, - 'picked_centers' => $exportManager->getPickedCenters($data['centers'] ?? []), + 'picked_centers' => $this->filterStatsByCenters ? $this->exportFormHelper->getPickedCenters($data) : [], + 'can_edit_full' => $canEditFull, + 'allowed_filters' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedFilters($savedExport->getOptions()['filters']), + 'allowed_aggregators' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedAggregators($savedExport->getOptions()['aggregators']), ], 'formatter', 'generate_formatter' => [ 'export_alias' => $alias, 'formatter_alias' => $exportManager->getFormatterAlias($data['export']), - 'aggregator_aliases' => $exportManager->getUsedAggregatorsAliases($data['export']), + 'aggregator_aliases' => $exportManager->getUsedAggregatorsAliases($data['export']['aggregators']), ], default => [ 'export_alias' => $alias, @@ -283,14 +142,14 @@ class ExportController extends AbstractController $defaultFormData = match ($savedExport) { null => $this->exportFormHelper->getDefaultData($step, $exportManager->getExport($alias), $options), - default => $this->exportFormHelper->savedExportDataToFormData($savedExport, $step, $options), + default => $this->exportFormHelper->savedExportDataToFormData($savedExport, $step), }; $builder = $this->formFactory ->createNamedBuilder( '', FormType::class, - $defaultFormData, + 'centers' === $step ? ['centers' => $defaultFormData] : $defaultFormData, [ 'method' => $isGenerate ? Request::METHOD_GET : Request::METHOD_POST, 'csrf_protection' => !$isGenerate, @@ -429,13 +288,14 @@ class ExportController extends AbstractController * and redirect to the `generate` action. * * The data from previous steps is removed from session. - * - * @param string $alias - * - * @return RedirectResponse */ - private function forwardToGenerate(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport) + private function forwardToGenerate(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport): Response { + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedHttpException('only regular users can generate export'); + } $dataCenters = $this->session->get('centers_step_raw', null); $dataFormatter = $this->session->get('formatter_step_raw', null); $dataExport = $this->session->get('export_step_raw', null); @@ -448,60 +308,82 @@ class ExportController extends AbstractController ]); } - $parameters = [ - 'formatter' => $dataFormatter ?? [], - 'export' => $dataExport ?? [], - 'centers' => $dataCenters ?? [], - 'alias' => $alias, - ]; - unset($parameters['_token']); - $key = md5(uniqid((string) random_int(0, mt_getrandmax()), false)); + $dataToNormalize = $this->buildExportDataForNormalization( + $alias, + $dataCenters, + $dataExport, + $dataFormatter, + $savedExport, + ); - $this->redis->setEx($key, 3600, \serialize($parameters)); + $deleteAt = $this->clock->now()->add(new \DateInterval('P6M')); + $options = $this->exportConfigNormalizer->normalizeConfig($alias, $dataToNormalize); + $exportGeneration = match (null === $savedExport) { + true => new ExportGeneration($alias, $options, $deleteAt), + false => ExportGeneration::fromSavedExport($savedExport, $deleteAt, $options), + }; + + $this->entityManager->persist($exportGeneration); + $this->entityManager->flush(); + $this->messageBus->dispatch(new ExportRequestGenerationMessage($exportGeneration, $user)); // remove data from session + $this->session->remove('centers_step_raw'); $this->session->remove('export_step_raw'); $this->session->remove('export_step'); $this->session->remove('formatter_step_raw'); $this->session->remove('formatter_step'); - return $this->redirectToRoute('chill_main_export_download', [ - 'key' => $key, - 'alias' => $alias, - 'from_saved' => $savedExport?->getId(), - ]); + return $this->redirectToRoute('chill_main_export-generation_wait', ['id' => $exportGeneration->getId()]); } - private function rebuildData($key, ?SavedExport $savedExport) + /** + * Build the export form data into a way suitable for normalization. + * + * @param string $alias the export alias + * @param array $dataCenters Raw data from center step + * @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 { - $rawData = $this->rebuildRawData($key); - - $alias = $rawData['alias']; - if ($this->filterStatsByCenters) { - $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], $savedExport); - $formCenters->submit($rawData['centers']); - $dataCenters = $formCenters->getData(); + $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null); + $formCenters->submit($dataCenters); + $dataAsCollection = $formCenters->getData()['centers']; + $centers = $dataAsCollection['centers']; + $regroupments = $dataAsCollection['regroupments'] ?? []; + $dataCenters = [ + 'centers' => $centers instanceof Collection ? $centers->toArray() : $centers, + 'regroupments' => $regroupments instanceof Collection ? $regroupments->toArray() : $regroupments, + ]; } else { - $dataCenters = ['centers' => []]; + $dataCenters = ['centers' => [], 'regroupments' => []]; } $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, $savedExport); - $formExport->submit($rawData['export']); + $formExport->submit($dataExport); $dataExport = $formExport->getData(); - if (\count($rawData['formatter']) > 0) { + if (\count($dataFormatter) > 0) { $formFormatter = $this->createCreateFormExport( $alias, 'generate_formatter', $dataExport, $savedExport ); - $formFormatter->submit($rawData['formatter']); + $formFormatter->submit($dataFormatter); $dataFormatter = $formFormatter->getData(); } - return [$dataCenters, $dataExport, $dataFormatter ?? null]; + return [ + 'centers' => ['centers' => $dataCenters['centers'], 'regroupments' => $dataCenters['regroupments']], + 'export' => $dataExport['export']['export'] ?? [], + 'filters' => $dataExport['export']['filters'] ?? [], + 'aggregators' => $dataExport['export']['aggregators'] ?? [], + 'pick_formatter' => $dataExport['export']['pick_formatter']['alias'], + 'formatter' => $dataFormatter['formatter'] ?? [], + ]; } /** @@ -509,7 +391,7 @@ class ExportController extends AbstractController * * @return Response */ - private function selectCentersStep(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport = null) + private function selectCentersStep(Request $request, DirectExportInterface|ExportInterface $export, $alias, ExportGeneration|SavedExport|null $savedExport = null) { if (!$this->filterStatsByCenters) { return $this->redirectToRoute('chill_main_export_new', [ @@ -522,7 +404,12 @@ class ExportController extends AbstractController /** @var ExportManager $exportManager */ $exportManager = $this->exportManager; - $form = $this->createCreateFormExport($alias, 'centers', [], $savedExport); + $form = $this->createCreateFormExport( + $alias, + 'centers', + $this->exportFormHelper->getDefaultData('centers', $export, []), + $savedExport + ); if (Request::METHOD_POST === $request->getMethod()) { $form->handleRequest($request); @@ -538,7 +425,7 @@ class ExportController extends AbstractController false === $exportManager->isGrantedForElement( $export, null, - $exportManager->getPickedCenters($data['centers']) + $this->exportFormHelper->getPickedCenters($data['centers']), ) ) { throw $this->createAccessDeniedException('you do not have access to this export for those centers'); @@ -548,7 +435,7 @@ class ExportController extends AbstractController 'centers_step_raw', $request->request->all() ); - $this->session->set('centers_step', $data); + $this->session->set('centers_step', $data['centers']); return $this->redirectToRoute('chill_main_export_new', [ 'step' => $this->getNextStep('centers', $export), @@ -632,43 +519,15 @@ class ExportController extends AbstractController } } - private function rebuildRawData(?string $key): array - { - if (null === $key) { - throw $this->createNotFoundException('key does not exists'); - } - - if (1 !== $this->redis->exists($key)) { - $this->addFlash('error', $this->translator->trans('This report is not available any more')); - - throw $this->createNotFoundException('key does not exists'); - } - - $serialized = $this->redis->get($key); - - if (false === $serialized) { - throw new \LogicException('the key could not be reached from redis'); - } - - $rawData = \unserialize($serialized); - - $this->logger->notice('[export] choices for an export unserialized', [ - 'key' => $key, - 'rawData' => json_encode($rawData, JSON_THROW_ON_ERROR), - ]); - - return $rawData; - } - - private function getSavedExportFromRequest(Request $request): ?SavedExport + private function getSavedExportFromRequest(Request $request): SavedExport|ExportGeneration|null { $savedExport = match ($savedExportId = $request->query->get('from_saved', '')) { '' => null, - default => $this->savedExportRepository->find($savedExportId), + default => $this->savedExportOrExportGenerationRepository->findById($savedExportId), }; - if (null !== $savedExport && !$this->security->isGranted(SavedExportVoter::EDIT, $savedExport)) { - throw new AccessDeniedHttpException('saved export edition not allowed'); + if (null !== $savedExport && !$this->security->isGranted(SavedExportVoter::GENERATE, $savedExport)) { + throw new AccessDeniedHttpException('saved export generation not allowed'); } return $savedExport; diff --git a/src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php b/src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php new file mode 100644 index 000000000..0e1b3b8c4 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php @@ -0,0 +1,64 @@ +security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException('Only users can download an export'); + } + + $export = $this->exportManager->getExport($exportGeneration->getExportAlias()); + + return new Response( + $this->twig->render('@ChillMain/ExportGeneration/wait.html.twig', ['exportGeneration' => $exportGeneration, 'export' => $export]), + ); + } + + #[Route('/api/1.0/main/export-generation/{id}/object', methods: ['GET'])] + public function objectStatus(ExportGeneration $exportGeneration): JsonResponse + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException('Only users can download an export'); + } + + return new JsonResponse( + $this->serializer->serialize( + $exportGeneration, + 'json', + [AbstractNormalizer::GROUPS => ['read']], + ), + json: true, + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php new file mode 100644 index 000000000..199b9c92d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php @@ -0,0 +1,62 @@ +security->isGranted(SavedExportVoter::GENERATE, $export)) { + throw new AccessDeniedHttpException('Not allowed to generate an export from this saved export'); + } + $user = $this->security->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedHttpException('Only users can create exports'); + } + + $exportGeneration = ExportGeneration::fromSavedExport($export, $this->clock->now()->add(new \DateInterval('P6M'))); + + $this->entityManager->persist($exportGeneration); + $this->entityManager->flush(); + + $this->messageBus->dispatch(new ExportRequestGenerationMessage($exportGeneration, $user)); + + return new JsonResponse( + $this->serializer->serialize($exportGeneration, 'json', ['groups' => ['read']]), + json: true, + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php b/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php new file mode 100644 index 000000000..4f3de7aef --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php @@ -0,0 +1,62 @@ +security->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedHttpException('Only regular user can see this page'); + } + + if (!$this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT)) { + throw new AccessDeniedHttpException(sprintf('Require the %s role', ChillExportVoter::COMPOSE_EXPORT)); + } + + $exports = $this->exportManager->getExportsGrouped(true); + + $lastExecutions = []; + foreach ($this->exportManager->getExports() as $alias => $export) { + $lastExecutions[$alias] = $this->exportGenerationRepository->findExportGenerationByAliasAndUser($alias, $user, 5); + } + + return new Response( + $this->twig->render('@ChillMain/Export/layout.html.twig', [ + 'grouped_exports' => $exports, + 'last_executions' => $lastExecutions, + ]), + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php index 82d41a9e1..20b9bf1c5 100644 --- a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php @@ -11,13 +11,13 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; +use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; -use Chill\MainBundle\Export\ExportInterface; +use Chill\MainBundle\Export\ExportDescriptionHelper; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Form\SavedExportType; -use Chill\MainBundle\Repository\SavedExportRepositoryInterface; +use Chill\MainBundle\Security\Authorization\ExportGenerationVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -25,16 +25,28 @@ use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -class SavedExportController +final readonly class SavedExportController { - public function __construct(private readonly \Twig\Environment $templating, private readonly EntityManagerInterface $entityManager, private readonly ExportManager $exportManager, private readonly FormFactoryInterface $formFactory, private readonly SavedExportRepositoryInterface $savedExportRepository, private readonly Security $security, private readonly SessionInterface $session, private readonly TranslatorInterface $translator, private readonly UrlGeneratorInterface $urlGenerator) {} + public function __construct( + private \Twig\Environment $templating, + private EntityManagerInterface $entityManager, + private ExportManager $exportManager, + private FormFactoryInterface $formFactory, + private Security $security, + private TranslatorInterface $translator, + private UrlGeneratorInterface $urlGenerator, + private ExportDescriptionHelper $exportDescriptionHelper, + ) {} #[Route(path: '/{_locale}/exports/saved/{id}/delete', name: 'chill_main_export_saved_delete')] public function delete(SavedExport $savedExport, Request $request): Response @@ -51,7 +63,10 @@ class SavedExportController $this->entityManager->remove($savedExport); $this->entityManager->flush(); - $this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Export is deleted')); + $session = $request->getSession(); + if ($session instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Export is deleted')); + } return new RedirectResponse( $this->urlGenerator->generate('chill_main_export_saved_list_my') @@ -69,6 +84,105 @@ class SavedExportController ); } + #[Route(path: '/exports/saved/create-from-export-generation/{id}/new', name: 'chill_main_export_saved_create_from_export_generation')] + public function createFromExportGeneration(ExportGeneration $exportGeneration, Request $request): Response + { + if (!$this->security->isGranted(ExportGenerationVoter::VIEW, $exportGeneration)) { + throw new AccessDeniedHttpException(); + } + + $user = $this->security->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedHttpException('only regular user can create a saved export'); + } + + $export = $this->exportManager->getExport($exportGeneration->getExportAlias()); + $title = $export->getTitle() instanceof TranslatableInterface ? $export->getTitle()->trans($this->translator) : + $this->translator->trans($export->getTitle()); + + $savedExport = new SavedExport(); + $savedExport + ->setExportAlias($exportGeneration->getExportAlias()) + ->setUser($user) + ->setOptions($exportGeneration->getOptions()) + ->setTitle( + $request->query->has('title') ? $request->query->get('title') : $title + ); + + if ($exportGeneration->isLinkedToSavedExport()) { + $savedExport->setDescription($exportGeneration->getSavedExport()->getDescription()); + } else { + $savedExport->setDescription( + implode( + "\n", + array_map( + fn (string $item) => '- '.$item."\n", + $this->exportDescriptionHelper->describe($savedExport->getExportAlias(), $savedExport->getOptions(), includeExportTitle: false) + ) + ) + ); + } + + return $this->handleEdit($savedExport, $request, true); + } + + #[Route(path: '/exports/saved/duplicate-from-saved-export/{id}/new', name: 'chill_main_export_saved_duplicate')] + public function duplicate(SavedExport $previousSavedExport, Request $request): Response + { + $user = $this->security->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedHttpException('only regular user can create a saved export'); + } + + if (!$this->security->isGranted(SavedExportVoter::EDIT, $previousSavedExport)) { + throw new AccessDeniedHttpException('Not allowed to edit this saved export'); + } + + $savedExport = new SavedExport(); + $savedExport + ->setExportAlias($previousSavedExport->getExportAlias()) + ->setUser($user) + ->setOptions($previousSavedExport->getOptions()) + ->setDescription($previousSavedExport->getDescription()) + ->setTitle( + $request->query->has('title') ? + $request->query->get('title') : + $previousSavedExport->getTitle().' ('.$this->translator->trans('saved_export.Duplicated').' '.(new \DateTimeImmutable('now'))->format('d-m-Y H:i:s').')' + ); + + return $this->handleEdit($savedExport, $request); + + } + + private function handleEdit(SavedExport $savedExport, Request $request, bool $showWarningAutoGeneratedDescription = false): Response + { + $form = $this->formFactory->create(SavedExportType::class, $savedExport); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->persist($savedExport); + $this->entityManager->flush(); + + if (($session = $request->getSession()) instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Saved export is saved!')); + } + + return new RedirectResponse( + $this->urlGenerator->generate('chill_main_export_saved_list_my'), + ); + } + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/new.html.twig', + [ + 'form' => $form->createView(), + 'showWarningAutoGeneratedDescription' => $showWarningAutoGeneratedDescription, + ], + ), + ); + } + #[Route(path: '/{_locale}/exports/saved/{id}/edit', name: 'chill_main_export_saved_edit')] public function edit(SavedExport $savedExport, Request $request): Response { @@ -83,10 +197,12 @@ class SavedExportController if ($form->isSubmitted() && $form->isValid()) { $this->entityManager->flush(); - $this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Saved export is saved!')); + if (($session = $request->getSession()) instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Saved export is saved!')); + } return new RedirectResponse( - $this->urlGenerator->generate('chill_main_export_saved_list_my') + $this->urlGenerator->generate('chill_main_export_saved_list_my'), ); } @@ -95,45 +211,37 @@ class SavedExportController '@ChillMain/SavedExport/edit.html.twig', [ 'form' => $form->createView(), - ] - ) + ], + ), ); } - #[Route(path: '/{_locale}/exports/saved/my', name: 'chill_main_export_saved_list_my')] - public function list(): Response + #[Route(path: '/{_locale}/exports/saved/{savedExport}/edit-options/{exportGeneration}', name: 'chill_main_export_saved_options_edit')] + public function updateOptionsFromGeneration(SavedExport $savedExport, ExportGeneration $exportGeneration, Request $request): Response { - $user = $this->security->getUser(); - - if (!$this->security->isGranted('ROLE_USER') || !$user instanceof User) { - throw new AccessDeniedHttpException(); + if (!$this->security->isGranted(SavedExportVoter::DUPLICATE, $savedExport)) { + throw new AccessDeniedHttpException('You are not allowed to access this saved export'); } - $exports = $this->savedExportRepository->findByUser($user, ['title' => 'ASC']); - - // group by center - /** @var array $exportsGrouped */ - $exportsGrouped = []; - - foreach ($exports as $savedExport) { - $export = $this->exportManager->getExport($savedExport->getExportAlias()); - - $exportsGrouped[ - $export instanceof GroupedExportInterface - ? $this->translator->trans($export->getGroup()) : '_' - ][] = ['saved' => $savedExport, 'export' => $export]; + if (!$this->security->isGranted(ExportGenerationVoter::VIEW, $exportGeneration)) { + throw new AccessDeniedHttpException('You are not allowed to access this export generation'); } - ksort($exportsGrouped); + if ($savedExport->getExportAlias() !== $exportGeneration->getExportAlias()) { + throw new UnprocessableEntityHttpException('export alias does not match'); + } - return new Response( - $this->templating->render( - '@ChillMain/SavedExport/index.html.twig', - [ - 'grouped_exports' => $exportsGrouped, - 'total' => \count($exports), - ] - ) + $savedExport->setOptions($exportGeneration->getOptions()); + + $this->entityManager->flush(); + + $session = $request->getSession(); + if ($session instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Options updated successfully')); + } + + return new RedirectResponse( + $this->urlGenerator->generate('chill_main_export_saved_edit', ['id' => $savedExport->getId()]), ); } } diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php new file mode 100644 index 000000000..82e9913da --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php @@ -0,0 +1,104 @@ +security->getUser(); + + if (!$this->security->isGranted(ChillExportVoter::GENERATE_SAVED_EXPORT) || !$user instanceof User) { + throw new AccessDeniedHttpException(sprintf('Missing role: %s', ChillExportVoter::GENERATE_SAVED_EXPORT)); + } + + $filter = $this->buildFilter(); + + $filterParams = []; + if ('' !== $filter->getQueryString() && null !== $filter->getQueryString()) { + $filterParams[SavedExportRepositoryInterface::FILTER_DESCRIPTION | SavedExportRepositoryInterface::FILTER_TITLE] = $filter->getQueryString(); + } + + $exports = array_filter( + $this->savedExportRepository->findSharedWithUser($user, ['exportAlias' => 'ASC', 'title' => 'ASC'], filters: $filterParams), + fn (SavedExport $savedExport): bool => $this->security->isGranted(SavedExportVoter::GENERATE, $savedExport), + ); + + // group by center + /** @var array $exportsGrouped */ + $exportsGrouped = []; + + foreach ($exports as $savedExport) { + $export = $this->exportManager->getExport($savedExport->getExportAlias()); + + $exportsGrouped[$export instanceof GroupedExportInterface + ? $this->translator->trans($export->getGroup()) : '_'][] = ['saved' => $savedExport, 'export' => $export]; + } + + ksort($exportsGrouped); + + // get last executions + $lastExecutions = []; + foreach ($exports as $savedExport) { + $lastExecutions[$savedExport->getId()->toString()] = $this->exportGenerationRepository + ->findExportGenerationBySavedExportAndUser($savedExport, $user, 5); + } + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/index.html.twig', + [ + 'grouped_exports' => $exportsGrouped, + 'total' => \count($exports), + 'last_executions' => $lastExecutions, + 'filter' => $filter, + ], + ), + ); + } + + private function buildFilter(): FilterOrderHelper + { + $filter = $this->filterOrderHelperFactory->create('saved-export-index-filter'); + $filter->addSearchBox(); + + return $filter->build(); + } +} diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php index 3afdefbab..e71c662b3 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php @@ -11,7 +11,10 @@ declare(strict_types=1); namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\GroupCenter; +use Chill\MainBundle\Entity\PermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; use Chill\MainBundle\Entity\User; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; @@ -62,6 +65,15 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde public function load(ObjectManager $manager): void { + $roleScope = new RoleScope(); + $roleScope->setRole('CHILL_MAIN_COMPOSE_EXPORT'); + $permissionGroup = new PermissionsGroup(); + $permissionGroup->setName('export'); + $permissionGroup->addRoleScope($roleScope); + + $manager->persist($roleScope); + $manager->persist($permissionGroup); + foreach (self::$refs as $username => $params) { $user = new User(); @@ -81,7 +93,14 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde ->setEmail(sprintf('%s@chill.social', \str_replace(' ', '', (string) $username))); foreach ($params['groupCenterRefs'] as $groupCenterRef) { - $user->addGroupCenter($this->getReference($groupCenterRef, GroupCenter::class)); + $user->addGroupCenter($gc = $this->getReference($groupCenterRef, GroupCenter::class)); + + $exportGroupCenter = new GroupCenter(); + $exportGroupCenter->setPermissionsGroup($permissionGroup); + $exportGroupCenter->setCenter($gc->getCenter()); + $manager->persist($exportGroupCenter); + + $user->addGroupCenter($exportGroupCenter); } echo 'Creating user '.$username."... \n"; diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 9fa3b0f42..6b33b998d 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -78,6 +78,7 @@ use Chill\MainBundle\Form\RegroupmentType; use Chill\MainBundle\Form\UserGroupType; use Chill\MainBundle\Form\UserJobType; use Chill\MainBundle\Form\UserType; +use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType; use Ramsey\Uuid\Doctrine\UuidType; use Symfony\Component\Config\FileLocator; @@ -332,6 +333,9 @@ class ChillMainExtension extends Extension implements 'strategy' => 'unanimous', 'allow_if_all_abstain' => false, ], + 'role_hierarchy' => [ + ChillExportVoter::COMPOSE_EXPORT => ChillExportVoter::GENERATE_SAVED_EXPORT, + ], ]); // add crud api diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php deleted file mode 100644 index c2126da1e..000000000 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php +++ /dev/null @@ -1,102 +0,0 @@ -has(ExportManager::class)) { - throw new \LogicException('service '.ExportManager::class.' is not defined. It is required by ExportsCompilerPass'); - } - - $chillManagerDefinition = $container->findDefinition( - ExportManager::class - ); - - $this->compileFormatters($chillManagerDefinition, $container); - $this->compileExportElementsProvider($chillManagerDefinition, $container); - } - - private function compileExportElementsProvider( - Definition $chillManagerDefinition, - ContainerBuilder $container, - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_elements_provider' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['prefix'])) { - throw new \LogicException("the 'prefix' attribute is missing in your service '{$id}' definition"); - } - - if (array_search($attributes['prefix'], $knownAliases, true)) { - throw new \LogicException('There is already a chill.export_elements_provider service with prefix '.$attributes['prefix'].'. Choose another prefix.'); - } - $knownAliases[] = $attributes['prefix']; - - $chillManagerDefinition->addMethodCall( - 'addExportElementsProvider', - [new Reference($id), $attributes['prefix']] - ); - } - } - } - - private function compileFormatters( - Definition $chillManagerDefinition, - ContainerBuilder $container, - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_formatter' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['alias'])) { - throw new \LogicException("the 'alias' attribute is missing in your service '{$id}' definition"); - } - - if (array_search($attributes['alias'], $knownAliases, true)) { - throw new \LogicException('There is already a chill.export_formatter service with alias '.$attributes['alias'].'. Choose another alias.'); - } - $knownAliases[] = $attributes['alias']; - - $chillManagerDefinition->addMethodCall( - 'addFormatter', - [new Reference($id), $attributes['alias']] - ); - } - } - } -} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php index 12a745a0c..3e1086409 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php @@ -23,12 +23,12 @@ class Unaccent extends FunctionNode { private ?\Doctrine\ORM\Query\AST\Node $string = null; - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string { return 'UNACCENT('.$this->string->dispatch($sqlWalker).')'; } - public function parse(\Doctrine\ORM\Query\Parser $parser) + public function parse(\Doctrine\ORM\Query\Parser $parser): void { $parser->match(\Doctrine\ORM\Query\TokenType::T_IDENTIFIER); $parser->match(\Doctrine\ORM\Query\TokenType::T_OPEN_PARENTHESIS); diff --git a/src/Bundle/ChillMainBundle/Entity/ExportGeneration.php b/src/Bundle/ChillMainBundle/Entity/ExportGeneration.php new file mode 100644 index 000000000..4cfbda0f6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/ExportGeneration.php @@ -0,0 +1,150 @@ + ExportGeneration::class])] +class ExportGeneration implements TrackCreationInterface +{ + use TrackCreationTrait; + + #[ORM\Id] + #[ORM\Column(type: 'uuid', unique: true)] + #[Serializer\Groups(['read'])] + private UuidInterface $id; + + + #[ORM\ManyToOne(targetEntity: StoredObject::class, cascade: ['persist', 'refresh'])] + #[ORM\JoinColumn(nullable: false)] + #[Serializer\Groups(['read'])] + private StoredObject $storedObject; + + public function __construct( + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])] + #[Serializer\Groups(['read'])] + private string $exportAlias, + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])] + private array $options = [], + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)] + private ?\DateTimeImmutable $deleteAt = null, + + /** + * The related saved export. + * + * Note that, in some case, the options of this ExportGeneration are not equals to the options of the saved export. + * This happens when the options of the saved export are updated. + */ + #[ORM\ManyToOne(targetEntity: SavedExport::class)] + #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] + private ?SavedExport $savedExport = null, + ) { + $this->id = Uuid::uuid4(); + $this->storedObject = new StoredObject(StoredObject::STATUS_PENDING); + } + + public function setDeleteAt(?\DateTimeImmutable $deleteAt): ExportGeneration + { + $this->deleteAt = $deleteAt; + + return $this; + } + + public function getDeleteAt(): ?\DateTimeImmutable + { + return $this->deleteAt; + } + + public function getId(): UuidInterface + { + return $this->id; + } + + public function getStoredObject(): StoredObject + { + return $this->storedObject; + } + + public function getExportAlias(): string + { + return $this->exportAlias; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getSavedExport(): ?SavedExport + { + return $this->savedExport; + } + + #[Serializer\Groups(['read'])] + #[Serializer\SerializedName('status')] + public function getStatus(): string + { + return $this->getStoredObject()->getStatus(); + } + + public function setSavedExport(SavedExport $savedExport): self + { + $this->savedExport = $savedExport; + + return $this; + } + + public function isLinkedToSavedExport(): bool + { + return null !== $this->savedExport; + } + + /** + * Compares the options of the saved export and the current export generation. + * + * Return false if the current export generation's options are not equal to the one in the saved export. This may + * happens when we update the configuration of a saved export. + */ + public function isConfigurationDifferentFromSavedExport(): bool + { + if (!$this->isLinkedToSavedExport()) { + return false; + } + + return $this->savedExport->getOptions() !== $this->getOptions(); + } + + public static function fromSavedExport(SavedExport $savedExport, ?\DateTimeImmutable $deletedAt = null, ?array $overrideOptions = null): self + { + $generation = new self($savedExport->getExportAlias(), $overrideOptions ?? $savedExport->getOptions(), $deletedAt, $savedExport); + $generation->getStoredObject()->setTitle($savedExport->getTitle()); + + return $generation; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php index 481b530d9..2d7028d21 100644 --- a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php @@ -50,4 +50,9 @@ class SimpleGeographicalUnitDTO #[Serializer\Groups(['read'])] public int $layerId, ) {} + + public function getId(): int + { + return $this->id; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/Regroupment.php b/src/Bundle/ChillMainBundle/Entity/Regroupment.php index 389eceb31..d28784210 100644 --- a/src/Bundle/ChillMainBundle/Entity/Regroupment.php +++ b/src/Bundle/ChillMainBundle/Entity/Regroupment.php @@ -102,4 +102,22 @@ class Regroupment return $this; } + + /** + * Return true if the given center is contained into this regroupment. + */ + public function containsCenter(Center $center): bool + { + return $this->centers->contains($center); + } + + /** + * Return true if at least one of the given centers is contained into this regroupment. + * + * @param list
$centers + */ + public function containsAtLeastOneCenter(array $centers): bool + { + return array_reduce($centers, fn (bool $carry, Center $center) => $carry || $this->containsCenter($center), false); + } } diff --git a/src/Bundle/ChillMainBundle/Entity/SavedExport.php b/src/Bundle/ChillMainBundle/Entity/SavedExport.php index eec6b83e4..7e0306a7e 100644 --- a/src/Bundle/ChillMainBundle/Entity/SavedExport.php +++ b/src/Bundle/ChillMainBundle/Entity/SavedExport.php @@ -15,6 +15,9 @@ use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ReadableCollection; use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -50,9 +53,25 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface #[ORM\ManyToOne(targetEntity: User::class)] private User $user; + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: User::class)] + #[ORM\JoinTable(name: 'chill_main_saved_export_users')] + private Collection $sharedWithUsers; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: UserGroup::class)] + #[ORM\JoinTable(name: 'chill_main_saved_export_usergroups')] + private Collection $sharedWithGroups; + public function __construct() { $this->id = Uuid::uuid4(); + $this->sharedWithUsers = new ArrayCollection(); + $this->sharedWithGroups = new ArrayCollection(); } public function getDescription(): string @@ -119,4 +138,71 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface return $this; } + + public function addShare(User|UserGroup $shareUser): SavedExport + { + if ($shareUser instanceof User) { + if (!$this->sharedWithUsers->contains($shareUser)) { + $this->sharedWithUsers->add($shareUser); + } + } else { + if (!$this->sharedWithGroups->contains($shareUser)) { + $this->sharedWithGroups->add($shareUser); + } + } + + return $this; + } + + public function removeShare(User|UserGroup $shareUser): SavedExport + { + if ($shareUser instanceof User) { + $this->sharedWithUsers->removeElement($shareUser); + } else { + $this->sharedWithGroups->removeElement($shareUser); + } + + return $this; + } + + /** + * @return ReadableCollection + */ + public function getShare(): ReadableCollection + { + return new ArrayCollection([ + ...$this->sharedWithUsers->toArray(), + ...$this->sharedWithGroups->toArray(), + ]); + } + + /** + * Return true if shared with at least one user or one group. + */ + public function isShared(): bool + { + return $this->sharedWithUsers->count() > 0 || $this->sharedWithGroups->count() > 0; + } + + /** + * Determines if the user is shared with either directly or through a group. + * + * @param User $user the user to check + * + * @return bool returns true if the user is shared with directly or via group, otherwise false + */ + public function isSharedWithUser(User $user): bool + { + if ($this->sharedWithUsers->contains($user)) { + return true; + } + + foreach ($this->sharedWithGroups as $group) { + if ($group->contains($user)) { + return true; + } + } + + return false; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/UserGroup.php b/src/Bundle/ChillMainBundle/Entity/UserGroup.php index d720d4ffc..39df04b31 100644 --- a/src/Bundle/ChillMainBundle/Entity/UserGroup.php +++ b/src/Bundle/ChillMainBundle/Entity/UserGroup.php @@ -21,6 +21,19 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; +/** + * Represents a user group entity in the system. + * + * This class is used for managing user groups, including their relationships + * with users, administrative users, and additional metadata such as colors and labels. + * + * Groups may be configured to have mutual exclusion properties based on an + * exclusion key. This ensures that groups sharing the same key cannot coexist + * in certain relationship contexts. + * + * Groups may be related to a UserJob. In that case, a cronjob task ensure that the members of the groups are + * automatically synced with this group. Such groups are also automatically created by the cronjob. + */ #[ORM\Entity] #[ORM\Table(name: 'chill_main_user_group')] // this discriminator key is required for automated denormalization @@ -71,6 +84,13 @@ class UserGroup #[Assert\Email] private string $email = ''; + /** + * UserJob to which the group is related. + */ + #[ORM\ManyToOne(targetEntity: UserJob::class)] + #[ORM\JoinColumn(nullable: true)] + private ?UserJob $userJob = null; + public function __construct() { $this->adminUsers = new ArrayCollection(); @@ -209,6 +229,21 @@ class UserGroup return '' !== $this->email; } + public function hasUserJob(): bool + { + return null !== $this->userJob; + } + + public function getUserJob(): ?UserJob + { + return $this->userJob; + } + + public function setUserJob(?UserJob $userJob): void + { + $this->userJob = $userJob; + } + /** * Checks if the current object is an instance of the UserGroup class. * diff --git a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php index e849dec07..27ce09746 100644 --- a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php +++ b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php @@ -12,25 +12,42 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatableInterface; /** * Interface for Aggregators. * * Aggregators gather result of a query. Most of the time, it will add * a GROUP BY clause. + * + * @template D of array */ interface AggregatorInterface extends ModifierInterface { /** * Add a form to collect data from the user. */ - public function buildForm(FormBuilderInterface $builder); + public function buildForm(FormBuilderInterface $builder): void; /** * Get the default data, that can be use as "data" for the form. + * + * @return D */ public function getFormDefaultData(): array; + /** + * @param D $formData + */ + public function normalizeFormData(array $formData): array; + + /** + * @return D + */ + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; + /** * get a callable which will be able to transform the results into * viewable and understable string. @@ -74,9 +91,9 @@ interface AggregatorInterface extends ModifierInterface * @param string $key The column key, as added in the query * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * - * @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * @return callable(mixed $value): (string|int|\DateTimeInterface|TranslatableInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ - public function getLabels($key, array $values, mixed $data); + public function getLabels(string $key, array $values, mixed $data): callable; /** * give the list of keys the current export added to the queryBuilder in @@ -85,7 +102,9 @@ interface AggregatorInterface extends ModifierInterface * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, * this function will return `array('count_id')`. * - * @param mixed[] $data the data from the export's form (added by self::buildForm) + * @param D $data the data from the export's form (added by self::buildForm) + * + * @return list */ - public function getQueryKeys($data); + public function getQueryKeys(array $data): array; } diff --git a/src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php b/src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php new file mode 100644 index 000000000..2f15bf095 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php @@ -0,0 +1,52 @@ +getLastStart()->getTimestamp() < $this->clock->now()->sub(new \DateInterval('PT24H'))->getTimestamp(); + } + + public function getKey(): string + { + return self::KEY; + } + + public function run(array $lastExecutionData): ?array + { + $now = $this->clock->now(); + + foreach ($this->exportGenerationRepository->findExpiredExportGeneration($now) as $exportGeneration) { + $this->messageBus->dispatch(new Envelope(new RemoveExportGenerationMessage($exportGeneration))); + } + + return ['last-deletion' => $now->getTimestamp()]; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php b/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php index 0949b0be0..0ca0b4a43 100644 --- a/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php @@ -28,8 +28,16 @@ interface DirectExportInterface extends ExportElementInterface /** * Generate the export. + * + * @return FormattedExportGeneration */ - public function generate(array $acl, array $data = []): Response; + public function generate(array $acl, array $data, ExportGenerationContext $context): Response|FormattedExportGeneration; + + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; /** * get a description, which will be used in UI (and translated). diff --git a/src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php b/src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php new file mode 100644 index 000000000..b8f12c6bf --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php @@ -0,0 +1,14 @@ +, regroupments: list}, export: array{form: array, version: int}, filters: array, version: int}>, aggregators: array, version: int}>, pick_formatter: string, formatter: array{form: array, version: int}} + */ +class ExportConfigNormalizer +{ + public function __construct( + private readonly ExportManager $exportManager, + private readonly CenterRepositoryInterface $centerRepository, + private readonly RegroupmentRepositoryInterface $regroupmentRepository, + ) {} + + /** + * @return NormalizedData + */ + public function normalizeConfig(string $exportAlias, array $formData): array + { + $exportData = $formData[ExportType::EXPORT_KEY]; + $export = $this->exportManager->getExport($exportAlias); + + $serialized = [ + 'export' => [ + 'form' => $export->normalizeFormData($exportData), + 'version' => $export->getNormalizationVersion(), + ], + ]; + + $serialized['centers'] = [ + 'centers' => array_values(array_map(static fn (Center $center) => $center->getId(), $formData['centers']['centers'] ?? [])), + 'regroupments' => array_values(array_map(static fn (Regroupment $group) => $group->getId(), $formData['centers']['regroupments'] ?? [])), + ]; + + $filtersSerialized = []; + foreach ($formData[ExportType::FILTER_KEY] as $alias => $filterData) { + $filter = $this->exportManager->getFilter($alias); + $filtersSerialized[$alias][FilterType::ENABLED_FIELD] = (bool) $filterData[FilterType::ENABLED_FIELD]; + if ($filterData[FilterType::ENABLED_FIELD]) { + $filtersSerialized[$alias]['form'] = $filter->normalizeFormData($filterData['form']); + $filtersSerialized[$alias]['version'] = $filter->getNormalizationVersion(); + } + } + $serialized['filters'] = $filtersSerialized; + + $aggregatorsSerialized = []; + foreach ($formData[ExportType::AGGREGATOR_KEY] as $alias => $aggregatorData) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsSerialized[$alias][FilterType::ENABLED_FIELD] = (bool) $aggregatorData[AggregatorType::ENABLED_FIELD]; + if ($aggregatorData[AggregatorType::ENABLED_FIELD]) { + $aggregatorsSerialized[$alias]['form'] = $aggregator->normalizeFormData($aggregatorData['form']); + $aggregatorsSerialized[$alias]['version'] = $aggregator->getNormalizationVersion(); + } + } + $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(); + + return $serialized; + } + + /** + * @param NormalizedData $serializedData + * @param bool $replaceDisabledByDefaultData if true, when a filter is not enabled, the formDefaultData is set + */ + public function denormalizeConfig(string $exportAlias, array $serializedData, bool $replaceDisabledByDefaultData = false): array + { + $export = $this->exportManager->getExport($exportAlias); + $formater = $this->exportManager->getFormatter($serializedData['pick_formatter']); + + $filtersConfig = []; + foreach ($serializedData['filters'] as $alias => $filterData) { + $aggregator = $this->exportManager->getFilter($alias); + $filtersConfig[$alias]['enabled'] = $filterData['enabled']; + + if ($filterData['enabled']) { + $filtersConfig[$alias]['form'] = $aggregator->denormalizeFormData($filterData['form'], $filterData['version']); + } elseif ($replaceDisabledByDefaultData) { + $filtersConfig[$alias]['form'] = $aggregator->getFormDefaultData(); + } + } + + $aggregatorsConfig = []; + foreach ($serializedData['aggregators'] as $alias => $aggregatorData) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsConfig[$alias]['enabled'] = $aggregatorData['enabled']; + + if ($aggregatorData['enabled']) { + $aggregatorsConfig[$alias]['form'] = $aggregator->denormalizeFormData($aggregatorData['form'], $aggregatorData['version']); + } elseif ($replaceDisabledByDefaultData) { + $aggregatorsConfig[$alias]['form'] = $aggregator->getFormDefaultData(); + } + } + + return [ + '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']), + '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)), + ], + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php b/src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php new file mode 100644 index 000000000..d898ac1af --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php @@ -0,0 +1,49 @@ + + */ + public function retrieveUsedAggregators(mixed $data): iterable + { + if (null === $data) { + return []; + } + + foreach ($data as $alias => $aggregatorData) { + if ($this->exportManager->hasAggregator($alias) && true === $aggregatorData['enabled']) { + yield $alias => $this->exportManager->getAggregator($alias); + } + } + } + + /** + * @return iterable + */ + public function retrieveUsedFilters(mixed $data): iterable + { + if (null === $data) { + return []; + } + + foreach ($data as $alias => $filterData) { + if ($this->exportManager->hasFilter($alias) && true === $filterData['enabled']) { + yield $alias => $this->exportManager->getFilter($alias); + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php b/src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php new file mode 100644 index 000000000..147b5844f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php @@ -0,0 +1,189 @@ +|null $entity the entity or collection of entities to normalize + * + * @return array|int|string Returns the identifier(s) of the entity or entities. If an array of entities is provided, + * an array of their identifiers is returned. If a single entity is provided, its identifier + * is returned. If null, returns an empty value. + */ + private function normalizeDoctrineEntity(object|array|null $entity): array|int|string + { + if (is_array($entity)) { + return array_values(array_filter(array_map(static fn (object $entity) => $entity->getId(), $entity), fn ($value) => null !== $value)); + } + if ($entity instanceof Collection) { + return $this->normalizeDoctrineEntity($entity->toArray()); + } + + return $entity?->getId(); + } + + /** + * Denormalizes a Doctrine entity by fetching it from the provided repository based on the given ID(s). + * + * @param list|int|string $id the identifier(s) of the entity to find + * @param ObjectRepository $repository the Doctrine repository to query + * + * @return object|array the found entity or an array of entities if multiple IDs are provided + * + * @throws \UnexpectedValueException when the entity with the given ID does not exist + */ + private function denormalizeDoctrineEntity(array|int|string $id, ObjectRepository $repository): object|array + { + if (is_array($id)) { + if ([] === $id) { + return []; + } + + return $repository->findBy(['id' => $id]); + } + + if (null === $object = $repository->find($id)) { + throw new \UnexpectedValueException(sprintf('Object with id "%s" does not exist.', $id)); + } + + return $object; + } + + /** + * Normalizer the "user or me" values. + * + * @param 'me'|User|iterable<'me'|User> $user + * + * @return int|'me'|list<'me'|int> + */ + private function normalizeUserOrMe(string|User|iterable $user): int|string|array + { + if (is_iterable($user)) { + $users = []; + foreach ($user as $u) { + $users[] = $this->normalizeUserOrMe($u); + } + + return $users; + } + + if ('me' === $user) { + return $user; + } + + return $user->getId(); + } + + /** + * @param 'me'|int|iterable<'me'|int> $userId + * + * @return 'me'|User|array|null + */ + private function denormalizeUserOrMe(string|int|iterable $userId, UserRepositoryInterface $userRepository): string|User|array|null + { + if (is_iterable($userId)) { + $users = []; + foreach ($userId as $id) { + $users[] = $this->denormalizeUserOrMe($id, $userRepository); + } + + return $users; + } + + if ('me' === $userId) { + return 'me'; + } + + return $userRepository->find($userId); + } + + /** + * @param 'me'|User|iterable<'me'|User> $user + * + * @return User|list + */ + private function userOrMe(string|User|iterable $user, ExportGenerationContext $context): User|array + { + if (is_iterable($user)) { + $users = []; + foreach ($user as $u) { + $users[] = $this->userOrMe($u, $context); + } + + return array_values( + array_filter($users, static fn (?User $user) => null !== $user) + ); + } + + if ('me' === $user) { + return $context->byUser; + } + + return $user; + } + + /** + * Normalizes a provided date into a specific string format. + * + * @param \DateTimeImmutable|\DateTime $date the date instance to normalize + * + * @return string a formatted string containing the type and formatted date + */ + private function normalizeDate(\DateTimeImmutable|\DateTime $date): string + { + return sprintf( + '%s,%s', + $date instanceof \DateTimeImmutable ? 'imm1' : 'mut1', + $date->format('d-m-Y-H:i:s.u e'), + ); + } + + /** + * Denormalizes a string back into a DateTime instance. + * + * The string is expected to contain a kind selector (e.g., 'imm1' or 'mut1') + * to determine the type of DateTime object (immutable or mutable) followed by a date format. + * + * @param string $date the string to be denormalized, containing the kind selector and formatted date + * + * @return \DateTimeImmutable|\DateTime a DateTime instance created from the given string + * + * @throws \UnexpectedValueException if the kind selector or date format is invalid + */ + private function denormalizeDate(string $date): \DateTimeImmutable|\DateTime + { + $format = 'd-m-Y-H:i:s.u e'; + + $denormalized = match (substr($date, 0, 4)) { + 'imm1' => \DateTimeImmutable::createFromFormat($format, substr($date, 5)), + 'mut1' => \DateTime::createFromFormat($format, substr($date, 5)), + default => throw new \UnexpectedValueException(sprintf('Unexpected format for the kind selector: %s', substr($date, 0, 4))), + }; + + if (false === $denormalized) { + throw new \UnexpectedValueException(sprintf('Unexpected date format: %s', substr($date, 5))); + } + + return $denormalized; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php b/src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php new file mode 100644 index 000000000..3016f2f79 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php @@ -0,0 +1,74 @@ + + */ + public function describe(string $exportAlias, array $exportOptions, bool $includeExportTitle = true): array + { + $output = []; + $denormalized = $this->exportConfigNormalizer->denormalizeConfig($exportAlias, $exportOptions); + $user = $this->security->getUser(); + + if ($includeExportTitle) { + $output[] = $this->trans($this->exportManager->getExport($exportAlias)->getTitle()); + } + + if (!$user instanceof User) { + return $output; + } + $context = new ExportGenerationContext($user); + + foreach ($this->exportConfigProcessor->retrieveUsedFilters($denormalized['filters']) as $name => $filter) { + $output[] = $this->trans($filter->describeAction($denormalized['filters'][$name]['form'], $context)); + } + + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($denormalized['aggregators']) as $name => $aggregator) { + $output[] = $this->trans($aggregator->getTitle()); + } + + return $output; + } + + private function trans(string|TranslatableInterface|array $translatable): string + { + if (is_string($translatable)) { + return $this->translator->trans($translatable); + } + + if ($translatable instanceof TranslatableInterface) { + return $translatable->trans($this->translator); + } + + // array case + return $this->translator->trans($translatable[0], $translatable[1] ?? []); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php index 49b2500f0..e8018bb1a 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Symfony\Contracts\Translation\TranslatableInterface; + /** * The common methods between different object used to build export (i.e. : ExportInterface, * FilterInterface, AggregatorInterface). @@ -19,8 +21,6 @@ interface ExportElementInterface { /** * get a title, which will be used in UI (and translated). - * - * @return string */ - public function getTitle(); + public function getTitle(): string|TranslatableInterface; } diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php index d4f58a570..d5c5b435c 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php @@ -31,5 +31,5 @@ interface ExportElementValidatedInterface * validate the form's data and, if required, build a contraint * violation on the data. */ - public function validateForm(mixed $data, ExecutionContextInterface $context); + public function validateForm(mixed $data, ExecutionContextInterface $context): void; } diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php index 43e0e506a..f92e9bd50 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php @@ -21,7 +21,7 @@ namespace Chill\MainBundle\Export; interface ExportElementsProviderInterface { /** - * @return ExportElementInterface[] + * @return iterable */ - public function getExportElements(); + public function getExportElements(): iterable; } diff --git a/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php b/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php index ce43c230a..7bc7e9fc5 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php +++ b/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php @@ -11,27 +11,28 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\FilterType; -use Chill\MainBundle\Form\Type\Export\FormatterType; -use Chill\MainBundle\Form\Type\Export\PickCenterType; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; -use Symfony\Component\Form\Extension\Core\Type\FormType; -use Symfony\Component\Form\FormFactoryInterface; +use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver; +use Doctrine\Common\Collections\Collection; final readonly class ExportFormHelper { public function __construct( private AuthorizationHelperForCurrentUserInterface $authorizationHelper, private ExportManager $exportManager, - private FormFactoryInterface $formFactory, + private ExportConfigNormalizer $configNormalizer, + private CenterRegroupementResolver $centerRegroupementResolver, ) {} public function getDefaultData(string $step, DirectExportInterface|ExportInterface $export, array $options = []): array { return match ($step) { - 'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole())], + 'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole()), 'regroupments' => []], 'export', 'generate_export' => ['export' => $this->getDefaultDataStepExport($export, $options)], 'formatter', 'generate_formatter' => ['formatter' => $this->getDefaultDataStepFormatter($options)], default => throw new \LogicException('step not allowed : '.$step), @@ -91,80 +92,68 @@ final readonly class ExportFormHelper } public function savedExportDataToFormData( - SavedExport $savedExport, + ExportGeneration|SavedExport $savedExport, string $step, - array $formOptions = [], ): array { return match ($step) { 'centers', 'generate_centers' => $this->savedExportDataToFormDataStepCenter($savedExport), - 'export', 'generate_export' => $this->savedExportDataToFormDataStepExport($savedExport, $formOptions), - 'formatter', 'generate_formatter' => $this->savedExportDataToFormDataStepFormatter($savedExport, $formOptions), + 'export', 'generate_export' => $this->savedExportDataToFormDataStepExport($savedExport), + 'formatter', 'generate_formatter' => $this->savedExportDataToFormDataStepFormatter($savedExport), default => throw new \LogicException('this step is not allowed: '.$step), }; } private function savedExportDataToFormDataStepCenter( - SavedExport $savedExport, + ExportGeneration|SavedExport $savedExport, ): array { - $builder = $this->formFactory - ->createBuilder( - FormType::class, - [], - [ - 'csrf_protection' => false, - ] - ); - $builder->add('centers', PickCenterType::class, [ - 'export_alias' => $savedExport->getExportAlias(), - ]); - $form = $builder->getForm(); - $form->submit($savedExport->getOptions()['centers']); - - return $form->getData(); + return [ + 'centers' => $this->configNormalizer->denormalizeConfig($savedExport->getExportAlias(), $savedExport->getOptions(), true)['centers'], + ]; } private function savedExportDataToFormDataStepExport( - SavedExport $savedExport, - array $formOptions, + ExportGeneration|SavedExport $savedExport, ): array { - $builder = $this->formFactory - ->createBuilder( - FormType::class, - [], - [ - 'csrf_protection' => false, - ] - ); + $data = $this->configNormalizer->denormalizeConfig($savedExport->getExportAlias(), $savedExport->getOptions(), true); - $builder->add('export', ExportType::class, [ - 'export_alias' => $savedExport->getExportAlias(), ...$formOptions, - ]); - $form = $builder->getForm(); - $form->submit($savedExport->getOptions()['export']); - - return $form->getData(); + return [ + 'export' => [ + 'export' => $data['export'], + 'filters' => $data['filters'], + 'pick_formatter' => ['alias' => $data['pick_formatter']], + 'aggregators' => $data['aggregators'], + ], + ]; } private function savedExportDataToFormDataStepFormatter( - SavedExport $savedExport, - array $formOptions, + ExportGeneration|SavedExport $savedExport, ): array { - $builder = $this->formFactory - ->createBuilder( - FormType::class, - [], - [ - 'csrf_protection' => false, - ] - ); + $data = $this->configNormalizer->denormalizeConfig($savedExport->getExportAlias(), $savedExport->getOptions(), true); - $builder->add('formatter', FormatterType::class, [ - 'export_alias' => $savedExport->getExportAlias(), ...$formOptions, - ]); - $form = $builder->getForm(); - $form->submit($savedExport->getOptions()['formatter']); + return [ + 'formatter' => $data['formatter'], + ]; + } - return $form->getData(); + /** + * Get the Center picked by the user for this export. The data are + * extracted from the PickCenterType data. + * + * @param array $data the data as given by the @see{Chill\MainBundle\Form\Type\Export\PickCenterType} + * + * @return list
+ */ + public function getPickedCenters(array $data): array + { + if (!array_key_exists('centers', $data)) { + throw new \RuntimeException('array has not the expected shape'); + } + + $centers = $data['centers'] instanceof Collection ? $data['centers']->toArray() : $data['centers']; + $regroupments = ($data['regroupments'] ?? []) instanceof Collection ? $data['regroupments']->toArray() : ($data['regroupments'] ?? []); + + return $this->centerRegroupementResolver->resolveCenters($regroupments, $centers); } } diff --git a/src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php b/src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php new file mode 100644 index 000000000..0ffb29c20 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php @@ -0,0 +1,21 @@ +filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration + { + $data = $this->configNormalizer->denormalizeConfig($exportAlias, $configuration); + $export = $this->exportManager->getExport($exportAlias); + + $centers = $this->filterCenters($byUser, $data['centers']['centers'], $data['centers']['regroupments'], $export); + + $context = new ExportGenerationContext($byUser); + + if ($export instanceof DirectExportInterface) { + $generatedExport = $export->generate( + $this->buildCenterReachableScopes($centers), + $data['export'], + $context, + ); + + if ($generatedExport instanceof Response) { + trigger_deprecation('chill-project/chill-bundles', '3.10', 'DirectExportInterface should not return a %s instance, but a %s instance', Response::class, FormattedExportGeneration::class); + + return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('Content-Type')); + } + + return $generatedExport; + } + + $query = $export->initiateQuery( + $this->retrieveUsedModifiers($data), + $this->buildCenterReachableScopes($centers), + $data['export'], + $context, + ); + + if ($query instanceof \Doctrine\ORM\NativeQuery) { + // throw an error if the export require other modifier, which is + // not allowed when the export return a `NativeQuery` + if (\count($export->supportsModifiers()) > 0) { + throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`'); + } + } elseif ($query instanceof QueryBuilder) { + // handle filters + $this->handleFilters($query, $data[ExportType::FILTER_KEY], $context); + + // handle aggregators + $this->handleAggregators($query, $data[ExportType::AGGREGATOR_KEY], $context); + + $this->logger->notice('[export] will execute this qb in export', [ + 'dql' => $query->getDQL(), + ]); + $this->logger->debug('[export] will execute this sql qb in export', [ + 'sql' => $query->getQuery()->getSQL(), + ]); + } else { + throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.'); + } + + $result = $export->getResult($query, $data['export'], $context); + + $formatter = $this->exportManager->getFormatter($data['pick_formatter']); + $filtersData = []; + $aggregatorsData = []; + + if ($query instanceof QueryBuilder) { + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]) as $alias => $aggregator) { + $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; + } + foreach ($this->exportConfigProcessor->retrieveUsedFilters($data[ExportType::FILTER_KEY]) as $alias => $filter) { + $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; + } + } + + /* @phpstan-ignore-next-line the method "generate" is not yet implemented on all formatters */ + if (method_exists($formatter, 'generate')) { + return $formatter->generate( + $result, + $data['formatter'], + $exportAlias, + $data['export'], + $filtersData, + $aggregatorsData, + $context, + ); + } + + trigger_deprecation('chill-project/chill-bundles', '3.10', '%s should implements the "generate" method', FormatterInterface::class); + + /* @phpstan-ignore-next-line this is a deprecated method that we must still call */ + $generatedExport = $formatter->getResponse( + $result, + $data['formatter'], + $exportAlias, + $data['export'], + $filtersData, + $aggregatorsData, + $context, + ); + + return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type')); + } + + private function filterCenters(User $byUser, array $centers, array $regroupements, ExportInterface|DirectExportInterface $export): array + { + if (!$this->filterStatsByCenters) { + return $this->centerRepository->findActive(); + } + + $authorizedCenters = new ArrayCollection($this->authorizationHelper->getReachableCenters($byUser, $export->requiredRole())); + if ($authorizedCenters->isEmpty()) { + throw new UnauthorizedGenerationException('No authorized centers'); + } + + $wantedCenters = $this->centerRegroupementResolver->resolveCenters($regroupements, $centers); + + $resolvedCenters = []; + foreach ($wantedCenters as $wantedCenter) { + if ($authorizedCenters->contains($wantedCenter)) { + $resolvedCenters[] = $wantedCenter; + } + } + + if ([] == $resolvedCenters) { + throw new UnauthorizedGenerationException('No common centers between wanted centers and authorized centers'); + } + + return $resolvedCenters; + } + + /** + * parse the data to retrieve the used filters and aggregators. + * + * @return list + */ + private function retrieveUsedModifiers(mixed $data): array + { + if (null === $data) { + return []; + } + + $usedTypes = array_merge( + $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), + $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]), + ); + + return array_values(array_unique($usedTypes)); + } + + /** + * Retrieve the filter used in this export. + * + * @return list an array with types + */ + private function retrieveUsedFiltersType(mixed $data): array + { + if (null === $data) { + return []; + } + + $usedTypes = []; + + foreach ($this->exportConfigProcessor->retrieveUsedFilters($data) as $filter) { + if (!\in_array($filter->applyOn(), $usedTypes, true)) { + $usedTypes[] = $filter->applyOn(); + } + } + + return $usedTypes; + } + + /** + * @return string[] + */ + private function retrieveUsedAggregatorsType(mixed $data): array + { + if (null === $data) { + return []; + } + + $usedTypes = []; + + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data) as $alias => $aggregator) { + if (!\in_array($aggregator->applyOn(), $usedTypes, true)) { + $usedTypes[] = $aggregator->applyOn(); + } + } + + return $usedTypes; + } + + /** + * Alter the query with selected aggregators. + */ + private function handleAggregators( + QueryBuilder $qb, + array $data, + ExportGenerationContext $context, + ): void { + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data) as $alias => $aggregator) { + $formData = $data[$alias]; + $aggregator->alterQuery($qb, $formData['form'], $context); + } + } + + /** + * alter the query with selected filters. + */ + private function handleFilters( + QueryBuilder $qb, + mixed $data, + ExportGenerationContext $context, + ): void { + foreach ($this->exportConfigProcessor->retrieveUsedFilters($data) as $alias => $filter) { + $formData = $data[$alias]; + + $filter->alterQuery($qb, $formData['form'], $context); + } + } + + /** + * build the array required for defining centers and circles in the initiate + * queries of ExportElementsInterfaces. + * + * @param list
$centers + */ + private function buildCenterReachableScopes(array $centers) + { + return array_map(static fn (Center $center) => ['center' => $center, 'circles' => []], $centers); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportInterface.php b/src/Bundle/ChillMainBundle/Export/ExportInterface.php index a9d3efd13..edb5a53d1 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportInterface.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Export; use Doctrine\ORM\NativeQuery; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatableInterface; /** * Interface for Export. @@ -27,6 +28,7 @@ use Symfony\Component\Form\FormBuilderInterface; * @example Chill\PersonBundle\Export\CountPerson an example of implementation * * @template Q of QueryBuilder|NativeQuery + * @template D of array */ interface ExportInterface extends ExportElementInterface { @@ -94,12 +96,12 @@ interface ExportInterface extends ExportElementInterface * which do not need to be translated, or value already translated in * database. But the header must be, in every case, translated. * - * @param string $key The column key, as added in the query - * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') + * @param string $key The column key, as added in the query + * @param list $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * - * @return (callable(string|int|float|'_header'|null $value): string|int|\DateTimeInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * @return (callable(string|int|float|'_header'|null $value): string|int|\DateTimeInterface|TranslatableInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ - public function getLabels($key, array $values, mixed $data); + public function getLabels(string $key, array $values, mixed $data); /** * give the list of keys the current export added to the queryBuilder in @@ -108,29 +110,27 @@ interface ExportInterface extends ExportElementInterface * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, * this function will return `array('count_id')`. * - * @param mixed[] $data the data from the export's form (added by self::buildForm) + * @param D $data the data from the export's form (added by self::buildForm) */ - public function getQueryKeys($data); + public function getQueryKeys(array $data): array; /** * Return the results of the query builder. * - * @param Q $query - * @param mixed[] $data the data from the export's fomr (added by self::buildForm) + * @param Q $query + * @param D $data the data from the export's fomr (added by self::buildForm) * * @return mixed[] an array of results */ - public function getResult($query, $data); + public function getResult(QueryBuilder|NativeQuery $query, array $data, ExportGenerationContext $context): array; /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType(); + public function getType(): string; /** * The initial query, which will be modified by ModifiersInterface @@ -147,7 +147,21 @@ interface ExportInterface extends ExportElementInterface * * @return Q the query to execute */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []); + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder|NativeQuery; + + /** + * @param D $formData + */ + public function normalizeFormData(array $formData): array; + + /** + * @param array $formData the normalized data + * + * @return D + */ + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; /** * Return the required Role to execute the Export. diff --git a/src/Bundle/ChillMainBundle/Export/ExportManager.php b/src/Bundle/ChillMainBundle/Export/ExportManager.php index 949523eb0..f5e9bfc4d 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportManager.php +++ b/src/Bundle/ChillMainBundle/Export/ExportManager.php @@ -11,13 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; -use Doctrine\ORM\QueryBuilder; use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -63,14 +59,13 @@ class ExportManager iterable $exports, iterable $aggregators, iterable $filters, - // iterable $formatters, + iterable $formatters, // iterable $exportElementProvider ) { $this->exports = iterator_to_array($exports); $this->aggregators = iterator_to_array($aggregators); $this->filters = iterator_to_array($filters); - // NOTE: PHP crashes on the next line (exit error code 11). This is desactivated until further investigation - // $this->formatters = iterator_to_array($formatters); + $this->formatters = iterator_to_array($formatters); // foreach ($exportElementProvider as $prefix => $provider) { // $this->addExportElementsProvider($provider, $prefix); @@ -102,7 +97,7 @@ class ExportManager \in_array($filter->applyOn(), $export->supportsModifiers(), true) && $this->isGrantedForElement($filter, $export, $centers) ) { - $filters[$alias] = $filter; + $filters[$alias] = $this->getFilter($alias); } } @@ -136,25 +131,6 @@ class ExportManager return $aggregators; } - public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void - { - foreach ($provider->getExportElements() as $suffix => $element) { - $alias = $prefix.'_'.$suffix; - - if ($element instanceof ExportInterface) { - $this->exports[$alias] = $element; - } elseif ($element instanceof FilterInterface) { - $this->filters[$alias] = $element; - } elseif ($element instanceof AggregatorInterface) { - $this->aggregators[$alias] = $element; - } elseif ($element instanceof FormatterInterface) { - $this->addFormatter($element, $alias); - } else { - throw new \LogicException('This element '.$element::class.' is not an instance of export element'); - } - } - } - /** * add a formatter. * @@ -165,96 +141,22 @@ class ExportManager $this->formatters[$alias] = $formatter; } - /** - * Generate a response which contains the requested data. - */ - public function generate(string $exportAlias, array $pickedCentersData, array $data, array $formatterData): Response - { - $export = $this->getExport($exportAlias); - $centers = $this->getPickedCenters($pickedCentersData); - - if ($export instanceof DirectExportInterface) { - return $export->generate( - $this->buildCenterReachableScopes($centers, $export), - $data[ExportType::EXPORT_KEY] - ); - } - - $query = $export->initiateQuery( - $this->retrieveUsedModifiers($data), - $this->buildCenterReachableScopes($centers, $export), - $data[ExportType::EXPORT_KEY] - ); - - if ($query instanceof \Doctrine\ORM\NativeQuery) { - // throw an error if the export require other modifier, which is - // not allowed when the export return a `NativeQuery` - if (\count($export->supportsModifiers()) > 0) { - throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`'); - } - } elseif ($query instanceof QueryBuilder) { - // handle filters - $this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers); - - // handle aggregators - $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); - - $this->logger->notice('[export] will execute this qb in export', [ - 'dql' => $query->getDQL(), - ]); - } else { - throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.'); - } - - $result = $export->getResult($query, $data[ExportType::EXPORT_KEY]); - - if (!is_iterable($result)) { - throw new \UnexpectedValueException(sprintf('The result of the export should be an iterable, %s given', \gettype($result))); - } - - /** @var FormatterInterface $formatter */ - $formatter = $this->getFormatter($this->getFormatterAlias($data)); - $filtersData = []; - $aggregatorsData = []; - - if ($query instanceof QueryBuilder) { - $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); - - foreach ($aggregators as $alias => $aggregator) { - $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; - } - } - - $filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]); - - foreach ($filters as $alias => $filter) { - $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; - } - - return $formatter->getResponse( - $result, - $formatterData, - $exportAlias, - $data[ExportType::EXPORT_KEY], - $filtersData, - $aggregatorsData - ); - } - /** * @param string $alias * - * @return AggregatorInterface - * * @throws \RuntimeException if the aggregator is not known */ - public function getAggregator($alias) + public function getAggregator($alias): AggregatorInterface { - if (!\array_key_exists($alias, $this->aggregators)) { + if (null === $aggregator = $this->aggregators[$alias] ?? null) { throw new \RuntimeException("The aggregator with alias {$alias} is not known."); } - return $this->aggregators[$alias]; + if ($aggregator instanceof ExportManagerAwareInterface) { + $aggregator->setExportManager($this); + } + + return $aggregator; } /** @@ -313,10 +215,10 @@ class ExportManager foreach ($this->exports as $alias => $export) { if ($whereUserIsGranted) { if ($this->isGrantedForElement($export, null, null)) { - yield $alias => $export; + yield $alias => $this->getExport($alias); } } else { - yield $alias => $export; + yield $alias => $this->getExport($alias); } } } @@ -332,9 +234,9 @@ class ExportManager foreach ($this->getExports($whereUserIsGranted) as $alias => $export) { if ($export instanceof GroupedExportInterface) { - $groups[$export->getGroup()][$alias] = $export; + $groups[$export->getGroup()][$alias] = $this->getExport($alias); } else { - $groups['_'][$alias] = $export; + $groups['_'][$alias] = $this->getExport($alias); } } @@ -346,11 +248,25 @@ class ExportManager */ public function getFilter(string $alias): FilterInterface { - if (!\array_key_exists($alias, $this->filters)) { + if (null === $filter = $this->filters[$alias] ?? null) { throw new \RuntimeException("The filter with alias {$alias} is not known."); } - return $this->filters[$alias]; + if ($filter instanceof ExportManagerAwareInterface) { + $filter->setExportManager($this); + } + + return $filter; + } + + public function hasFilter(string $alias): bool + { + return array_key_exists($alias, $this->filters); + } + + public function hasAggregator(string $alias): bool + { + return array_key_exists($alias, $this->aggregators); } public function getAllFilters(): array @@ -358,7 +274,7 @@ class ExportManager $filters = []; foreach ($this->filters as $alias => $filter) { - $filters[$alias] = $filter; + $filters[$alias] = $this->getFilter($alias); } return $filters; @@ -380,11 +296,15 @@ class ExportManager public function getFormatter(string $alias): FormatterInterface { - if (!\array_key_exists($alias, $this->formatters)) { + if (null === $formatter = $this->formatters[$alias] ?? null) { throw new \RuntimeException("The formatter with alias {$alias} is not known."); } - return $this->formatters[$alias]; + if ($formatter instanceof ExportManagerAwareInterface) { + $formatter->setExportManager($this); + } + + return $formatter; } /** @@ -412,26 +332,13 @@ class ExportManager { foreach ($this->formatters as $alias => $formatter) { if (\in_array($formatter->getType(), $types, true)) { - yield $alias => $formatter; + yield $alias => $this->getFormatter($alias); } } } /** - * Get the Center picked by the user for this export. The data are - * extracted from the PickCenterType data. - * - * @param array $data the data from a PickCenterType - * - * @return \Chill\MainBundle\Entity\Center[] the picked center - */ - public function getPickedCenters(array $data): array - { - return $data; - } - - /** - * get the aggregators typse used in the form export data. + * get the aggregators types used in the form export data. * * @param array $data the data from the export form * @@ -439,9 +346,15 @@ class ExportManager */ public function getUsedAggregatorsAliases(array $data): array { - $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); + $keys = []; - return array_keys(iterator_to_array($aggregators)); + foreach ($data as $alias => $aggregatorData) { + if (true === $aggregatorData['enabled']) { + $keys[] = $alias; + } + } + + return array_values(array_unique($keys)); } /** @@ -490,190 +403,4 @@ class ExportManager return true; } - - /** - * build the array required for defining centers and circles in the initiate - * queries of ExportElementsInterfaces. - * - * @param \Chill\MainBundle\Entity\Center[] $centers - */ - private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) - { - $r = []; - - $user = $this->tokenStorage->getToken()->getUser(); - - if (!$user instanceof User) { - return []; - } - - foreach ($centers as $center) { - $r[] = [ - 'center' => $center, - 'circles' => $this->authorizationHelper->getReachableScopes( - $user, - $element->requiredRole(), - $center - ), - ]; - } - - return $r; - } - - /** - * Alter the query with selected aggregators. - * - * Check for acl. If an user is not authorized to see an aggregator, throw an - * UnauthorizedException. - * - * @throw UnauthorizedHttpException if the user is not authorized - */ - private function handleAggregators( - ExportInterface $export, - QueryBuilder $qb, - array $data, - array $center, - ) { - $aggregators = $this->retrieveUsedAggregators($data); - - foreach ($aggregators as $alias => $aggregator) { - if (false === $this->isGrantedForElement($aggregator, $export, $center)) { - throw new UnauthorizedHttpException('You are not authorized to use the aggregator'.$aggregator->getTitle()); - } - - $formData = $data[$alias]; - $aggregator->alterQuery($qb, $formData['form']); - } - } - - /** - * alter the query with selected filters. - * - * This function check the acl. - * - * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers - * - * @throw UnauthorizedHttpException if the user is not authorized - */ - private function handleFilters( - ExportInterface $export, - QueryBuilder $qb, - mixed $data, - array $centers, - ) { - $filters = $this->retrieveUsedFilters($data); - - foreach ($filters as $alias => $filter) { - if (false === $this->isGrantedForElement($filter, $export, $centers)) { - throw new UnauthorizedHttpException('You are not authorized to use the filter '.$filter->getTitle()); - } - - $formData = $data[$alias]; - - $this->logger->debug('alter query by filter '.$alias, [ - 'class' => self::class, 'function' => __FUNCTION__, - ]); - $filter->alterQuery($qb, $formData['form']); - } - } - - /** - * @return iterable - */ - private function retrieveUsedAggregators(mixed $data): iterable - { - if (null === $data) { - return []; - } - - foreach ($data as $alias => $aggregatorData) { - if (true === $aggregatorData['enabled']) { - yield $alias => $this->getAggregator($alias); - } - } - } - - /** - * @return string[] - */ - private function retrieveUsedAggregatorsType(mixed $data) - { - if (null === $data) { - return []; - } - - $usedTypes = []; - - foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) { - if (!\in_array($aggregator->applyOn(), $usedTypes, true)) { - $usedTypes[] = $aggregator->applyOn(); - } - } - - return $usedTypes; - } - - private function retrieveUsedFilters(mixed $data): iterable - { - if (null === $data) { - return []; - } - - foreach ($data as $alias => $filterData) { - if (true === $filterData['enabled']) { - yield $alias => $this->getFilter($alias); - } - } - } - - /** - * Retrieve the filter used in this export. - * - * @return array an array with types - */ - private function retrieveUsedFiltersType(mixed $data): iterable - { - if (null === $data) { - return []; - } - - $usedTypes = []; - - foreach ($data as $alias => $filterData) { - if (true === $filterData['enabled']) { - $filter = $this->getFilter($alias); - - if (!\in_array($filter->applyOn(), $usedTypes, true)) { - $usedTypes[] = $filter->applyOn(); - } - } - } - - return $usedTypes; - } - - /** - * parse the data to retrieve the used filters and aggregators. - * - * @return string[] - */ - private function retrieveUsedModifiers(mixed $data) - { - if (null === $data) { - return []; - } - - $usedTypes = array_merge( - $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), - $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]) - ); - - $this->logger->debug( - 'Required types are '.implode(', ', $usedTypes), - ['class' => self::class, 'function' => __FUNCTION__] - ); - - return array_unique($usedTypes); - } } diff --git a/src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php b/src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php new file mode 100644 index 000000000..f4e43d206 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php @@ -0,0 +1,20 @@ + 'good news'], 'mydomain', 'mylocale')` * - * @param array $data - * @param string $format the format + * @param D $data * - * @return array|string a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments + * @return array|string|TranslatableInterface a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments */ - public function describeAction($data, $format = 'string'); + public function describeAction(array $data, ExportGenerationContext $context): array|string|TranslatableInterface; } diff --git a/src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php b/src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php new file mode 100644 index 000000000..678bb8f31 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php @@ -0,0 +1,20 @@ +exportManager = $manager; - } - - /** - * @uses appendAggregatorForm - */ - public function buildForm(FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases) - { - $aggregators = $this->exportManager->getAggregators($aggregatorAliases); - $nb = \count($aggregatorAliases); - - foreach ($aggregators as $alias => $aggregator) { - $builderAggregator = $builder->create($alias, FormType::class, [ - 'label' => $aggregator->getTitle(), - 'block_name' => '_aggregator_placement_csv_formatter', - ]); - $this->appendAggregatorForm($builderAggregator, $nb); - $builder->add($builderAggregator); - } - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return []; - } - - public function gatherFiltersDescriptions() - { - $descriptions = []; - - foreach ($this->filtersData as $key => $filterData) { - $statement = $this->exportManager - ->getFilter($key) - ->describeAction($filterData); - - if (null === $statement) { - continue; - } - - if (\is_array($statement)) { - $descriptions[] = $this->translator->trans( - $statement[0], - $statement[1], - $statement[2] ?? null, - $statement[3] ?? null - ); - } else { - $descriptions[] = $statement; - } - } - - return $descriptions; - } - - public function getName() - { - return 'Comma separated values (CSV)'; - } - - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->orderingHeaders($formatterData); - $this->export = $this->exportManager->getExport($exportAlias); - $this->aggregators = iterator_to_array($this->exportManager - ->getAggregators(array_keys($aggregatorsData))); - $this->exportData = $exportData; - $this->aggregatorsData = $aggregatorsData; - $this->labels = $this->gatherLabels(); - $this->filtersData = $filtersData; - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - // $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); - - $response->setContent($this->generateContent()); - - return $response; - } - - public function getType() - { - return 'tabular'; - } - - protected function gatherLabels() - { - return array_merge( - $this->gatherLabelsFromAggregators(), - $this->gatherLabelsFromExport() - ); - } - - protected function gatherLabelsFromAggregators() - { - $labels = []; - /* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */ - foreach ($this->aggregators as $alias => $aggregator) { - $keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]); - - // gather data in an array - foreach ($keys as $key) { - $values = array_map(static function ($row) use ($key, $alias) { - if (!\array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by the aggregator with alias '".$alias."' but is not ".'present in results'); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $aggregator->getLabels( - $key, - array_unique($values), - $this->aggregatorsData[$alias] - ); - } - } - - return $labels; - } - - protected function gatherLabelsFromExport() - { - $labels = []; - $export = $this->export; - $keys = $this->export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - $values = array_map(static function ($row) use ($key, $export) { - if (!\array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by the export with title '".$export->getTitle()."' but is not ".'present in results'); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $this->export->getLabels( - $key, - array_unique($values), - $this->exportData - ); - } - - return $labels; - } - - protected function generateContent() - { - $line = null; - $rowKeysNb = \count($this->getRowHeaders()); - $columnKeysNb = \count($this->getColumnHeaders()); - $resultsKeysNb = \count($this->export->getQueryKeys($this->exportData)); - $results = $this->getOrderedResults(); - /** @var string[] $columnHeaders the column headers associations */ - $columnHeaders = []; - /** @var string[] $data the data of the csv file */ - $contentData = []; - $content = []; - - // create a file pointer connected to the output stream - $output = fopen('php://output', 'wb'); - - // title - fputcsv($output, [$this->translator->trans($this->export->getTitle())]); - // blank line - fputcsv($output, ['']); - - // add filtering description - foreach ($this->gatherFiltersDescriptions() as $desc) { - fputcsv($output, [$desc]); - } - // blank line - fputcsv($output, ['']); - - // iterate on result to : 1. populate row headers, 2. populate column headers, 3. add result - foreach ($results as $row) { - $rowHeaders = \array_slice($row, 0, $rowKeysNb); - - // first line : we create line and adding them row headers - if (!isset($line)) { - $line = \array_slice($row, 0, $rowKeysNb); - } - - // do we have to create a new line ? if the rows are equals, continue on the line, else create a next line - if (\array_slice($line, 0, $rowKeysNb) !== $rowHeaders) { - $contentData[] = $line; - $line = \array_slice($row, 0, $rowKeysNb); - } - - // add the column headers - /** @var string[] $columns the column for this row */ - $columns = \array_slice($row, $rowKeysNb, $columnKeysNb); - $columnPosition = $this->findColumnPosition($columnHeaders, $columns); - - // fill with blank at the position given by the columnPosition + nbRowHeaders - for ($i = 0; $i < $columnPosition; ++$i) { - if (!isset($line[$rowKeysNb + $i])) { - $line[$rowKeysNb + $i] = ''; - } - } - - $resultData = \array_slice($row, $resultsKeysNb * -1); - - foreach ($resultData as $data) { - $line[] = $data; - } - } - - // we add the last line - $contentData[] = $line; - - // column title headers - for ($i = 0; $i < $columnKeysNb; ++$i) { - $line = array_fill(0, $rowKeysNb, ''); - - foreach ($columnHeaders as $set) { - $line[] = $set[$i]; - } - - $content[] = $line; - } - - // row title headers - $headerLine = []; - - foreach ($this->getRowHeaders() as $headerKey) { - $headerLine[] = \array_key_exists('_header', $this->labels[$headerKey]) ? - $this->labels[$headerKey]['_header'] : ''; - } - - foreach ($this->export->getQueryKeys($this->exportData) as $key) { - $headerLine[] = \array_key_exists('_header', $this->labels[$key]) ? - $this->labels[$key]['_header'] : ''; - } - fputcsv($output, $headerLine); - unset($headerLine); // free memory - - // generate CSV - foreach ($content as $line) { - fputcsv($output, $line); - } - - foreach ($contentData as $line) { - fputcsv($output, $line); - } - - $text = stream_get_contents($output); - fclose($output); - - return $text; - } - - protected function getColumnHeaders() - { - return $this->getPositionnalHeaders('c'); - } - - protected function getRowHeaders() - { - return $this->getPositionnalHeaders('r'); - } - - /** - * ordering aggregators, preserving key association. - * - * This function do not mind about position. - * - * If two aggregators have the same order, the second given will be placed - * after. This is not significant for the first ordering. - */ - protected function orderingHeaders(array $formatterData) - { - $this->formatterData = $formatterData; - uasort( - $this->formatterData, - static fn (array $a, array $b): int => ($a['order'] <= $b['order'] ? -1 : 1) - ); - } - - /** - * append a form line by aggregator on the formatter form. - * - * This form allow to choose the aggregator position (row or column) and - * the ordering - * - * @param string $nbAggregators - */ - private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) - { - $builder->add('order', ChoiceType::class, [ - 'choices' => array_combine( - range(1, $nbAggregators), - range(1, $nbAggregators) - ), - 'multiple' => false, - 'expanded' => false, - ]); - - $builder->add('position', ChoiceType::class, [ - 'choices' => [ - 'row' => 'r', - 'column' => 'c', - ], - 'multiple' => false, - 'expanded' => false, - ]); - } - - private function findColumnPosition(&$columnHeaders, $columnToFind): int - { - $i = 0; - - foreach ($columnHeaders as $set) { - if ($set === $columnToFind) { - return $i; - } - ++$i; - } - - // we didn't find it, adding the column - $columnHeaders[] = $columnToFind; - - return $i++; - } - - private function getOrderedResults() - { - $r = []; - $results = $this->result; - $labels = $this->labels; - $rowKeys = $this->getRowHeaders(); - $columnKeys = $this->getColumnHeaders(); - $resultsKeys = $this->export->getQueryKeys($this->exportData); - $headers = array_merge($rowKeys, $columnKeys); - - foreach ($results as $row) { - $line = []; - - foreach ($headers as $key) { - $line[] = \call_user_func($labels[$key], $row[$key]); - } - - // append result - foreach ($resultsKeys as $key) { - $line[] = \call_user_func($labels[$key], $row[$key]); - } - - $r[] = $line; - } - - array_multisort($r); - - return $r; - } - - /** - * @param string $position may be 'c' (column) or 'r' (row) - * - * @return string[] - * - * @throws \RuntimeException - */ - private function getPositionnalHeaders($position) - { - $headers = []; - - foreach ($this->formatterData as $alias => $data) { - if (!\array_key_exists($alias, $this->aggregatorsData)) { - throw new \RuntimeException('the formatter wants to use the '."aggregator with alias {$alias}, but the export do not ".'contains data about it'); - } - - $aggregator = $this->aggregators[$alias]; - - if ($data['position'] === $position) { - $headers = array_merge($headers, $aggregator->getQueryKeys($this->aggregatorsData[$alias])); - } - } - - return $headers; - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php deleted file mode 100644 index 1a54eee5f..000000000 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php +++ /dev/null @@ -1,223 +0,0 @@ -translator = $translatorInterface; - $this->exportManager = $exportManager; - } - - /** - * build a form, which will be used to collect data required for the execution - * of this formatter. - * - * @uses appendAggregatorForm - * - * @param type $exportAlias - */ - public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases, - ) { - $builder->add('numerotation', ChoiceType::class, [ - 'choices' => [ - 'yes' => true, - 'no' => false, - ], - 'expanded' => true, - 'multiple' => false, - 'label' => 'Add a number on first column', - ]); - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return ['numerotation' => true]; - } - - public function getName() - { - return 'CSV vertical list'; - } - - /** - * Generate a response from the data collected on differents ExportElementInterface. - * - * @param mixed[] $result The result, as given by the ExportInterface - * @param mixed[] $formatterData collected from the current form - * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data - * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data - * - * @return Response The response to be shown - */ - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->exportAlias = $exportAlias; - $this->exportData = $exportData; - $this->formatterData = $formatterData; - - $output = fopen('php://output', 'wb'); - - $this->prepareHeaders($output); - - $i = 1; - - foreach ($result as $row) { - $line = []; - - if (true === $this->formatterData['numerotation']) { - $line[] = $i; - } - - foreach ($row as $key => $value) { - $line[] = $this->getLabel($key, $value); - } - - fputcsv($output, $line); - - ++$i; - } - - $csvContent = stream_get_contents($output); - fclose($output); - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - // $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); - - $response->setContent($csvContent); - - return $response; - } - - public function getType() - { - return FormatterInterface::TYPE_LIST; - } - - /** - * Give the label corresponding to the given key and value. - * - * @param string $key - * @param string $value - * - * @return string - * - * @throws \LogicException if the label is not found - */ - protected function getLabel($key, $value) - { - if (null === $this->labelsCache) { - $this->prepareCacheLabels(); - } - - if (!\array_key_exists($key, $this->labelsCache)) { - throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache)))); - } - - return $this->labelsCache[$key]($value); - } - - /** - * Prepare the label cache which will be used by getLabel. This function - * should be called only once in the generation lifecycle. - */ - protected function prepareCacheLabels() - { - $export = $this->exportManager->getExport($this->exportAlias); - $keys = $export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - // get an array with all values for this key if possible - $values = \array_map(static fn ($v) => $v[$key], $this->result); - // store the label in the labelsCache property - $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); - } - } - - /** - * add the headers to the csv file. - * - * @param resource $output - */ - protected function prepareHeaders($output) - { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = \count($this->result) > 0 ? $this->result[0] : []; - $header_line = []; - - if (true === $this->formatterData['numerotation']) { - $header_line[] = $this->translator->trans('Number'); - } - - foreach ($first_row as $key => $value) { - $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header') - ); - } - - if (\count($header_line) > 0) { - fputcsv($output, $header_line); - } - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php deleted file mode 100644 index 9385f1b61..000000000 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php +++ /dev/null @@ -1,217 +0,0 @@ -translator = $translatorInterface; - $this->exportManager = $exportManager; - } - - /** - * build a form, which will be used to collect data required for the execution - * of this formatter. - * - * @uses appendAggregatorForm - * - * @param type $exportAlias - */ - public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases, - ) { - $builder->add('numerotation', ChoiceType::class, [ - 'choices' => [ - 'yes' => true, - 'no' => false, - ], - 'expanded' => true, - 'multiple' => false, - 'label' => 'Add a number on first column', - 'data' => true, - ]); - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return ['numerotation' => true]; - } - - public function getName() - { - return 'CSV horizontal list'; - } - - /** - * Generate a response from the data collected on differents ExportElementInterface. - * - * @param mixed[] $result The result, as given by the ExportInterface - * @param mixed[] $formatterData collected from the current form - * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data - * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data - * - * @return Response The response to be shown - */ - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->exportAlias = $exportAlias; - $this->exportData = $exportData; - $this->formatterData = $formatterData; - - $output = fopen('php://output', 'wb'); - - $i = 1; - $lines = []; - $this->prepareHeaders($lines); - - foreach ($result as $row) { - $j = 0; - - if (true === $this->formatterData['numerotation']) { - $lines[$j][] = $i; - ++$j; - } - - foreach ($row as $key => $value) { - $lines[$j][] = $this->getLabel($key, $value); - ++$j; - } - ++$i; - } - - // adding the lines to the csv output - foreach ($lines as $line) { - fputcsv($output, $line); - } - - $csvContent = stream_get_contents($output); - fclose($output); - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - $response->headers->set('Content-Disposition', 'attachment; filename="export.csv"'); - - $response->setContent($csvContent); - - return $response; - } - - public function getType() - { - return FormatterInterface::TYPE_LIST; - } - - /** - * Give the label corresponding to the given key and value. - * - * @param string $key - * @param string $value - * - * @return string - * - * @throws \LogicException if the label is not found - */ - protected function getLabel($key, $value) - { - if (null === $this->labelsCache) { - $this->prepareCacheLabels(); - } - - return $this->labelsCache[$key]($value); - } - - /** - * Prepare the label cache which will be used by getLabel. This function - * should be called only once in the generation lifecycle. - */ - protected function prepareCacheLabels() - { - $export = $this->exportManager->getExport($this->exportAlias); - $keys = $export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - // get an array with all values for this key if possible - $values = \array_map(static fn ($v) => $v[$key], $this->result); - // store the label in the labelsCache property - $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); - } - } - - /** - * add the headers to lines array. - * - * @param array $lines the lines where the header will be added - */ - protected function prepareHeaders(array &$lines) - { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = \count($this->result) > 0 ? $this->result[0] : []; - $header_line = []; - - if (true === $this->formatterData['numerotation']) { - $lines[] = [$this->translator->trans('Number')]; - } - - foreach ($first_row as $key => $value) { - $lines[] = [$this->getLabel($key, '_header')]; - } - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php index 76e17378f..87c46dfbb 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php @@ -11,126 +11,32 @@ declare(strict_types=1); namespace Chill\MainBundle\Export\Formatter; -use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\ExportGenerationContext; +use Chill\MainBundle\Export\ExportInterface; +use Chill\MainBundle\Export\ExportManagerAwareInterface; +use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormatterInterface; +use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -class SpreadSheetFormatter implements FormatterInterface +final class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInterface { - /** - * an array where keys are the aggregators aliases and - * values are the data. - * - * replaced when `getResponse` is called. - * - * @var type - */ - protected $aggregatorsData; + use ExportManagerAwareTrait; - /** - * The export. - * - * replaced when `getResponse` is called. - * - * @var \Chill\MainBundle\Export\ExportInterface - */ - protected $export; - - /** - * replaced when `getResponse` is called. - * - * @var type - */ - // protected $aggregators; - - /** - * array containing value of export form. - * - * replaced when `getResponse` is called. - * - * @var array - */ - protected $exportData; - - /** - * @var ExportManager - */ - protected $exportManager; - - /** - * replaced when `getResponse` is called. - * - * @var array - */ - protected $filtersData; - - /** - * replaced when `getResponse` is called. - * - * @var type - */ - protected $formatterData; - - /** - * The result, as returned by the export. - * - * replaced when `getResponse` is called. - * - * @var type - */ - protected $result; - - /** - * replaced when `getResponse` is called. - * - * @var array - */ - // protected $labels; - - /** - * temporary file to store spreadsheet. - * - * @var string - */ - protected $tempfile; - - /** - * @var TranslatorInterface - */ - protected $translator; - - /** - * cache for displayable result. - * - * This cache is reset when `getResponse` is called. - * - * The array's keys are the keys in the raw result, and - * values are the callable which will transform the raw result to - * displayable result. - */ - private ?array $cacheDisplayableResult = null; - - /** - * Whethe `cacheDisplayableResult` is initialized or not. - */ - private bool $cacheDisplayableResultIsInitialized = false; - - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) - { - $this->translator = $translatorInterface; - $this->exportManager = $exportManager; - } + public function __construct(private readonly TranslatorInterface $translator) {} public function buildForm( FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases, - ) { + ): void { // choosing between formats $builder->add('format', ChoiceType::class, [ 'choices' => [ @@ -142,7 +48,7 @@ class SpreadSheetFormatter implements FormatterInterface ]); // ordering aggregators - $aggregators = $this->exportManager->getAggregators($aggregatorAliases); + $aggregators = $this->getExportManager()->getAggregators($aggregatorAliases); $nb = \count($aggregatorAliases); foreach ($aggregators as $alias => $aggregator) { @@ -155,11 +61,26 @@ class SpreadSheetFormatter implements FormatterInterface } } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return $formData; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return $formData; + } + public function getFormDefaultData(array $aggregatorAliases): array { $data = ['format' => 'xlsx']; - $aggregators = iterator_to_array($this->exportManager->getAggregators($aggregatorAliases)); + $aggregators = iterator_to_array($this->getExportManager()->getAggregators($aggregatorAliases)); foreach (array_keys($aggregators) as $index => $alias) { $data[$alias] = ['order' => $index + 1]; } @@ -172,6 +93,51 @@ class SpreadSheetFormatter implements FormatterInterface return 'SpreadSheet (xlsx, ods)'; } + public function generate( + $result, + $formatterData, + string $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData, + ExportGenerationContext $context, + ) { + // Initialize local variables instead of class properties + /** @var ExportInterface $export */ + $export = $this->getExportManager()->getExport($exportAlias); + + // Initialize cache variables + $cacheDisplayableResult = $this->initializeDisplayable($result, $export, $exportData, $aggregatorsData); + + $tempfile = \tempnam(\sys_get_temp_dir(), ''); + + if (false === $tempfile) { + throw new \RuntimeException('Unable to create temporary file'); + } + + $this->generateContent( + $context, + $tempfile, + $result, + $formatterData, + $export, + $exportData, + $filtersData, + $aggregatorsData, + $cacheDisplayableResult, + ); + + $result = new FormattedExportGeneration( + file_get_contents($tempfile), + $this->getContentType($formatterData['format']), + ); + + // remove the temp file from disk + \unlink($tempfile); + + return $result; + } + public function getResponse( $result, $formatterData, @@ -179,44 +145,22 @@ class SpreadSheetFormatter implements FormatterInterface array $exportData, array $filtersData, array $aggregatorsData, + ExportGenerationContext $context, ): Response { - // store all data when the process is initiated - $this->result = $result; - $this->formatterData = $formatterData; - $this->export = $this->exportManager->getExport($exportAlias); - $this->exportData = $exportData; - $this->filtersData = $filtersData; - $this->aggregatorsData = $aggregatorsData; + $formattedResult = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context); - // reset cache - $this->cacheDisplayableResult = []; - $this->cacheDisplayableResultIsInitialized = false; - - $response = new Response(); - $response->headers->set( - 'Content-Type', - $this->getContentType($this->formatterData['format']) - ); - - $this->tempfile = \tempnam(\sys_get_temp_dir(), ''); - $this->generateContent(); - - $f = \fopen($this->tempfile, 'rb'); - $response->setContent(\stream_get_contents($f)); - fclose($f); - - // remove the temp file from disk - \unlink($this->tempfile); + $response = new BinaryFileResponse($formattedResult->content); + $response->headers->set('Content-Type', $formattedResult->contentType); return $response; } - public function getType() + public function getType(): string { return 'tabular'; } - protected function addContentTable( + private function addContentTable( Worksheet $worksheet, $sortedResults, $line, @@ -238,20 +182,21 @@ class SpreadSheetFormatter implements FormatterInterface * * @return int the line number after the last description */ - protected function addFiltersDescription(Worksheet &$worksheet) + private function addFiltersDescription(Worksheet &$worksheet, ExportGenerationContext $context, array $filtersData) { $line = 3; - foreach ($this->filtersData as $alias => $data) { - $filter = $this->exportManager->getFilter($alias); - $description = $filter->describeAction($data, 'string'); - + foreach ($filtersData as $alias => $data) { + $filter = $this->getExportManager()->getFilter($alias); + $description = $filter->describeAction($data, $context); if (\is_array($description)) { $description = $this->translator ->trans( $description[0], - $description[1] ?? [] + $description[1] ?? [], ); + } elseif ($description instanceof TranslatableInterface) { + $description = $description->trans($this->translator, $this->translator->getLocale()); } $worksheet->setCellValue('A'.$line, $description); @@ -266,23 +211,23 @@ class SpreadSheetFormatter implements FormatterInterface * * return the line number where the next content (i.e. result) should * be appended. - * - * @param int $line - * - * @return int */ - protected function addHeaders( + private function addHeaders( Worksheet &$worksheet, array $globalKeys, - $line, - ) { + int $line, + array $cacheDisplayableResult = [], + ): int { // get the displayable form of headers $displayables = []; - foreach ($globalKeys as $key) { - $displayables[] = $this->translator->trans( - $this->getDisplayableResult($key, '_header') - ); + $displayable = $this->getDisplayableResult($key, '_header', $cacheDisplayableResult); + + if ($displayable instanceof TranslatableInterface) { + $displayables[] = $displayable->trans($this->translator, $this->translator->getLocale()); + } else { + $displayables[] = $this->translator->trans($this->getDisplayableResult($key, '_header', $cacheDisplayableResult)); + } } // add headers on worksheet @@ -299,9 +244,9 @@ class SpreadSheetFormatter implements FormatterInterface * Add the title to the worksheet and merge the cell containing * the title. */ - protected function addTitleToWorkSheet(Worksheet &$worksheet) + private function addTitleToWorkSheet(Worksheet &$worksheet, $export) { - $worksheet->setCellValue('A1', $this->getTitle()); + $worksheet->setCellValue('A1', $this->getTitle($export)); $worksheet->mergeCells('A1:G1'); } @@ -310,14 +255,14 @@ class SpreadSheetFormatter implements FormatterInterface * * @return array where 1st member is spreadsheet, 2nd is worksheet */ - protected function createSpreadsheet() + private function createSpreadsheet($export) { $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $worksheet = $spreadsheet->getActiveSheet(); // setting the worksheet title and code name $worksheet - ->setTitle($this->getTitle()) + ->setTitle($this->getTitle($export)) ->setCodeName('result'); return [$spreadsheet, $worksheet]; @@ -326,29 +271,38 @@ class SpreadSheetFormatter implements FormatterInterface /** * Generate the content and write it to php://temp. */ - protected function generateContent() - { - [$spreadsheet, $worksheet] = $this->createSpreadsheet(); + private function generateContent( + ExportGenerationContext $context, + string $tempfile, + $result, + $formatterData, + $export, + array $exportData, + array $filtersData, + array $aggregatorsData, + array $cacheDisplayableResult, + ) { + [$spreadsheet, $worksheet] = $this->createSpreadsheet($export); - $this->addTitleToWorkSheet($worksheet); - $line = $this->addFiltersDescription($worksheet); + $this->addTitleToWorkSheet($worksheet, $export); + $line = $this->addFiltersDescription($worksheet, $context, $filtersData); - // at this point, we are going to sort retsults for an easier manipulation + // at this point, we are going to sort results for an easier manipulation [$sortedResult, $exportKeys, $aggregatorKeys, $globalKeys] = - $this->sortResult(); + $this->sortResult($result, $export, $exportData, $aggregatorsData, $formatterData, $cacheDisplayableResult); - $line = $this->addHeaders($worksheet, $globalKeys, $line); + $line = $this->addHeaders($worksheet, $globalKeys, $line, $cacheDisplayableResult); - $line = $this->addContentTable($worksheet, $sortedResult, $line); + $this->addContentTable($worksheet, $sortedResult, $line); - $writer = match ($this->formatterData['format']) { + $writer = match ($formatterData['format']) { 'ods' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'), 'xlsx' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'), 'csv' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv'), default => throw new \LogicException(), }; - $writer->save($this->tempfile); + $writer->save($tempfile); } /** @@ -357,7 +311,7 @@ class SpreadSheetFormatter implements FormatterInterface * * @return string[] an array containing the keys of aggregators */ - protected function getAggregatorKeysSorted() + private function getAggregatorKeysSorted(array $aggregatorsData, array $formatterData) { // empty array for aggregators keys $keys = []; @@ -365,7 +319,7 @@ class SpreadSheetFormatter implements FormatterInterface // during sorting $aggregatorKeyAssociation = []; - foreach ($this->aggregatorsData as $alias => $data) { + foreach ($aggregatorsData as $alias => $data) { $aggregator = $this->exportManager->getAggregator($alias); $aggregatorsKeys = $aggregator->getQueryKeys($data); // append the keys from aggregator to the $keys existing array @@ -377,9 +331,9 @@ class SpreadSheetFormatter implements FormatterInterface } // sort the result using the form - usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) { - $A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order']; - $B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order']; + usort($keys, function ($a, $b) use ($aggregatorKeyAssociation, $formatterData) { + $A = $formatterData[$aggregatorKeyAssociation[$a]]['order']; + $B = $formatterData[$aggregatorKeyAssociation[$b]]['order']; if ($A === $B) { return 0; @@ -395,7 +349,7 @@ class SpreadSheetFormatter implements FormatterInterface return $keys; } - protected function getContentType($format) + private function getContentType($format) { switch ($format) { case 'csv': @@ -412,25 +366,26 @@ class SpreadSheetFormatter implements FormatterInterface /** * Get the displayable result. - * - * @param string $key - * - * @return string */ - protected function getDisplayableResult($key, mixed $value) - { - if (false === $this->cacheDisplayableResultIsInitialized) { - $this->initializeCache($key); - } - + private function getDisplayableResult( + string $key, + mixed $value, + array $cacheDisplayableResult, + ): string|TranslatableInterface|\DateTimeInterface|int|float|bool { $value ??= ''; - return \call_user_func($this->cacheDisplayableResult[$key], $value); + return \call_user_func($cacheDisplayableResult[$key], $value); } - protected function getTitle() + private function getTitle($export): string { - $title = $this->translator->trans($this->export->getTitle()); + $original = $export->getTitle(); + + if ($original instanceof TranslatableInterface) { + $title = $original->trans($this->translator, $this->translator->getLocale()); + } else { + $title = $this->translator->trans($original); + } if (30 < strlen($title)) { return substr($title, 0, 30).'…'; @@ -439,8 +394,13 @@ class SpreadSheetFormatter implements FormatterInterface return $title; } - protected function initializeCache($key) - { + private function initializeDisplayable( + $result, + ExportInterface $export, + array $exportData, + array $aggregatorsData, + ): array { + $cacheDisplayableResult = []; /* * this function follows the following steps : * @@ -453,13 +413,12 @@ class SpreadSheetFormatter implements FormatterInterface // 1. create an associative array with key and export / aggregator $keysExportElementAssociation = []; // keys for export - foreach ($this->export->getQueryKeys($this->exportData) as $key) { - $keysExportElementAssociation[$key] = [$this->export, - $this->exportData, ]; + foreach ($export->getQueryKeys($exportData) as $key) { + $keysExportElementAssociation[$key] = [$export, $exportData]; } // keys for aggregator - foreach ($this->aggregatorsData as $alias => $data) { - $aggregator = $this->exportManager->getAggregator($alias); + foreach ($aggregatorsData as $alias => $data) { + $aggregator = $this->getExportManager()->getAggregator($alias); foreach ($aggregator->getQueryKeys($data) as $key) { $keysExportElementAssociation[$key] = [$aggregator, $data]; @@ -471,7 +430,7 @@ class SpreadSheetFormatter implements FormatterInterface $allValues = []; // store all the values in an array - foreach ($this->result as $row) { + foreach ($result as $row) { foreach ($keys as $key) { $allValues[$key][] = $row[$key]; } @@ -482,15 +441,14 @@ class SpreadSheetFormatter implements FormatterInterface foreach ($keysExportElementAssociation as $key => [$element, $data]) { // handle the case when there is not results lines (query is empty) if ([] === $allValues) { - $this->cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data); + $cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data); } else { - $this->cacheDisplayableResult[$key] = + $cacheDisplayableResult[$key] = $element->getLabels($key, \array_unique($allValues[$key]), $data); } } - // the cache is initialized ! - $this->cacheDisplayableResultIsInitialized = true; + return $cacheDisplayableResult; } /** @@ -528,23 +486,28 @@ class SpreadSheetFormatter implements FormatterInterface * ) * ``` */ - protected function sortResult() - { + private function sortResult( + $result, + ExportInterface $export, + array $exportData, + array $aggregatorsData, + array $formatterData, + array $cacheDisplayableResult, + ) { // get the keys for each row - $exportKeys = $this->export->getQueryKeys($this->exportData); - $aggregatorKeys = $this->getAggregatorKeysSorted(); - + $exportKeys = $export->getQueryKeys($exportData); + $aggregatorKeys = $this->getAggregatorKeysSorted($aggregatorsData, $formatterData); $globalKeys = \array_merge($aggregatorKeys, $exportKeys); - $sortedResult = \array_map(function ($row) use ($globalKeys) { + $sortedResult = \array_map(function ($row) use ($globalKeys, $cacheDisplayableResult) { $newRow = []; foreach ($globalKeys as $key) { - $newRow[] = $this->getDisplayableResult($key, $row[$key]); + $newRow[] = $this->getDisplayableResult($key, $row[$key], $cacheDisplayableResult); } return $newRow; - }, $this->result); + }, $result); \array_multisort($sortedResult); diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php index 0a35e087f..27ee7f44c 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php @@ -11,68 +11,42 @@ declare(strict_types=1); namespace Chill\MainBundle\Export\Formatter; -use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\ExportGenerationContext; +use Chill\MainBundle\Export\ExportManagerAwareInterface; +use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormatterInterface; +use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff - /** * Create a CSV List for the export. */ -class SpreadsheetListFormatter implements FormatterInterface +class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAwareInterface { - protected $exportAlias; + use ExportManagerAwareTrait; - protected $exportData; - - /** - * @var ExportManager - */ - protected $exportManager; - - protected $formatterData; - - /** - * This variable cache the labels internally. - * - * @var string[] - */ - protected $labelsCache; - - protected $result; - - /** - * @var TranslatorInterface - */ - protected $translator; - - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) - { - $this->translator = $translatorInterface; - $this->exportManager = $exportManager; - } + public function __construct(private readonly TranslatorInterface $translator) {} /** * build a form, which will be used to collect data required for the execution * of this formatter. * * @uses appendAggregatorForm - * - * @param string $exportAlias */ public function buildForm( FormBuilderInterface $builder, - $exportAlias, + string $exportAlias, array $aggregatorAliases, - ) { + ): void { $builder ->add('format', ChoiceType::class, [ 'choices' => [ @@ -92,58 +66,52 @@ class SpreadsheetListFormatter implements FormatterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['format' => $formData['format'], 'numerotation' => $formData['numerotation']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['format' => $formData['format'], 'numerotation' => $formData['numerotation']]; + } + public function getFormDefaultData(array $aggregatorAliases): array { return ['numerotation' => true, 'format' => 'xlsx']; } - public function getName() + public function getName(): string|TranslatableInterface { return 'Spreadsheet list formatter (.xlsx, .ods)'; } - /** - * Generate a response from the data collected on differents ExportElementInterface. - * - * @param mixed[] $result The result, as given by the ExportInterface - * @param mixed[] $formatterData collected from the current form - * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data - * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data - * - * @return Response The response to be shown - */ - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->exportAlias = $exportAlias; - $this->exportData = $exportData; - $this->formatterData = $formatterData; - + public function generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, ExportGenerationContext $context): FormattedExportGeneration + { $spreadsheet = new Spreadsheet(); $worksheet = $spreadsheet->getActiveSheet(); + $cacheLabels = $this->prepareCacheLabels($result, $exportAlias, $exportData); - $this->prepareHeaders($worksheet); + $this->prepareHeaders($cacheLabels, $worksheet, $result, $formatterData, $exportAlias, $exportData); $i = 1; foreach ($result as $row) { - if (true === $this->formatterData['numerotation']) { + if (true === $formatterData['numerotation']) { $worksheet->setCellValue('A'.($i + 1), (string) $i); } - $a = $this->formatterData['numerotation'] ? 'B' : 'A'; + $a = $formatterData['numerotation'] ? 'B' : 'A'; foreach ($row as $key => $value) { $row = $a.($i + 1); - $formattedValue = $this->getLabel($key, $value); + $formattedValue = $this->getLabel($cacheLabels, $key, $value, $result, $exportAlias, $exportData); if ($formattedValue instanceof \DateTimeInterface) { $worksheet->setCellValue($row, Date::PHPToExcel($formattedValue)); @@ -157,6 +125,8 @@ class SpreadsheetListFormatter implements FormatterInterface ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); } + } elseif ($formattedValue instanceof TranslatableInterface) { + $worksheet->setCellValue($row, $formattedValue->trans($this->translator)); } else { $worksheet->setCellValue($row, $formattedValue); } @@ -166,7 +136,7 @@ class SpreadsheetListFormatter implements FormatterInterface ++$i; } - switch ($this->formatterData['format']) { + switch ($formatterData['format']) { case 'ods': $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'); $contentType = 'application/vnd.oasis.opendocument.spreadsheet'; @@ -189,26 +159,52 @@ class SpreadsheetListFormatter implements FormatterInterface default: // this should not happen // throw an exception to ensure that the error is catched - throw new \OutOfBoundsException('The format '.$this->formatterData['format'].' is not supported'); + throw new \OutOfBoundsException('The format '.$formatterData['format'].' is not supported'); } - $response = new Response(); - $response->headers->set('content-type', $contentType); - $tempfile = \tempnam(\sys_get_temp_dir(), ''); $writer->save($tempfile); - $f = \fopen($tempfile, 'rb'); - $response->setContent(\stream_get_contents($f)); - fclose($f); + $generated = new FormattedExportGeneration( + file_get_contents($tempfile), + $contentType, + ); // remove the temp file from disk \unlink($tempfile); + return $generated; + } + + /** + * Generate a response from the data collected on differents ExportElementInterface. + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * + * @return Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData, + ExportGenerationContext $context, + ) { + $generated = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context); + + $response = new BinaryFileResponse($generated->content); + $response->headers->set('Content-Type', $generated->contentType); + return $response; } - public function getType() + public function getType(): string { return FormatterInterface::TYPE_LIST; } @@ -216,34 +212,29 @@ class SpreadsheetListFormatter implements FormatterInterface /** * Give the label corresponding to the given key and value. * - * @param string $key - * @param string $value - * - * @return string + * @return string|\DateTimeInterface|int|float|TranslatableInterface|null * * @throws \LogicException if the label is not found */ - protected function getLabel($key, $value) + private function getLabel(array $labelsCache, $key, $value, array $result, string $exportAlias, array $exportData) { - if (null === $this->labelsCache) { - $this->prepareCacheLabels(); + if (!\array_key_exists($key, $labelsCache)) { + throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($labelsCache)))); } - if (!\array_key_exists($key, $this->labelsCache)) { - throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache)))); - } - - return $this->labelsCache[$key]($value); + return $labelsCache[$key]($value); } /** - * Prepare the label cache which will be used by getLabel. This function - * should be called only once in the generation lifecycle. + * Prepare the label cache which will be used by getLabel. + * + * @return array The labels cache */ - protected function prepareCacheLabels() + private function prepareCacheLabels(array $result, string $exportAlias, array $exportData): array { - $export = $this->exportManager->getExport($this->exportAlias); - $keys = $export->getQueryKeys($this->exportData); + $labelsCache = []; + $export = $this->getExportManager()->getExport($exportAlias); + $keys = $export->getQueryKeys($exportData); foreach ($keys as $key) { // get an array with all values for this key if possible @@ -253,29 +244,31 @@ class SpreadsheetListFormatter implements FormatterInterface } return $v[$key]; - }, $this->result); - // store the label in the labelsCache property - $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); + }, $result); + // store the label in the labelsCache + $labelsCache[$key] = $export->getLabels($key, $values, $exportData); } + + return $labelsCache; } /** * add the headers to the csv file. */ - protected function prepareHeaders(Worksheet $worksheet) + protected function prepareHeaders(array $labelsCache, Worksheet $worksheet, array $result, array $formatterData, string $exportAlias, array $exportData) { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + $keys = $this->getExportManager()->getExport($exportAlias)->getQueryKeys($exportData); // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = \count($this->result) > 0 ? $this->result[0] : []; + $first_row = \count($result) > 0 ? $result[0] : []; $header_line = []; - if (true === $this->formatterData['numerotation']) { + if (true === $formatterData['numerotation']) { $header_line[] = $this->translator->trans('Number'); } foreach ($first_row as $key => $value) { $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header') + $this->getLabel($labelsCache, $key, '_header', $result, $exportAlias, $exportData) ); } diff --git a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php index 37ed788f4..ac9c3e903 100644 --- a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php @@ -12,7 +12,11 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatableInterface; +/** + * @method generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, ExportGenerationContext $context): FormattedExportGeneration + */ interface FormatterInterface { public const TYPE_LIST = 'list'; @@ -30,16 +34,16 @@ interface FormatterInterface */ public function buildForm( FormBuilderInterface $builder, - $exportAlias, + string $exportAlias, array $aggregatorAliases, - ); + ): void; /** * get the default data for the form build by buildForm. */ public function getFormDefaultData(array $aggregatorAliases): array; - public function getName(); + public function getName(): string|TranslatableInterface; /** * Generate a response from the data collected on differents ExportElementInterface. @@ -47,19 +51,28 @@ interface FormatterInterface * @param mixed[] $result The result, as given by the ExportInterface * @param mixed[] $formatterData collected from the current form * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * * @return \Symfony\Component\HttpFoundation\Response The response to be shown + * + * @deprecated use generate instead */ public function getResponse( - $result, - $formatterData, - $exportAlias, + array $result, + array $formatterData, + string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, + ExportGenerationContext $context, ); - public function getType(); + public function getType(): string; + + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; } diff --git a/src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php b/src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php new file mode 100644 index 000000000..eb89f7970 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php @@ -0,0 +1,34 @@ +exportManager = $exportManager; + } + + public function getExportManager(): ExportManager + { + if (null === $this->exportManager) { + throw new ExportRuntimeException('ExportManager not set'); + } + + return $this->exportManager; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ListInterface.php b/src/Bundle/ChillMainBundle/Export/ListInterface.php index 9b88525ca..3fe9315ae 100644 --- a/src/Bundle/ChillMainBundle/Export/ListInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ListInterface.php @@ -11,6 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Doctrine\ORM\NativeQuery; +use Doctrine\ORM\QueryBuilder; + /** * Define methods to export list. * @@ -19,5 +22,10 @@ namespace Chill\MainBundle\Export; * (and list does not support aggregation on their data). * * When used, the `ExportManager` will not handle aggregator for this class. + * + * @template Q of QueryBuilder|NativeQuery + * @template D of array + * + * @template-extends ExportInterface */ interface ListInterface extends ExportInterface {} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php new file mode 100644 index 000000000..cb6c757b8 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php @@ -0,0 +1,31 @@ +id = $exportGeneration->getId(); + $this->userId = $user->getId(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php new file mode 100644 index 000000000..94610a995 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php @@ -0,0 +1,77 @@ +logger->info( + self::LOG_PREFIX.'Handle generation message', + [ + 'exportId' => (string) $exportRequestGenerationMessage->id, + ] + ); + + if (null === $exportGeneration = $this->repository->find($exportRequestGenerationMessage->id)) { + throw new \UnexpectedValueException('ExportRequestGenerationMessage not found'); + } + + if (null === $user = $this->userRepository->find($exportRequestGenerationMessage->userId)) { + throw new \UnexpectedValueException('User not found'); + } + + if (StoredObject::STATUS_PENDING !== $exportGeneration->getStatus()) { + throw new UnrecoverableMessageHandlingException('object already generated'); + } + + $generated = $this->exportGenerator->generate($exportGeneration->getExportAlias(), $exportGeneration->getOptions(), $user); + $this->storedObjectManager->write($exportGeneration->getStoredObject(), $generated->content, $generated->contentType); + $exportGeneration->getStoredObject()->setStatus(StoredObject::STATUS_READY); + + $this->entityManager->flush(); + + $end = microtime(true); + + $this->logger->notice(self::LOG_PREFIX.'Export generation successfully finished', [ + 'exportId' => (string) $exportRequestGenerationMessage->id, + 'exportAlias' => $exportGeneration->getExportAlias(), + 'full_generation_duration' => $end - $exportGeneration->getCreatedAt()->getTimestamp(), + 'message_handler_duration' => $end - $start, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php b/src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php new file mode 100644 index 000000000..04c07f5cf --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php @@ -0,0 +1,74 @@ + 'onMessageFailed', + ]; + } + + public function onMessageFailed(WorkerMessageFailedEvent $event): void + { + if ($event->willRetry()) { + return; + } + + $message = $event->getEnvelope()->getMessage(); + + if (!$message instanceof ExportRequestGenerationMessage) { + return; + } + + if (null === $exportGeneration = $this->repository->find($message->id)) { + throw new \UnexpectedValueException('ExportRequestGenerationMessage not found'); + } + + $this->logger->error(self::LOG_PREFIX.'ExportRequestGenerationMessage failed to execute generation', [ + 'exportId' => (string) $message->id, + 'userId' => $message->userId, + 'alias' => $exportGeneration->getExportAlias(), + 'throwable_message' => $event->getThrowable()->getMessage(), + 'throwable_trace' => $event->getThrowable()->getTraceAsString(), + 'throwable' => $event->getThrowable()::class, + 'full_generation_duration_failure' => microtime(true) - $exportGeneration->getCreatedAt()->getTimestamp(), + ]); + + $this->markObjectAsFailed($event, $exportGeneration); + $this->entityManager->flush(); + } + + private function markObjectAsFailed(WorkerMessageFailedEvent $event, ExportGeneration $exportGeneration): void + { + $exportGeneration->getStoredObject()->addGenerationErrors($event->getThrowable()->getMessage()); + $exportGeneration->getStoredObject()->setStatus(StoredObject::STATUS_FAILURE); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php new file mode 100644 index 000000000..31e68a82d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php @@ -0,0 +1,24 @@ +exportGenerationId = $exportGeneration->getId()->toString(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php new file mode 100644 index 000000000..c1f59a8fd --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php @@ -0,0 +1,49 @@ +exportGenerationRepository->find($message->exportGenerationId); + + if (null === $exportGeneration) { + $this->logger->error(self::LOG_PREFIX.'ExportGeneration not found'); + throw new UnrecoverableMessageHandlingException(self::LOG_PREFIX.'ExportGeneration not found'); + } + + $storedObject = $exportGeneration->getStoredObject(); + $storedObject->setDeleteAt($this->clock->now()); + + $this->entityManager->remove($exportGeneration); + $this->entityManager->flush(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php b/src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php new file mode 100644 index 000000000..36e9c5f7d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php @@ -0,0 +1,124 @@ + array_map( + self::mapFormData(...), + array_filter( + $fromOptions['export']['export']['export'] ?? [], + static fn (string $key) => !in_array($key, ['filters', 'aggregators', 'pick_formatter'], true), + ARRAY_FILTER_USE_KEY, + ), + ), + 'version' => 1, + ]; + + $to['pick_formatter'] = $fromOptions['export']['export']['pick_formatter']['alias'] ?? null; + $to['centers'] = [ + 'centers' => array_values(array_map(static fn ($id) => (int) $id, $fromOptions['centers']['centers']['c'] ?? $fromOptions['centers']['centers']['center'] ?? [])), + 'regroupments' => array_values(array_map(static fn ($id) => (int) $id, $fromOptions['centers']['centers']['regroupment'] ?? [])), + ]; + $to['formatter'] = [ + 'form' => $fromOptions['formatter']['formatter'] ?? [], + 'version' => 1, + ]; + + return $to; + } + + private static function mapEnabledStatus(array $modifiersData): array + { + if ('1' === ($modifiersData['enabled'] ?? '0')) { + return [ + 'form' => array_map(self::mapFormData(...), $modifiersData['form'] ?? []), + 'version' => 1, + 'enabled' => true, + ]; + } + + return ['enabled' => false]; + } + + private static function mapFormData(array|string $formData): array|string|null + { + if (is_array($formData) && array_key_exists('roll', $formData)) { + return self::refactorRollingDate($formData); + } + + if (is_string($formData)) { + // we try different date formats + if (false !== \DateTimeImmutable::createFromFormat('d-m-Y', $formData)) { + return $formData; + } + if (false !== \DateTimeImmutable::createFromFormat('Y-m-d', $formData)) { + return $formData; + } + + // we try json content + try { + $data = json_decode($formData, true, 512, JSON_THROW_ON_ERROR); + + if (is_array($data)) { + if (array_key_exists('type', $data) && array_key_exists('id', $data) && in_array($data['type'], ['person', 'thirdParty', 'user'], true)) { + return $data['id']; + } + $response = []; + foreach ($data as $item) { + if (array_key_exists('type', $item) && array_key_exists('id', $item) && in_array($item['type'], ['person', 'thirdParty', 'user'], true)) { + $response[] = $item['id']; + } + } + if ([] !== $response) { + return $response; + } + } + } catch (\JsonException) { + return $formData; + } + } + + return $formData; + } + + private static function refactorRollingDate(array $formData): ?array + { + if ('' === $formData['roll']) { + return null; + } + + $fixedDate = null !== ($formData['fixedDate'] ?? null) && '' !== $formData['fixedDate'] ? + \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', sprintf('%s 00:00:00', $formData['fixedDate']), new \DateTimeZone(date_default_timezone_get())) : null; + + return (new RollingDate( + $formData['roll'], + $fixedDate, + ))->normalize(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ModifierInterface.php b/src/Bundle/ChillMainBundle/Export/ModifierInterface.php index d3ec9de83..9596e026b 100644 --- a/src/Bundle/ChillMainBundle/Export/ModifierInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ModifierInterface.php @@ -37,12 +37,12 @@ interface ModifierInterface extends ExportElementInterface * @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers) * @param mixed[] $data the data from the Form (builded by buildForm) */ - public function alterQuery(QueryBuilder $qb, $data); + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void; /** * On which type of Export this ModifiersInterface may apply. * * @return string the type on which the Modifiers apply */ - public function applyOn(); + public function applyOn(): string; } diff --git a/src/Bundle/ChillMainBundle/Export/SortExportElement.php b/src/Bundle/ChillMainBundle/Export/SortExportElement.php index 6228109ed..7fdac9be1 100644 --- a/src/Bundle/ChillMainBundle/Export/SortExportElement.php +++ b/src/Bundle/ChillMainBundle/Export/SortExportElement.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; final readonly class SortExportElement @@ -19,12 +20,21 @@ final readonly class SortExportElement private TranslatorInterface $translator, ) {} + private function trans(string|TranslatableInterface $message): string + { + if ($message instanceof TranslatableInterface) { + return $message->trans($this->translator, $this->translator->getLocale()); + } + + return $this->translator->trans($message); + } + /** * @param array $elements */ public function sortFilters(array &$elements): void { - uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->trans($a->getTitle()) <=> $this->trans($b->getTitle())); } /** @@ -32,6 +42,6 @@ final readonly class SortExportElement */ public function sortAggregators(array &$elements): void { - uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->trans($a->getTitle()) <=> $this->trans($b->getTitle())); } } diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php deleted file mode 100644 index 2a19c8e0b..000000000 --- a/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php +++ /dev/null @@ -1,56 +0,0 @@ - $form */ - $form = iterator_to_array($forms); - - $form['center']->setData($viewData); - - // NOTE: we do not map back the regroupments - } - - public function mapFormsToData(\Traversable $forms, &$viewData): void - { - /** @var array $forms */ - $forms = iterator_to_array($forms); - - $centers = []; - - foreach ($forms['center']->getData() as $center) { - $centers[spl_object_hash($center)] = $center; - } - - if (\array_key_exists('regroupment', $forms)) { - /** @var Regroupment $regroupment */ - foreach ($forms['regroupment']->getData() as $regroupment) { - foreach ($regroupment->getCenters() as $center) { - $centers[spl_object_hash($center)] = $center; - } - } - } - - $viewData = array_values($centers); - } -} diff --git a/src/Bundle/ChillMainBundle/Form/SavedExportType.php b/src/Bundle/ChillMainBundle/Form/SavedExportType.php index 16aa4e42e..beccfbd19 100644 --- a/src/Bundle/ChillMainBundle/Form/SavedExportType.php +++ b/src/Bundle/ChillMainBundle/Form/SavedExportType.php @@ -13,15 +13,22 @@ namespace Chill\MainBundle\Form; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Form\Type\ChillTextareaType; +use Chill\MainBundle\Form\Type\PickUserGroupOrUserDynamicType; +use Chill\MainBundle\Security\Authorization\SavedExportVoter; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; class SavedExportType extends AbstractType { + public function __construct(private readonly Security $security) {} + public function buildForm(FormBuilderInterface $builder, array $options) { + $savedExport = $options['data']; + $builder ->add('title', TextType::class, [ 'required' => true, @@ -29,6 +36,14 @@ class SavedExportType extends AbstractType ->add('description', ChillTextareaType::class, [ 'required' => false, ]); + + if ($this->security->isGranted(SavedExportVoter::SHARE, $savedExport)) { + $builder->add('share', PickUserGroupOrUserDynamicType::class, [ + 'multiple' => true, + 'required' => false, + 'label' => 'saved_export.Share', + ]); + } } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php index 8f0126049..cf78cd33d 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php @@ -66,8 +66,11 @@ class EntityToJsonTransformer implements DataTransformerInterface ]); } - private function denormalizeOne(array $item) + private function denormalizeOne(array|string $item) { + if ('me' === $item) { + return $item; + } if (!\array_key_exists('type', $item)) { throw new TransformationFailedException('the key "type" is missing on element'); } @@ -98,5 +101,6 @@ class EntityToJsonTransformer implements DataTransformerInterface 'json', $context, ); + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php index 72e501108..cc6e583d5 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php @@ -21,15 +21,18 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class AggregatorType extends AbstractType { + public const ENABLED_FIELD = 'enabled'; + public function buildForm(FormBuilderInterface $builder, array $options): void { $exportManager = $options['export_manager']; $aggregator = $exportManager->getAggregator($options['aggregator_alias']); $builder - ->add('enabled', CheckboxType::class, [ + ->add(self::ENABLED_FIELD, CheckboxType::class, [ 'value' => true, 'required' => false, + 'disabled' => $options['disable_enable_field'], ]); $aggregatorFormBuilder = $builder->create('form', FormType::class, [ @@ -53,6 +56,7 @@ class AggregatorType extends AbstractType { $resolver->setRequired('aggregator_alias') ->setRequired('export_manager') + ->setDefault('disable_enable_field', false) ->setDefault('compound', true) ->setDefault('error_bubbling', false); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php index fe4c43830..8f654ced2 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php @@ -35,7 +35,7 @@ class ExportType extends AbstractType public function __construct( private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement, - protected ParameterBagInterface $parameterBag, + ParameterBagInterface $parameterBag, ) { $this->personFieldsConfig = $parameterBag->get('chill_person.person_fields'); } @@ -43,6 +43,8 @@ class ExportType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { $export = $this->exportManager->getExport($options['export_alias']); + /** @var bool $canEditFull */ + $canEditFull = $options['can_edit_full']; $exportOptions = [ 'compound' => true, @@ -59,8 +61,18 @@ class ExportType extends AbstractType if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { // add filters - $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); + $filterAliases = $options['allowed_filters']; + $filters = []; + if (is_iterable($filterAliases)) { + foreach ($filterAliases as $alias => $filter) { + $filters[$alias] = $filter; + } + } else { + $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); + } + $this->sortExportElement->sortFilters($filters); + $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); foreach ($filters as $alias => $filter) { @@ -70,15 +82,26 @@ class ExportType extends AbstractType 'constraints' => [ new ExportElementConstraint(['element' => $filter]), ], + 'disable_enable_field' => !$canEditFull, ]); } $builder->add($filterBuilder); // add aggregators - $aggregators = $this->exportManager - ->getAggregatorsApplyingOn($export, $options['picked_centers']); + $aggregatorsAliases = $options['allowed_aggregators']; + $aggregators = []; + if (is_iterable($aggregatorsAliases)) { + foreach ($aggregatorsAliases as $alias => $aggregator) { + $aggregators[$alias] = $aggregator; + } + } else { + $aggregators = $this->exportManager + ->getAggregatorsApplyingOn($export, $options['picked_centers']); + } + $this->sortExportElement->sortAggregators($aggregators); + $aggregatorBuilder = $builder->create( self::AGGREGATOR_KEY, FormType::class, @@ -96,11 +119,11 @@ class ExportType extends AbstractType } } - $aggregatorBuilder->add($alias, AggregatorType::class, [ 'aggregator_alias' => $alias, 'export_manager' => $this->exportManager, 'label' => $aggregator->getTitle(), + 'disable_enable_field' => !$canEditFull, 'constraints' => [ new ExportElementConstraint(['element' => $aggregator]), ], @@ -125,8 +148,13 @@ class ExportType extends AbstractType public function configureOptions(OptionsResolver $resolver) { - $resolver->setRequired(['export_alias', 'picked_centers']) + $resolver->setRequired(['export_alias', 'picked_centers', 'can_edit_full']) ->setAllowedTypes('export_alias', ['string']) + ->setAllowedValues('can_edit_full', [true, false]) + ->setDefault('allowed_filters', null) + ->setAllowedTypes('allowed_filters', ['iterable', 'null']) + ->setDefault('allowed_aggregators', null) + ->setAllowedTypes('allowed_aggregators', ['iterable', 'null']) ->setDefault('compound', true) ->setDefault('constraints', [ // new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint() diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php index d4873df83..7e1550878 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php @@ -34,6 +34,7 @@ class FilterType extends AbstractType ->add(self::ENABLED_FIELD, CheckboxType::class, [ 'value' => true, 'required' => false, + 'disabled' => $options['disable_enable_field'], ]); $filterFormBuilder = $builder->create('form', FormType::class, [ @@ -58,6 +59,7 @@ class FilterType extends AbstractType $resolver ->setRequired('filter') ->setAllowedTypes('filter', [FilterInterface::class]) + ->setDefault('disable_enable_field', false) ->setDefault('compound', true) ->setDefault('error_bubbling', false); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php index fa8caf488..9196b05c0 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php @@ -14,9 +14,9 @@ namespace Chill\MainBundle\Form\Type\Export; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Regroupment; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Form\DataMapper\ExportPickCenterDataMapper; use Chill\MainBundle\Repository\RegroupmentRepository; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Service\Regroupement\RegroupementFiltering; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -27,27 +27,26 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ final class PickCenterType extends AbstractType { - public const CENTERS_IDENTIFIERS = 'c'; - public function __construct( private readonly ExportManager $exportManager, private readonly RegroupmentRepository $regroupmentRepository, private readonly AuthorizationHelperForCurrentUserInterface $authorizationHelper, + private readonly RegroupementFiltering $regroupementFiltering, ) {} public function buildForm(FormBuilderInterface $builder, array $options) { $export = $this->exportManager->getExport($options['export_alias']); $centers = $this->authorizationHelper->getReachableCenters( - $export->requiredRole() + $export->requiredRole(), ); $centersActive = array_filter($centers, fn (Center $c) => $c->getIsActive()); // order alphabetically - usort($centersActive, fn (Center $a, Center $b) => $a->getCenter() <=> $b->getName()); + usort($centersActive, fn (Center $a, Center $b) => $a->getName() <=> $b->getName()); - $builder->add('center', EntityType::class, [ + $builder->add('centers', EntityType::class, [ 'class' => Center::class, 'choices' => $centersActive, 'label' => 'center', @@ -56,18 +55,22 @@ final class PickCenterType extends AbstractType 'choice_label' => static fn (Center $c) => $c->getName(), ]); - if (\count($this->regroupmentRepository->findAllActive()) > 0) { - $builder->add('regroupment', EntityType::class, [ + $groups = $this->regroupementFiltering + ->filterContainsAtLeastOneCenter($this->regroupmentRepository->findAllActive(), $centersActive); + + // order alphabetically + usort($groups, fn (Regroupment $a, Regroupment $b) => $a->getName() <=> $b->getName()); + + if (\count($groups) > 0) { + $builder->add('regroupments', EntityType::class, [ 'class' => Regroupment::class, 'label' => 'regroupment', 'multiple' => true, 'expanded' => true, - 'choices' => $this->regroupmentRepository->findAllActive(), + 'choices' => $groups, 'choice_label' => static fn (Regroupment $r) => $r->getName(), ]); } - - $builder->setDataMapper(new ExportPickCenterDataMapper()); } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php new file mode 100644 index 000000000..93ecd3f4e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php @@ -0,0 +1,82 @@ +addViewTransformer(new EntityToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'], 'user')); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['multiple'] = $options['multiple']; + $view->vars['types'] = ['user']; + $view->vars['uniqid'] = uniqid('pick_user_or_me_dyn'); + $view->vars['suggested'] = []; + $view->vars['as_id'] = true === $options['as_id'] ? '1' : '0'; + $view->vars['submit_on_adding_new_entity'] = true === $options['submit_on_adding_new_entity'] ? '1' : '0'; + + foreach ($options['suggested'] as $user) { + $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); + } + // $user = /* should come from context */ $options['context']; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('multiple', false) + ->setAllowedTypes('multiple', ['bool']) + ->setDefault('compound', false) + ->setDefault('suggested', []) + // if set to true, only the id will be set inside the content. The denormalization will not work. + ->setDefault('as_id', false) + ->setAllowedTypes('as_id', ['bool']) + ->setDefault('submit_on_adding_new_entity', false) + ->setAllowedTypes('submit_on_adding_new_entity', ['bool']); + } + + public function getBlockPrefix() + { + return 'pick_entity_dynamic'; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/UserGroupType.php b/src/Bundle/ChillMainBundle/Form/UserGroupType.php index 2e3819560..2ab7ea358 100644 --- a/src/Bundle/ChillMainBundle/Form/UserGroupType.php +++ b/src/Bundle/ChillMainBundle/Form/UserGroupType.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Form; +use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Form\Type\TranslatableStringFormType; use Symfony\Component\Form\AbstractType; @@ -23,6 +24,9 @@ class UserGroupType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { + /** @var UserGroup $userGroup */ + $userGroup = $options['data']; + $builder ->add('label', TranslatableStringFormType::class, [ 'label' => 'user_group.Label', @@ -46,20 +50,25 @@ class UserGroupType extends AbstractType 'help' => 'user_group.ExcludeKeyHelp', 'required' => false, 'empty_data' => '', - ]) - ->add('users', PickUserDynamicType::class, [ - 'label' => 'user_group.Users', - 'multiple' => true, - 'required' => false, - 'empty_data' => [], - ]) - ->add('adminUsers', PickUserDynamicType::class, [ - 'label' => 'user_group.adminUsers', - 'multiple' => true, - 'required' => false, - 'empty_data' => [], - 'help' => 'user_group.adminUsersHelp', - ]) - ; + ]); + + if (!$userGroup->hasUserJob()) { + $builder + ->add('users', PickUserDynamicType::class, [ + 'label' => 'user_group.Users', + 'multiple' => true, + 'required' => false, + 'empty_data' => [], + ]) + ->add('adminUsers', PickUserDynamicType::class, [ + 'label' => 'user_group.adminUsers', + 'multiple' => true, + 'required' => false, + 'empty_data' => [], + 'help' => 'user_group.adminUsersHelp', + ]) + ; + } + } } diff --git a/src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php b/src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php new file mode 100644 index 000000000..bb70a22ba --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php @@ -0,0 +1,85 @@ + + * + * @implements AssociatedEntityToStoredObjectInterface + */ +class ExportGenerationRepository extends ServiceEntityRepository implements AssociatedEntityToStoredObjectInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ExportGeneration::class); + } + + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?ExportGeneration + { + return $this->createQueryBuilder('e') + ->where('e.storedObject = :storedObject') + ->setParameter('storedObject', $storedObject) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * @return list + */ + public function findExportGenerationByAliasAndUser(string $alias, User $user, int $limit = 100, int $offset = 0): array + { + return $this->createQueryBuilder('e') + ->where('e.createdBy = :user') + ->andWhere('e.exportAlias LIKE :alias') + ->orderBy('e.createdAt', 'DESC') + ->setParameter('user', $user) + ->setParameter('alias', $alias) + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getQuery() + ->getResult(); + } + + /** + * @return list + */ + public function findExportGenerationBySavedExportAndUser(SavedExport $savedExport, User $user, int $limit = 100, int $offset = 0): array + { + return $this->createQueryBuilder('e') + ->where('e.createdBy = :user') + ->andWhere('e.savedExport = :savedExport') + ->orderBy('e.createdAt', 'DESC') + ->setParameter('user', $user) + ->setParameter('savedExport', $savedExport) + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getQuery() + ->getResult(); + } + + public function findExpiredExportGeneration(\DateTimeImmutable $atDate): iterable + { + return $this->createQueryBuilder('e') + ->where('e.deleteAt < :atDate') + ->setParameter('atDate', $atDate) + ->getQuery() + ->toIterable(); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php index 35ec31ff3..ec24421f8 100644 --- a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php @@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\GeographicalUnit; +use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\Expr\Join; @@ -42,7 +43,7 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos $qb = $this->buildQueryGeographicalUnitContainingAddress($address); return $qb - ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) ->addOrderBy('IDENTITY(gu.layer)') ->addOrderBy('gu.unitName') ->getQuery() @@ -58,7 +59,7 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos ; return $qb - ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) ->innerJoin(Address::class, 'address', Join::WITH, 'ST_CONTAINS(gu.geom, address.point) = TRUE') ->where($qb->expr()->eq('address', ':address')) ->setParameter('address', $address) @@ -70,6 +71,19 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos return $this->repository->find($id); } + public function findSimpleGeographicalUnit(int $id): ?SimpleGeographicalUnitDTO + { + $qb = $this->repository + ->createQueryBuilder('gu'); + + return $qb + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) + ->where('gu.id = :id') + ->setParameter('id', $id) + ->getQuery() + ->getOneOrNullResult(); + } + /** * Will return only partial object, where the @see{GeographicalUnit::geom} property is not loaded. * @@ -79,7 +93,7 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos { return $this->repository ->createQueryBuilder('gu') - ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) ->addOrderBy('IDENTITY(gu.layer)') ->addOrderBy('gu.unitName') ->getQuery() diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php index b00b0cd98..695474036 100644 --- a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php @@ -27,4 +27,6 @@ interface GeographicalUnitRepositoryInterface extends ObjectRepository public function findGeographicalUnitContainingAddress(Address $address, int $offset = 0, int $limit = 50): array; public function countGeographicalUnitContainingAddress(Address $address): int; + + public function findSimpleGeographicalUnit(int $id): ?SimpleGeographicalUnitDTO; } diff --git a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php index 793015f5b..b78e5ccc1 100644 --- a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php @@ -16,9 +16,8 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; -use Doctrine\Persistence\ObjectRepository; -final readonly class RegroupmentRepository implements ObjectRepository +final readonly class RegroupmentRepository implements RegroupmentRepositoryInterface { private EntityRepository $repository; diff --git a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php new file mode 100644 index 000000000..501cee8c5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php @@ -0,0 +1,34 @@ + + */ +interface RegroupmentRepositoryInterface extends ObjectRepository +{ + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function findOneByName(string $name): ?Regroupment; + + /** + * @return array + */ + public function findRegroupmentAssociatedToNoCenter(): array; +} diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php b/src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php new file mode 100644 index 000000000..81174fd1e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php @@ -0,0 +1,32 @@ +savedExportRepository->find($uuid)) { + return $savedExport; + } + + return $this->exportGenerationRepository->find($uuid); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php index 702fdf7d6..9b0d5a2ef 100644 --- a/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php @@ -13,9 +13,12 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use Symfony\Component\String\UnicodeString; /** * @implements ObjectRepository @@ -55,6 +58,51 @@ class SavedExportRepository implements SavedExportRepositoryInterface ->where($qb->expr()->eq('se.user', ':user')) ->setParameter('user', $user); + return $this->prepareResult($qb, $orderBy, $limit, $offset); + } + + public function findSharedWithUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null, array $filters = []): array + { + $qb = $this->repository->createQueryBuilder('se'); + + $qb + ->where( + $qb->expr()->orX( + $qb->expr()->eq('se.user', ':user'), + $qb->expr()->isMemberOf(':user', 'se.sharedWithUsers'), + $qb->expr()->exists( + sprintf('SELECT 1 FROM %s ug WHERE ug MEMBER OF se.sharedWithGroups AND :user MEMBER OF ug.users', UserGroup::class) + ) + ) + ) + ->setParameter('user', $user); + + foreach ($filters as $key => $filter) { + if (self::FILTER_TITLE === ($key & self::FILTER_TITLE) + || self::FILTER_DESCRIPTION === ($key & self::FILTER_DESCRIPTION)) { + $filter = new UnicodeString($filter); + + $i = 0; + foreach ($filter->split(' ') as $word) { + $orx = $qb->expr()->orX(); + if (self::FILTER_TITLE === ($key & self::FILTER_TITLE)) { + $orx->add($qb->expr()->like('LOWER(se.title)', 'LOWER(:qs'.$i.')')); + } + if (self::FILTER_DESCRIPTION === ($key & self::FILTER_DESCRIPTION)) { + $orx->add($qb->expr()->like('LOWER(se.description)', 'LOWER(:qs'.$i.')')); + } + $qb->andWhere($orx); + $qb->setParameter('qs'.$i, '%'.$word->trim().'%'); + ++$i; + } + } + } + + return $this->prepareResult($qb, $orderBy, $limit, $offset); + } + + private function prepareResult(QueryBuilder $qb, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array + { if (null !== $limit) { $qb->setMaxResults($limit); } diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php index 3b168505f..c62e0f29c 100644 --- a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php @@ -20,6 +20,9 @@ use Doctrine\Persistence\ObjectRepository; */ interface SavedExportRepositoryInterface extends ObjectRepository { + public const FILTER_TITLE = 0x01; + public const FILTER_DESCRIPTION = 0x10; + public function find($id): ?SavedExport; /** @@ -34,6 +37,15 @@ interface SavedExportRepositoryInterface extends ObjectRepository */ public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; + /** + * Get the saved export created by and the user and the ones shared with the user. + * + * @param array $filters filters where keys are one of the constant starting with FILTER_ + * + * @return list + */ + public function findSharedWithUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null, array $filters = []): array; + public function findOneBy(array $criteria): ?SavedExport; public function getClassName(): string; diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php index 6b793a2c8..ff8e5ffd2 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Repository; +use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\EntityManagerInterface; @@ -30,9 +31,6 @@ readonly class UserJobRepository implements UserJobRepositoryInterface return $this->repository->find($id); } - /** - * @return array|UserJob[] - */ public function findAll(): array { return $this->repository->findAll(); @@ -56,12 +54,20 @@ readonly class UserJobRepository implements UserJobRepositoryInterface return $jobs; } - /** - * @param mixed|null $limit - * @param mixed|null $offset - * - * @return array|object[]|UserJob[] - */ + public function findAllNotAssociatedWithUserGroup(): array + { + $qb = $this->repository->createQueryBuilder('u'); + $qb->select('u'); + + $qb->where( + $qb->expr()->not( + $qb->expr()->exists(sprintf('SELECT 1 FROM %s ug WHERE ug.userJob = u', UserGroup::class)) + ) + ); + + return $qb->getQuery()->getResult(); + } + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php index 75c2a5671..df042101c 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php @@ -14,18 +14,15 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\UserJob; use Doctrine\Persistence\ObjectRepository; +/** + * @template-extends ObjectRepository + */ interface UserJobRepositoryInterface extends ObjectRepository { public function find($id): ?UserJob; - /** - * @return array|UserJob[] - */ public function findAll(): array; - /** - * @return array|UserJob[] - */ public function findAllActive(): array; /** @@ -36,11 +33,14 @@ interface UserJobRepositoryInterface extends ObjectRepository public function findAllOrderedByName(): array; /** - * @param mixed|null $limit - * @param mixed|null $offset + * Find all the user job which are not related to a UserGroup. * - * @return array|object[]|UserJob[] + * This is useful for synchronizing UserGroups with jobs. + * + * @return list */ + public function findAllNotAssociatedWithUserGroup(): array; + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null); public function findOneBy(array $criteria): ?UserJob; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts new file mode 100644 index 000000000..7e350b102 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts @@ -0,0 +1,18 @@ +import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; +import { ExportGeneration } from "ChillMainAssets/types"; + +export const fetchExportGenerationStatus = async ( + exportGenerationId: string, +): Promise => + makeFetch( + "GET", + `/api/1.0/main/export-generation/${exportGenerationId}/object`, + ); + +export const generateFromSavedExport = async ( + savedExportUuid: string, +): Promise => + makeFetch( + "POST", + `/api/1.0/main/export/export-generation/create-from-saved-export/${savedExportUuid}`, + ); diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts new file mode 100644 index 000000000..e444aa2e5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts @@ -0,0 +1,3 @@ +export function buildReturnPath(location: Location): string { + return location.pathname + location.search; +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 1b3ee4594..9f6bb753e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -12,6 +12,11 @@ function loadDynamicPicker(element) { let apps = element.querySelectorAll('[data-module="pick-dynamic"]'); apps.forEach(function (el) { + let suggested; + let as_id; + let submit_on_adding_new_entity; + let label; + let isCurrentUserPicker; const isMultiple = parseInt(el.dataset.multiple) === 1, uniqId = el.dataset.uniqid, input = element.querySelector( @@ -22,12 +27,13 @@ function loadDynamicPicker(element) { ? JSON.parse(input.value) : input.value === "[]" || input.value === "" ? null - : [JSON.parse(input.value)], - suggested = JSON.parse(el.dataset.suggested), - as_id = parseInt(el.dataset.asId) === 1, - submit_on_adding_new_entity = - parseInt(el.dataset.submitOnAddingNewEntity) === 1, - label = el.dataset.label; + : [JSON.parse(input.value)]; + suggested = JSON.parse(el.dataset.suggested); + as_id = parseInt(el.dataset.asId) === 1; + submit_on_adding_new_entity = + parseInt(el.dataset.submitOnAddingNewEntity) === 1; + label = el.dataset.label; + isCurrentUserPicker = uniqId.startsWith("pick_user_or_me_dyn"); if (!isMultiple) { if (input.value === "[]") { @@ -44,6 +50,7 @@ function loadDynamicPicker(element) { ':uniqid="uniqid" ' + ':suggested="notPickedSuggested" ' + ':label="label" ' + + ':isCurrentUserPicker="isCurrentUserPicker" ' + '@addNewEntity="addNewEntity" ' + '@removeEntity="removeEntity" ' + '@addNewEntityProcessEnded="addNewEntityProcessEnded"' + @@ -61,6 +68,7 @@ function loadDynamicPicker(element) { as_id, submit_on_adding_new_entity, label, + isCurrentUserPicker, }; }, computed: { @@ -89,7 +97,8 @@ function loadDynamicPicker(element) { const ids = this.picked.map((el) => el.id); input.value = ids.join(","); } - console.log(entity); + console.log(this.picked); + // console.log(entity); } } else { if ( diff --git a/src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js b/src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js deleted file mode 100644 index 164e8e97e..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js +++ /dev/null @@ -1,14 +0,0 @@ -import { download_report } from "../../lib/download-report/download-report"; - -window.addEventListener("DOMContentLoaded", function (e) { - const export_generate_url = window.export_generate_url; - - if (typeof export_generate_url === "undefined") { - console.error("Alias not found!"); - throw new Error("Alias not found!"); - } - - const query = window.location.search, - container = document.querySelector("#download_container"); - download_report(export_generate_url + query.toString(), container); -}); diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts index b2ba6d948..2cd83bc64 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/types.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts @@ -1,4 +1,5 @@ import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc"; +import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types"; export interface DateTime { datetime: string; @@ -201,6 +202,16 @@ export interface WorkflowAttachment { genericDoc: null | GenericDoc; } +export interface ExportGeneration { + id: string; + type: "export_generation"; + exportAlias: string; + createdBy: User | null; + createdAt: DateTime | null; + status: StoredObjectStatus; + storedObject: StoredObject; +} + export interface PrivateCommentEmbeddable { comments: Record; } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue new file mode 100644 index 000000000..033eb6cac --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts new file mode 100644 index 000000000..e970e51b9 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts @@ -0,0 +1,15 @@ +import { createApp } from "vue"; +import App from "./App.vue"; + +const el = document.getElementById("app"); + +if (null === el) { + console.error("div element app was not found"); + throw new Error("div element app was not found"); +} + +const exportGenerationId = el?.dataset.exportGenerationId as string; +const title = el?.dataset.exportTitle as string; +const createdDate = el?.dataset.exportGenerationDate as string; + +createApp(App, { exportGenerationId, title, createdDate }).mount(el); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue index 56e324133..dde8c2dca 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue @@ -1,10 +1,26 @@ - + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue new file mode 100644 index 000000000..6f021ac80 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue new file mode 100644 index 000000000..f4dafd006 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts new file mode 100644 index 000000000..518746dea --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts @@ -0,0 +1,13 @@ +import { createApp } from "vue"; + +import App from "./App.vue"; + +const buttons = document.querySelectorAll( + "[data-generate-export-button]", +); + +buttons.forEach((button) => { + const savedExportUuid = button.dataset.savedExportUuid as string; + + createApp(App, { savedExportUuid, savedExportAlias: "" }).mount(button); +}); diff --git a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig index 927e496f9..3e32fdef1 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig @@ -446,11 +446,31 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles
-

Badges

+

Entity badges

Action d'accompagnement Type d'échange Rendez-vous
+ +

Badges

+ +

+ Primary + Secondary + Success + Danger + Warning + Info + Light + Dark + chill-blue + chill-green + chill-yellow + chill-orange + chill-red + chill-beige +

+ {% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig index a7d7216b4..6ae68f240 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig @@ -1,12 +1,14 @@ \ No newline at end of file + diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig index 873ace4ac..9f5b5e99f 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig @@ -1,5 +1,5 @@ {# - * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, + * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, / * * This program is free software: you can redistribute it and/or modify @@ -20,6 +20,49 @@ {% block title %}{{ 'Exports list'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + +{% endblock %} + +{% macro render_export_card(export, export_alias, generations) %} +
+
+
+

{{ export.title|trans }}

+

{{ export.description|trans }}

+
+ {% if generations|length > 0 %} +
    + {% for generation in generations %} +
  • + {{ generation.createdAt|format_datetime('short', 'short') }} + {% if generation.status == 'pending' %} +  {{ 'export.generation.Export generation is pending_short'|trans }} + {% elseif generation.status == 'failure' %} +  {{ 'export.generation.Error_short'|trans }} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + +
+
+{% endmacro %} {% block content %} @@ -28,45 +71,23 @@
- + {% for group, exports in grouped_exports %}{% if group != '_' %} -

{{ group|trans }}

-
+

{{ group|trans }}

+
{% for export_alias, export in exports %} -
-
-

{{ export.title|trans }}

-

{{ export.description|trans }}

-

- - {{ 'Create an export'|trans }} - -

-
-
+ {{ _self.render_export_card(export, export_alias, last_executions[export_alias]) }} {% endfor %}
{% endif %}{% endfor %} - + {% if grouped_exports|keys|length > 1 and grouped_exports['_']|length > 0 %} -

{{ 'Ungrouped exports'|trans }}

+

{{ 'Ungrouped exports'|trans }}

{% endif %} - -
+ +
{% for export_alias,export in grouped_exports['_'] %} - -
-
-

{{ export.title|trans }}

-

{{ export.description|trans }}

-

- - {{ 'Create an export'|trans }} - -

-
-
- + {{ _self.render_export_card(export, export_alias, last_executions[export_alias]) }} {% endfor %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig index 2e2dc0ec6..42d2d4574 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig @@ -40,15 +40,15 @@

{{ 'Center'|trans }}

- {{ form_widget(form.centers.center) }} + {{ form_widget(form.centers.centers) }}
- {% if form.centers.regroupment is defined %} + {% if form.centers.regroupments is defined %}

{{ 'Pick aggregated centers'|trans }}

- {{ form_widget(form.centers.regroupment) }} + {{ form_widget(form.centers.regroupments) }} {% endif %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig b/src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig new file mode 100644 index 000000000..75bf0fa75 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig @@ -0,0 +1,61 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('page_download_exports') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('page_download_exports') }} +{% endblock %} + + {% block title exportGeneration.linkedToSavedExport ? exportGeneration.savedExport.title : ('Download export'|trans) %} + +{% block content %} +

{{ block('title') }}

+
+ + +{% endblock content %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig index 98cfd6281..f1f741d1a 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig @@ -2,6 +2,16 @@ {% block title %}{{ 'saved_export.Edit'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + {% block content %}

{{ block('title') }}

@@ -10,9 +20,13 @@ {{ form_row(form.title) }} {{ form_row(form.description) }} + {% if form.share is defined %} + {{ form_row(form.share) }} + {% endif %} + {{ form_end(form) }}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig index 7e0517e31..6ccadbcdf 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig @@ -1,6 +1,88 @@ {% extends "@ChillMain/layout.html.twig" %} -{% block title %}{{ 'saved_export.My saved exports'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_saved_export_button') }} + +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_saved_export_button') }} +{% endblock %} + +{% block title %}{{ 'saved_export.Saved exports'|trans }}{% endblock %} + + +{% macro render_export_card(saved, export, export_alias, generations) %} +
+
+
+ {{ export.title|trans }} +
+
+

{{ saved.title }}

+ {% if app.user is same as saved.user %} +

+ {% if app.user is same as saved.user %}{{ 'saved_export.Owner'|trans }}{% endif %} + {% if saved.isShared() %}{{ 'saved_export.Shared with others'|trans }}{% endif %} +

+ {% else %} +

+ Partagé par {{ saved.user|chill_entity_render_box }} +

+ {% endif %} +

{{ saved.description|chill_markdown_to_html }}

+
+ {% if generations|length > 0 %} +
    + {% for generation in generations %} +
  • + {{ generation.createdAt|format_datetime('short', 'short') }} + {% if generation.status == 'pending' %} +  {{ 'export.generation.Export generation is pending_short'|trans }} + {% elseif generation.status == 'failure' %} +  {{ 'export.generation.Error_short'|trans }} + {% endif %} +
  • + {% endfor %} +
+ {% endif %} + +
+
+{% endmacro %} {% block content %}
@@ -8,6 +90,7 @@ {{ include('@ChillMain/Export/_navbar.html.twig', {'current' : 'my'}) }}
+ {{ filter|chill_render_filter_order_helper }} {% if total == 0 %}

{{ 'saved_export.Any saved export'|trans }}

@@ -15,71 +98,23 @@ {% for group, saveds in grouped_exports %} {% if group != '_' %} -

{{ group }}

-
+

{{ group }}

+
{% for s in saveds %} -
-
-

{{ s.export.title|trans }}

-

{{ s.saved.title }}

- -
{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}
- -
- {{ s.saved.description|chill_markdown_to_html }} -
- - -
-
+ {{ _self.render_export_card(s.saved, s.export, s.saved.exportAlias, last_executions[s.saved.id.toString()]) }} {% endfor %}
{% endif %} {% endfor %} {% if grouped_exports|keys|length > 1 and grouped_exports['_']|default([])|length > 0 %} -

{{ 'Ungrouped exports'|trans }}

+

{{ 'Ungrouped exports'|trans }}

{% endif %}
{% for saveds in grouped_exports['_']|default([]) %} {% for s in saveds %} -
-
-

{{ s.export.title|trans }}

-

{{ s.saved.title }}

- -
{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}
- -
- {{ s.saved.description|chill_markdown_to_html }} -
- -
-
    -
  • -
  • -
  • -
-
-
-
+ {{ _self.render_export_card(s.saved, s.export, s.saved.exportAlias, last_executions[s.saved.id.toString()]) }} {% endfor %} {% endfor %}
diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig index 317f53e42..d6e3e3eee 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig @@ -2,14 +2,35 @@ {% block title %}{{ 'saved_export.New'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + {% block content %}

{{ block('title') }}

{{ form_start(form) }} {{ form_row(form.title) }} + + {% if showWarningAutoGeneratedDescription|default(false) %} + + {% endif %} + {{ form_row(form.description) }} + {% if form.share is defined %} + {{ form_row(form.share) }} + {% endif %} + {{ form_end(form) }}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig index c192eaaac..cacd082c9 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig @@ -10,19 +10,19 @@ {% for flashMessage in app.session.flashbag.get('success') %}
- {{ flashMessage|raw }} + {{ flashMessage|trans }}
{% endfor %} {% for flashMessage in app.session.flashbag.get('error') %}
- {{ flashMessage|raw }} + {{ flashMessage|trans }}
{% endfor %} {% for flashMessage in app.session.flashbag.get('notice') %}
- {{ flashMessage|raw }} + {{ flashMessage|trans }}
{% endfor %} diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php index ab73932b5..0bfea342c 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php @@ -49,9 +49,9 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface ); } - if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) { + if ($this->authorizationChecker->isGranted(ChillExportVoter::GENERATE_SAVED_EXPORT)) { $menu->addChild($this->translator->trans('Export Menu'), [ - 'route' => 'chill_main_export_index', + 'route' => 'chill_main_export_saved_list_my', ]) ->setExtras([ 'icons' => ['upload'], diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php index 10552d1f7..4e6719d0c 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php @@ -11,12 +11,21 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Authorization; +use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class ChillExportVoter extends Voter +class ChillExportVoter extends Voter implements ProvideRoleHierarchyInterface { - final public const EXPORT = 'chill_export'; + /** + * Role which give access to the creation of new export from the export itself. + */ + final public const COMPOSE_EXPORT = 'CHILL_MAIN_COMPOSE_EXPORT'; + + /** + * Role which give access to the execution and edition to the saved exports, but not for creating new ones. + */ + final public const GENERATE_SAVED_EXPORT = 'CHILL_MAIN_GENERATE_SAVED_EXPORT'; private readonly VoterHelperInterface $helper; @@ -24,7 +33,7 @@ class ChillExportVoter extends Voter { $this->helper = $voterHelperFactory ->generate(self::class) - ->addCheckFor(null, [self::EXPORT]) + ->addCheckFor(null, [self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT]) ->build(); } @@ -37,4 +46,21 @@ class ChillExportVoter extends Voter { return $this->helper->voteOnAttribute($attribute, $subject, $token); } + + public function getRolesWithHierarchy(): array + { + return ['export.role.export_role' => [ + self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT, + ]]; + } + + public function getRoles(): array + { + return [self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT]; + } + + public function getRolesWithoutScope(): array + { + return $this->getRoles(); + } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php new file mode 100644 index 000000000..008f76b40 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php @@ -0,0 +1,32 @@ +getUser()->getUserIdentifier() === $subject->getCreatedBy()->getUserIdentifier(); + } +} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php index 0e9a7da52..ec749f725 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php @@ -12,23 +12,37 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Authorization; use Chill\MainBundle\Entity\SavedExport; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportManager; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class SavedExportVoter extends Voter +final class SavedExportVoter extends Voter { - final public const DELETE = 'CHLL_MAIN_EXPORT_SAVED_DELETE'; + final public const DELETE = 'CHILL_MAIN_EXPORT_SAVED_DELETE'; - final public const EDIT = 'CHLL_MAIN_EXPORT_SAVED_EDIT'; + final public const EDIT = 'CHILL_MAIN_EXPORT_SAVED_EDIT'; - final public const GENERATE = 'CHLL_MAIN_EXPORT_SAVED_GENERATE'; + final public const GENERATE = 'CHILL_MAIN_EXPORT_SAVED_GENERATE'; + + final public const DUPLICATE = 'CHILL_MAIN_EXPORT_SAVED_DUPLICATE'; + + final public const SHARE = 'CHILL_MAIN_EXPORT_SAVED_SHARE'; private const ALL = [ self::DELETE, self::EDIT, self::GENERATE, + self::SHARE, + self::DUPLICATE, ]; + public function __construct( + private readonly ExportManager $exportManager, + private readonly AccessDecisionManagerInterface $accessDecisionManager, + ) {} + protected function supports($attribute, $subject): bool { return $subject instanceof SavedExport && \in_array($attribute, self::ALL, true); @@ -36,9 +50,30 @@ class SavedExportVoter extends Voter protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool { + /* @var SavedExport $subject */ + $user = $token->getUser(); + + if (!$user instanceof User) { + return false; + } + return match ($attribute) { - self::DELETE, self::EDIT, self::GENERATE => $subject->getUser() === $token->getUser(), + self::DELETE, self::EDIT => $subject->getUser() === $token->getUser(), + self::SHARE => $subject->getUser() === $token->getUser() && $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]), + self::DUPLICATE => $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]) && $this->accessDecisionManager->decide($token, [self::EDIT], $subject) , + self::GENERATE => $this->canUserGenerate($user, $subject), default => throw new \UnexpectedValueException('attribute not supported: '.$attribute), }; } + + private function canUserGenerate(User $user, SavedExport $savedExport): bool + { + if (!($savedExport->getUser() === $user || $savedExport->isSharedWithUser($user))) { + return false; + } + + $export = $this->exportManager->getExport($savedExport->getExportAlias()); + + return $this->exportManager->isGrantedForElement($export); + } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php new file mode 100644 index 000000000..4979ffec2 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php @@ -0,0 +1,43 @@ +repository->findAssociatedEntityToStoredObject($subject); + } + + public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool + { + if (StoredObjectRoleEnum::EDIT === $attribute) { + return false; + } + + if (null === $generation = $this->repository->findAssociatedEntityToStoredObject($subject)) { + throw new \UnexpectedValueException('generation not found'); + } + + return $this->security->isGranted(ExportGenerationVoter::VIEW, $generation); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php b/src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php new file mode 100644 index 000000000..9f1813c5c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php @@ -0,0 +1,44 @@ + $groups + * @param list
$centers + * + * @return list
+ */ + public function resolveCenters(array $groups, array $centers = []): array + { + $centersByHash = []; + + foreach ($groups as $group) { + foreach ($group->getCenters() as $center) { + $centersByHash[spl_object_hash($center)] = $center; + } + } + + foreach ($centers as $center) { + $centersByHash[spl_object_hash($center)] = $center; + } + + return array_values($centersByHash); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php b/src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php new file mode 100644 index 000000000..76e4a892a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php @@ -0,0 +1,37 @@ + $group->containsAtLeastOneCenter($centers)), + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php index 1f7f01dbc..ea673b872 100644 --- a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php +++ b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\MainBundle\Service\RollingDate; +use Doctrine\Instantiator\Exception\UnexpectedValueException; + class RollingDate { final public const ALL_T = [ @@ -61,6 +63,8 @@ class RollingDate final public const T_YEAR_PREVIOUS_START = 'year_previous_start'; + private const NORMALIZATION_FORMAT = 'Y-m-d-H:i:s.u e'; + /** * @param string|self::T_* $roll * @param \DateTimeImmutable|null $fixedDate Only to insert if $roll equals @see{self::T_FIXED_DATE} @@ -68,7 +72,7 @@ class RollingDate public function __construct( private readonly string $roll, private readonly ?\DateTimeImmutable $fixedDate = null, - private readonly \DateTimeImmutable $pivotDate = new \DateTimeImmutable('now'), + private readonly ?\DateTimeImmutable $pivotDate = null, ) {} public function getFixedDate(): ?\DateTimeImmutable @@ -76,7 +80,7 @@ class RollingDate return $this->fixedDate; } - public function getPivotDate(): \DateTimeImmutable + public function getPivotDate(): ?\DateTimeImmutable { return $this->pivotDate; } @@ -85,4 +89,31 @@ class RollingDate { return $this->roll; } + + public function normalize(): array + { + return [ + 'roll' => $this->getRoll(), + 'fixed_date' => $this->getFixedDate()?->format(self::NORMALIZATION_FORMAT), + 'pivot_date' => $this->getPivotDate()?->format(self::NORMALIZATION_FORMAT), + 'v' => 1, + ]; + } + + public static function fromNormalized(?array $data): ?self + { + if (null === $data) { + return null; + } + + if (1 === $data['v']) { + return new self( + $data['roll'], + null !== $data['fixed_date'] ? \DateTimeImmutable::createFromFormat(self::NORMALIZATION_FORMAT, (string) $data['fixed_date']) : null, + null !== $data['pivot_date'] ? \DateTimeImmutable::createFromFormat(self::NORMALIZATION_FORMAT, (string) $data['pivot_date']) : null, + ); + } + + throw new UnexpectedValueException('Format of the rolling date unknow, no version information'); + } } diff --git a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php index f569ebf18..c2633cebb 100644 --- a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php +++ b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php @@ -11,8 +11,12 @@ declare(strict_types=1); namespace Chill\MainBundle\Service\RollingDate; -class RollingDateConverter implements RollingDateConverterInterface +use Symfony\Component\Clock\ClockInterface; + +final readonly class RollingDateConverter implements RollingDateConverterInterface { + public function __construct(private readonly ClockInterface $clock) {} + public function convert(?RollingDate $rollingDate): ?\DateTimeImmutable { if (null === $rollingDate) { @@ -21,43 +25,43 @@ class RollingDateConverter implements RollingDateConverterInterface switch ($rollingDate->getRoll()) { case RollingDate::T_MONTH_CURRENT_START: - return $this->toBeginOfMonth($rollingDate->getPivotDate()); + return $this->toBeginOfMonth($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_MONTH_NEXT_START: - return $this->toBeginOfMonth($rollingDate->getPivotDate()->add(new \DateInterval('P1M'))); + return $this->toBeginOfMonth(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1M'))); case RollingDate::T_MONTH_PREVIOUS_START: - return $this->toBeginOfMonth($rollingDate->getPivotDate()->sub(new \DateInterval('P1M'))); + return $this->toBeginOfMonth(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1M'))); case RollingDate::T_QUARTER_CURRENT_START: - return $this->toBeginOfQuarter($rollingDate->getPivotDate()); + return $this->toBeginOfQuarter($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_QUARTER_NEXT_START: - return $this->toBeginOfQuarter($rollingDate->getPivotDate()->add(new \DateInterval('P3M'))); + return $this->toBeginOfQuarter(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P3M'))); case RollingDate::T_QUARTER_PREVIOUS_START: - return $this->toBeginOfQuarter($rollingDate->getPivotDate()->sub(new \DateInterval('P3M'))); + return $this->toBeginOfQuarter(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P3M'))); case RollingDate::T_WEEK_CURRENT_START: - return $this->toBeginOfWeek($rollingDate->getPivotDate()); + return $this->toBeginOfWeek($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_WEEK_NEXT_START: - return $this->toBeginOfWeek($rollingDate->getPivotDate()->add(new \DateInterval('P1W'))); + return $this->toBeginOfWeek(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1W'))); case RollingDate::T_WEEK_PREVIOUS_START: - return $this->toBeginOfWeek($rollingDate->getPivotDate()->sub(new \DateInterval('P1W'))); + return $this->toBeginOfWeek(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1W'))); case RollingDate::T_YEAR_CURRENT_START: - return $this->toBeginOfYear($rollingDate->getPivotDate()); + return $this->toBeginOfYear($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_YEAR_PREVIOUS_START: - return $this->toBeginOfYear($rollingDate->getPivotDate()->sub(new \DateInterval('P1Y'))); + return $this->toBeginOfYear(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1Y'))); case RollingDate::T_YEAR_NEXT_START: - return $this->toBeginOfYear($rollingDate->getPivotDate()->add(new \DateInterval('P1Y'))); + return $this->toBeginOfYear(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1Y'))); case RollingDate::T_TODAY: - return $rollingDate->getPivotDate(); + return $rollingDate->getPivotDate() ?? $this->clock->now(); case RollingDate::T_FIXED_DATE: if (null === $rollingDate->getFixedDate()) { @@ -75,7 +79,7 @@ class RollingDateConverter implements RollingDateConverterInterface { return \DateTimeImmutable::createFromFormat( 'Y-m-d His', - sprintf('%s-%s-01 000000', $date->format('Y'), $date->format('m')) + sprintf('%s-%s-01 000000', $date->format('Y'), $date->format('m')), ); } @@ -90,7 +94,7 @@ class RollingDateConverter implements RollingDateConverterInterface return \DateTimeImmutable::createFromFormat( 'Y-m-d His', - sprintf('%s-%s-01 000000', $date->format('Y'), $month) + sprintf('%s-%s-01 000000', $date->format('Y'), $month), ); } diff --git a/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php new file mode 100644 index 000000000..bc602cf2a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php @@ -0,0 +1,129 @@ +createNotExistingUserGroups(); + + $connection = $this->entityManager->getConnection(); + $stats = $connection->transactional(function (Connection $connection) { + $removed = $this->removeUserNotRelatedToJob($connection); + $created = $this->createNewAssociations($connection); + + return ['association_removed' => $removed, 'association_created' => $created]; + }); + + $fullStats = ['userjob_created' => $created, ...$stats]; + + $this->logger->info(self::LOG_PREFIX.'Executed synchronisation', $fullStats); + + return $fullStats; + } + + private function createNotExistingUserGroups(): int + { + $jobs = $this->userJobRepository->findAllNotAssociatedWithUserGroup(); + $counter = 0; + + foreach ($jobs as $job) { + $userGroup = new UserGroup(); + $userGroup->setUserJob($job); + $userGroup->setLabel( + [ + $this->translator->getLocale() => $this->translator->trans( + 'user_group.label_related_to_user_job', + ['job' => $this->translatableStringHelper->localize($job->getLabel())] + )] + ); + $userGroup->setBackgroundColor('#e5a50a')->setForegroundColor('#f6f5f4'); + + $this->entityManager->persist($userGroup); + $this->logger->info(self::LOG_PREFIX.'Will create user group', ['job' => $this->translatableStringHelper->localize($userGroup->getLabel())]); + ++$counter; + } + + $this->entityManager->flush(); + + return $counter; + } + + private function removeUserNotRelatedToJob(Connection $connection): int + { + $sql = <<<'SQL' + DELETE FROM chill_main_user_group_user + USING users AS u, chill_main_user_group ug + WHERE + chill_main_user_group_user.usergroup_id = ug.id + AND chill_main_user_group_user.user_id = u.id + -- only where user_group.userjob_id is set (we ignore groups not automatically created) + AND ug.userjob_id IS NOT NULL + AND ( + -- Case 1: User has no job history records matching the time period + NOT EXISTS ( + SELECT 1 FROM chill_main_user_job_history jh + WHERE jh.user_id = u.id + AND tsrange(jh.startdate, jh.enddate) @> localtimestamp + ) + OR + -- Case 2: User has job history but with different job_id or user is disabled + EXISTS ( + SELECT 1 FROM chill_main_user_job_history jh + WHERE jh.user_id = u.id + AND tsrange(jh.startdate, jh.enddate) @> localtimestamp + AND (jh.job_id <> ug.userjob_id OR u.enabled IS FALSE OR jh.job_id IS NULL) + ) + ) + SQL; + + $result = $connection->executeQuery($sql); + + return $result->rowCount(); + } + + private function createNewAssociations(Connection $connection): int + { + $sql = <<<'SQL' + INSERT INTO chill_main_user_group_user (usergroup_id, user_id) + SELECT cmug.id, jh.user_id + FROM chill_main_user_group cmug + JOIN chill_main_user_job_history jh ON jh.job_id = cmug.userjob_id AND tsrange(jh.startdate, jh.enddate) @> localtimestamp + JOIN users u ON u.id = jh.user_id + WHERE cmug.userjob_id IS NOT NULL AND u.enabled IS TRUE + ON CONFLICT DO NOTHING + SQL; + + $result = $connection->executeQuery($sql); + + return $result->rowCount(); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php new file mode 100644 index 000000000..673578f80 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php @@ -0,0 +1,42 @@ +getLastStart() < $this->clock->now()->sub(new \DateInterval('P1D')); + } + + public function getKey(): string + { + return self::KEY; + } + + public function run(array $lastExecutionData): ?array + { + return ($this->userJobSync)(); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php new file mode 100644 index 000000000..e596cafc3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php @@ -0,0 +1,20 @@ +get(EntityManagerInterface::class); + if (null === $user = $em->createQueryBuilder()->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery()->getOneOrNullResult()) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + /** * Compare aliases array before and after that aggregator alter query. * @@ -75,7 +90,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase { $aliases = $qb->getAllAliases(); - $this->getAggregator()->alterQuery($qb, $data); + $this->getAggregator()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $alteredQuery = $qb->getAllAliases(); @@ -115,7 +130,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase */ public function testQueryExecution(QueryBuilder $qb, array $data): void { - $this->getAggregator()->alterQuery($qb, $data); + $this->getAggregator()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $actual = $qb->getQuery()->getResult(); @@ -158,7 +173,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase $nbOfSelect = null !== $query->getDQLPart('select') ? \count($query->getDQLPart('select')) : 0; - $this->getAggregator()->alterQuery($query, $data); + $this->getAggregator()->alterQuery($query, $data, new ExportGenerationContext($this->getUser())); $this->assertGreaterThanOrEqual( $nbOfFrom, @@ -218,6 +233,44 @@ abstract class AbstractAggregatorTest extends KernelTestCase ); } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $aggregator = $this->getAggregator(); + + $normalized = $aggregator->normalizeFormData($data); + $actual = $aggregator->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($data), array_keys($actual)); + + foreach ($data as $key => $value) { + self::assertArrayHasKey($key, $actual); + if (array_key_exists($key, $customAssert)) { + call_user_func($customAssert[$key], $actual[$key], $value); + } elseif (is_iterable($value)) { + continue; + } elseif (is_object($value) && method_exists($value, 'getId')) { + self::assertEquals($value->getId(), $actual[$key]->getId()); + } else { + self::assertEquals($value, $actual[$key]); + } + } + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (static::getFormData() as $data) { + yield [$data, 1, []]; + } + } + /** * Test that the query keys are strings. * @@ -280,7 +333,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase $qb->setMaxResults(1); $queryKeys = $this->getAggregator()->getQueryKeys($data); - $this->getAggregator()->alterQuery($qb, $data); + $this->getAggregator()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php index 9e9a87833..7e0ecf166 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php @@ -11,7 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Test\Export; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\DirectExportInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Test\PrepareClientTrait; use Doctrine\ORM\EntityManagerInterface; @@ -99,6 +101,19 @@ abstract class AbstractExportTest extends WebTestCase */ abstract public static function getModifiersCombination(): array; + protected function getUser(): User + { + $em = static::getContainer()->get(EntityManagerInterface::class); + if (null === $user = $em->createQueryBuilder()->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery()->getOneOrNullResult()) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + protected function getParameters(bool $filterStatsByCenter): ParameterBagInterface { return new ParameterBag(['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStatsByCenter]]]); @@ -118,6 +133,55 @@ abstract class AbstractExportTest extends WebTestCase return [$exports]; } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $export = $this->getExport(); + + if (is_iterable($export)) { + foreach ($export as $e) { + $this->testOneDataNormalization($e, $data, $version, $customAssert); + } + } else { + $this->testOneDataNormalization($export, $data, $version, $customAssert); + } + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (static::getFormData() as $data) { + yield [$data, 1, []]; + } + } + + private function testOneDataNormalization(ExportInterface|DirectExportInterface $export, array $data, int $version, array $customAssert): void + { + $normalized = $export->normalizeFormData($data); + $actual = $export->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($data), array_keys($actual)); + + foreach ($data as $key => $value) { + self::assertArrayHasKey($key, $actual); + if (array_key_exists($key, $customAssert)) { + call_user_func($customAssert[$key], $actual[$key], $value); + } elseif (is_iterable($value)) { + continue; + } elseif (is_object($value) && method_exists($value, 'getId')) { + self::assertEquals($value->getId(), $actual[$key]->getId()); + } else { + self::assertEquals($value, $actual[$key]); + } + } + } + /** * Test the formatters type are string. */ @@ -205,7 +269,7 @@ abstract class AbstractExportTest extends WebTestCase // due to the fact that testing both methods use the same tools. $queryKeys = $export->getQueryKeys($data); - $query = $export->initiateQuery($modifiers, $acl, $data); + $query = $export->initiateQuery($modifiers, $acl, $data, $exportGenerationContext = new ExportGenerationContext($this->getUser())); // limit the result for the query for performance reason (only for QueryBuilder, // not possible in NativeQuery) @@ -213,7 +277,7 @@ abstract class AbstractExportTest extends WebTestCase $query->setMaxResults(1); } - $results = $export->getResult($query, $data); + $results = $export->getResult($query, $data, $exportGenerationContext); $this->assertIsArray( $results, @@ -295,7 +359,7 @@ abstract class AbstractExportTest extends WebTestCase public function testInitiateQuery(mixed $modifiers, mixed $acl, mixed $data) { foreach ($this->getExports() as $export) { - $query = $export->initiateQuery($modifiers, $acl, $data); + $query = $export->initiateQuery($modifiers, $acl, $data, new ExportGenerationContext($this->getUser())); $this->assertTrue( $query instanceof QueryBuilder || $query instanceof NativeQuery, @@ -350,7 +414,7 @@ abstract class AbstractExportTest extends WebTestCase public function testSupportsModifier(mixed $modifiers, mixed $acl, mixed $data) { foreach ($this->getExports() as $export) { - $query = $export->initiateQuery($modifiers, $acl, $data); + $query = $export->initiateQuery($modifiers, $acl, $data, new ExportGenerationContext($this->getUser())); if ($query instanceof QueryBuilder) { $this->assertContainsOnly( diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php index 05b7231b3..e76d7d504 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\MainBundle\Test\Export; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Prophecy\PhpUnit\ProphecyTrait; @@ -23,16 +25,6 @@ abstract class AbstractFilterTest extends KernelTestCase { use ProphecyTrait; - /** - * @var \Prophecy\Prophet - */ - private $prophet; - - protected function setUp(): void - { - $this->prophet = $this->getProphet(); - } - public static function tearDownAfterClass(): void { if (null !== self::getContainer()) { @@ -43,6 +35,19 @@ abstract class AbstractFilterTest extends KernelTestCase self::ensureKernelShutdown(); } + protected function getUser(): User + { + $em = static::getContainer()->get(EntityManagerInterface::class); + if (null === $user = $em->createQueryBuilder()->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery()->getOneOrNullResult()) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + /** * Create a filter which will be used in tests. * @@ -74,6 +79,44 @@ abstract class AbstractFilterTest extends KernelTestCase */ abstract public static function getQueryBuilders(); + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $filter = $this->getFilter(); + + $normalized = $filter->normalizeFormData($data); + $actual = $filter->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($data), array_keys($actual)); + + foreach ($data as $key => $value) { + self::assertArrayHasKey($key, $actual); + if (array_key_exists($key, $customAssert)) { + call_user_func($customAssert[$key], $actual[$key], $value); + } elseif (is_iterable($value)) { + continue; + } elseif (is_object($value) && method_exists($value, 'getId')) { + self::assertEquals($value->getId(), $actual[$key]->getId()); + } else { + self::assertEquals($value, $actual[$key]); + } + } + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (static::getFormData() as $data) { + yield [$data, 1, []]; + } + } + /** * Compare aliases array before and after that filter alter query. * @@ -83,7 +126,7 @@ abstract class AbstractFilterTest extends KernelTestCase { $aliases = $qb->getAllAliases(); - $this->getFilter()->alterQuery($qb, $data); + $this->getFilter()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $alteredQuery = $qb->getAllAliases(); @@ -129,7 +172,9 @@ abstract class AbstractFilterTest extends KernelTestCase $nbOfSelect = null !== $query->getDQLPart('select') ? \count($query->getDQLPart('select')) : 0; - $this->getFilter()->alterQuery($query, $data); + $context = new ExportGenerationContext($this->getUser()); + + $this->getFilter()->alterQuery($query, $data, $context); $this->assertGreaterThanOrEqual( $nbOfFrom, @@ -170,7 +215,7 @@ abstract class AbstractFilterTest extends KernelTestCase */ public function testQueryExecution(QueryBuilder $qb, mixed $data): void { - $this->getFilter()->alterQuery($qb, $data); + $this->getFilter()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $actual = $qb->getQuery()->getResult(); @@ -206,7 +251,8 @@ abstract class AbstractFilterTest extends KernelTestCase */ public function testDescriptionAction($data) { - $description = $this->getFilter()->describeAction($data); + $context = new ExportGenerationContext((new User())->setLabel('test user')); + $description = $this->getFilter()->describeAction($data, $context); $this->assertTrue( \is_string($description) || \is_array($description), @@ -262,15 +308,4 @@ abstract class AbstractFilterTest extends KernelTestCase yield [$data]; } } - - public function testGetTitle() - { - $title = $this->getFilter()->getTitle(); - - $this->assertIsString($title); - $this->assertNotEmpty( - $title, - 'test that the title is not empty' - ); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php new file mode 100644 index 000000000..8e9e65963 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php @@ -0,0 +1,81 @@ +setOptions($exportOptions = ['test' => 'content'])->setExportAlias('dummy_export_alias'); + + $user = new User(); + $reflection = new \ReflectionClass($user); + $id = $reflection->getProperty('id'); + $id->setValue($user, 1); + + $security = $this->prophesize(Security::class); + $security->isGranted(SavedExportVoter::GENERATE, $savedExport)->shouldBeCalled()->willReturn(true); + $security->getUser()->willReturn($user); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->persist(Argument::that( + static fn ($arg) => $arg instanceof ExportGeneration && $arg->getOptions() === $exportOptions && $arg->getSavedExport() === $savedExport, + ))->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + + $messenger = $this->prophesize(MessageBusInterface::class); + $messenger->dispatch(Argument::type(ExportRequestGenerationMessage::class))->shouldBeCalled()->will( + static fn (array $args): Envelope => new Envelope($args[0]), + ); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize(Argument::type(ExportGeneration::class), 'json', ['groups' => ['read']])->shouldBeCalled()->willReturn('{"test": "export-generation"}'); + + $controller = new ExportGenerationCreateFromSavedExportController( + $security->reveal(), + $entityManager->reveal(), + $messenger->reveal(), + new MockClock(), + $serializer->reveal() + ); + + $response = $controller($savedExport); + + self::assertInstanceOf(JsonResponse::class, $response); + self::assertEquals('{"test": "export-generation"}', $response->getContent()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php new file mode 100644 index 000000000..eff86072e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php @@ -0,0 +1,56 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + $environment = $this->prophesize(Environment::class); + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize(Argument::any(), 'json', ['groups' => ['read']])->willReturn('{}'); + $exportManager = $this->prophesize(ExportManager::class); + + $pending = new ExportGeneration('dummy', []); + + $controller = new ExportGenerationController($security->reveal(), $environment->reveal(), $serializer->reveal(), $exportManager->reveal()); + + $actual = $controller->objectStatus($pending); + self::assertEquals('{}', $actual->getContent()); + + $generated = new ExportGeneration('dummy', []); + $generated->getStoredObject()->setStatus(StoredObject::STATUS_READY); + + self:assertEquals('{}', $controller->objectStatus($generated)->getContent()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/ExportIndexControllerTest.php similarity index 84% rename from src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php rename to src/Bundle/ChillMainBundle/Tests/Controller/ExportIndexControllerTest.php index 6c7ade9bb..beaff9b5c 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/ExportIndexControllerTest.php @@ -21,13 +21,13 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; * * @coversNothing */ -final class ExportControllerTest extends WebTestCase +final class ExportIndexControllerTest extends WebTestCase { use PrepareClientTrait; public function testIndex() { - $client = $this->getClientAuthenticatedAsAdmin(); + $client = $this->getClientAuthenticated(); $client->request('GET', '/fr/exports/'); diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php new file mode 100644 index 000000000..cd98df66f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php @@ -0,0 +1,52 @@ +addUser($user2); + + // Create a SavedExport entity + $savedExport = new SavedExport(); + + // Share the saved export with user1 + $savedExport->addShare($user1); + + // Share the saved export with the group + $savedExport->addShare($group); + + // Assertions + $this->assertTrue($savedExport->isSharedWithUser($user1), 'User1 should have access to the saved export.'); + $this->assertTrue($savedExport->isSharedWithUser($user2), 'User2 (via group) should have access to the saved export.'); + $this->assertFalse($savedExport->isSharedWithUser($user3), 'User3 should not have access to the saved export.'); + + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php new file mode 100644 index 000000000..c47c4c878 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php @@ -0,0 +1,117 @@ +prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $this->assertTrue($cronJob->canRun(null)); + } + + public function testCanRunReturnsTrueWhenLastStartIsOlderThan24Hours() + { + $clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00')); + $repo = $this->prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $execution = new CronJobExecution('remove-expired-export-generation'); + $execution->setLastStart(new \DateTimeImmutable('2024-06-24 09:59:59')); + + $this->assertTrue($cronJob->canRun($execution)); + } + + public function testCanRunReturnsFalseWhenLastStartIsWithin24Hours() + { + $clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00')); + $repo = $this->prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $execution = new CronJobExecution('remove-expired-export-generation'); + $execution->setLastStart(new \DateTimeImmutable('2024-06-24 10:01:00')); + + $this->assertFalse($cronJob->canRun($execution)); + } + + public function testRunDispatchesMessagesForExpiredExportsAndReturnsLastDeletion() + { + $clock = new MockClock(new \DateTimeImmutable('2024-06-25 11:21:00')); + $repo = $this->prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $expiredExports = [ + new ExportGeneration('dummy', []), + ]; + + $repo->findExpiredExportGeneration(Argument::that(fn ($dateTime) => + // Ensure the repository is called with the current clock time + $dateTime instanceof \DateTimeImmutable + && $dateTime->getTimestamp() === $clock->now()->getTimestamp()))->willReturn($expiredExports); + + // Expect one RemoveExportGenerationMessage for each expired export + $bus->dispatch(Argument::that(fn (Envelope $envelope) => $envelope->getMessage() instanceof RemoveExportGenerationMessage)) + ->shouldBeCalledTimes(1) + ->will(fn ($args) => $args[0]); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $result = $cronJob->run([]); + + $this->assertIsArray($result); + $this->assertEquals(['last-deletion' => $clock->now()->getTimestamp()], $result); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php new file mode 100644 index 000000000..34137ff26 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php @@ -0,0 +1,333 @@ +prophesize(FilterInterface::class); + $filterEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $filterEnabled->getNormalizationVersion()->willReturn(1); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled(); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $aggregatorEnabled->getNormalizationVersion()->willReturn(1); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled(); + + $export = $this->prophesize(ExportInterface::class); + $export->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $export->getNormalizationVersion()->willReturn(1); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $formatter->getNormalizationVersion()->willReturn(1); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $center = $this->prophesize(Center::class); + $center->getId()->willReturn(10); + + $formData = [ + 'centers' => ['centers' => [$center->reveal()]], + 'export' => ['test' => '0'], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => ['test' => '0'], + ]; + + $expected = [ + 'export' => ['form' => ['test' => '0'], 'version' => 1], + 'centers' => ['centers' => [10], 'regroupments' => []], + 'filters' => [ + 'filtersEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + 'version' => 1, + ], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer( + $exportManager->reveal(), + $this->prophesize(CenterRepositoryInterface::class)->reveal(), + $regroupmentRepository->reveal() + ); + + $actual = $exportConfigNormalizer->normalizeConfig('export', $formData); + + self::assertEqualsCanonicalizing($expected, $actual); + } + + public function testDenormalizeConfig(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $filterDisabled->getFormDefaultData()->willReturn(['default' => '0']); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $aggregatorDisabled->getFormDefaultData()->willReturn(['default' => '0']); + + $export = $this->prophesize(ExportInterface::class); + $export->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->find(10)->willReturn($center = new Center()); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $serialized = [ + 'centers' => ['regroupments' => [], 'centers' => [10]], + 'export' => ['form' => ['test' => '0'], 'version' => 1], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + 'version' => 1, + ], + ]; + + $expected = [ + 'export' => ['test' => '0'], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => ['test' => '0'], + 'centers' => ['centers' => [$center], 'regroupments' => []], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal(), $regroupmentRepository->reveal()); + $actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true); + + self::assertEquals($expected, $actual); + } + + public function testNormalizeConfigEmptyData(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $filterEnabled->getNormalizationVersion()->willReturn(1); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->normalizeFormData([])->shouldNotBeCalled(); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $aggregatorEnabled->getNormalizationVersion()->willReturn(1); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->normalizeFormData([])->shouldNotBeCalled(); + + $export = $this->prophesize(ExportInterface::class); + $export->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $export->getNormalizationVersion()->willReturn(1); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $formatter->getNormalizationVersion()->willReturn(1); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $center = $this->prophesize(Center::class); + $center->getId()->willReturn(10); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $formData = [ + 'centers' => ['centers' => [$center->reveal()]], + 'export' => [], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => []], + 'filterDisabled' => ['enabled' => false, 'form' => []], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => []], + 'aggregatorDisabled' => ['enabled' => false, 'form' => []], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [], + ]; + + $expected = [ + 'export' => ['form' => [], 'version' => 1], + 'centers' => ['centers' => [10], 'regroupments' => []], + 'filters' => [ + 'filtersEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => [], + 'version' => 1, + ], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal(), $regroupmentRepository->reveal()); + + $actual = $exportConfigNormalizer->normalizeConfig('export', $formData); + + self::assertEqualsCanonicalizing($expected, $actual); + } + + public function testDenormalizeConfigWithEmptyData(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $filterDisabled->getFormDefaultData()->willReturn([]); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $aggregatorDisabled->getFormDefaultData()->willReturn([]); + + $export = $this->prophesize(ExportInterface::class); + $export->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->find(10)->willReturn($center = new Center()); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $serialized = [ + 'centers' => ['centers' => [10], 'regroupments' => []], + 'export' => ['form' => [], 'version' => 1], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => [], + 'version' => 1, + ], + ]; + + $expected = [ + 'export' => [], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => []], + 'filterDisabled' => ['enabled' => false, 'form' => []], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => []], + 'aggregatorDisabled' => ['enabled' => false, 'form' => []], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [], + 'centers' => ['centers' => [$center], 'regroupments' => []], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal(), $regroupmentRepository->reveal()); + $actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true); + + self::assertEquals($expected, $actual); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php new file mode 100644 index 000000000..0e3d3d502 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php @@ -0,0 +1,121 @@ +normalizeDoctrineEntity($entity); + } + + public function denormalizeEntity(mixed $entity, ObjectRepository $repository) + { + return $this->denormalizeDoctrineEntity($entity, $repository); + } + + public function normalizeD(\DateTimeImmutable|\DateTime $date): string + { + return $this->normalizeDate($date); + } + + public function denormalizeD(string $date): \DateTimeImmutable|\DateTime + { + return $this->denormalizeDate($date); + } + }; + } + + public function testNormalizationDoctrineEntitySingle(): void + { + $entity = new class () { + public function getId(): int + { + return 1; + } + }; + + $repository = $this->prophesize(ObjectRepository::class); + $repository->find(1)->willReturn($entity); + + $normalized = $this->buildTrait()->normalizeEntity($entity); + $actual = $this->buildTrait()->denormalizeEntity($normalized, $repository->reveal()); + + self::assertSame($entity, $actual); + } + + public function testNormalizationDoctrineEntityMulti(): void + { + $entityA = new class () { + public function getId(): int + { + return 1; + } + }; + + $entityB = new class () { + public function getId(): int + { + return 2; + } + }; + + $repository = $this->prophesize(ObjectRepository::class); + $repository->findBy( + Argument::that(static fn ($arg): bool => in_array(1, $arg['id'] ?? []) && in_array(2, $arg['id'] ?? [])) + )->willReturn([$entityA, $entityB]); + + $normalized = $this->buildTrait()->normalizeEntity([$entityA, $entityB]); + $actual = $this->buildTrait()->denormalizeEntity($normalized, $repository->reveal()); + + self::assertContains(1, array_map(static fn (object $item) => $item->getId(), $actual)); + self::assertContains(2, array_map(static fn (object $item) => $item->getId(), $actual)); + self::assertCount(2, $actual); + } + + /** + * @dataProvider provideDate + */ + public function testNormalizationDate(\DateTimeImmutable|\DateTime $date): void + { + $normalized = $this->buildTrait()->normalizeD($date); + $actual = $this->buildTrait()->denormalizeD($normalized); + + self::assertEquals($date, $actual); + } + + public static function provideDate(): iterable + { + yield [new \DateTimeImmutable('2024-01-15T18:57:20', new \DateTimeZone('Europe/Athens'))]; + yield [new \DateTimeImmutable('2024-01-15T18:57:30', new \DateTimeZone('America/Havana'))]; + yield [new \DateTime('2024-01-15T18:57:40', new \DateTimeZone('Europe/Madrid'))]; + yield [new \DateTime('2024-01-15T18:57:50', new \DateTimeZone('Africa/Kinshasa'))]; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php new file mode 100644 index 000000000..75638dd0f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php @@ -0,0 +1,174 @@ +prophesize(Security::class); + $security->getUser()->willReturn($user = new User()); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('my_export', Argument::type('array'))->willReturn($options); + + $export = $this->prophesize(ExportInterface::class); + $export->getTitle()->willReturn('Title'); + + $myFilterString = $this->prophesize(FilterInterface::class); + $myFilterString->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn($string0 = 'This is a filter description'); + $myFilterArray = $this->prophesize(FilterInterface::class); + $myFilterArray->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn([$string1 = 'This is a filter with %argument%', $arg1 = ['%argument%' => 'zero']]); + $myFilterTranslatable = $this->prophesize(FilterInterface::class); + $myFilterTranslatable->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class)) + ->willReturn(new class () implements TranslatableInterface { + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return 'translatable'; + } + }); + + $myAggregator = $this->prophesize(AggregatorInterface::class); + $myAggregator->getTitle()->willReturn('Some aggregator'); + + $token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + + $exportManager = new ExportManager( + new NullLogger(), + $security->reveal(), + $this->prophesize(AuthorizationHelperInterface::class)->reveal(), + $tokenStorage, + ['my_export' => $export->reveal()], + ['my_aggregator' => $myAggregator->reveal()], + [ + 'my_filter_string' => $myFilterString->reveal(), + 'my_filter_array' => $myFilterArray->reveal(), + 'my_filter_translatable' => $myFilterTranslatable->reveal(), + ], + [], + ); + + $exportConfigProcessor = new ExportConfigProcessor($exportManager); + + $translator = $this->prophesize(TranslatorInterface::class); + $translator->trans('Title')->shouldBeCalled()->willReturn('Title'); + $translator->trans($string0)->shouldBeCalled()->willReturn($string0); + $translator->trans($string1, $arg1)->shouldBeCalled()->willReturn($string1); + $translator->trans('Some aggregator')->shouldBeCalled()->willReturn('Some aggregator'); + + $exportDescriptionHelper = new ExportDescriptionHelper( + $exportManager, + $exportConfigNormalizer->reveal(), + $exportConfigProcessor, + $translator->reveal(), + $security->reveal(), + ); + + $actual = $exportDescriptionHelper->describe('my_export', $options); + + self::assertIsArray($actual); + self::assertEquals($actual[0], 'Title'); + self::assertEquals($actual[1], 'This is a filter description'); + self::assertEquals($actual[2], 'This is a filter with %argument%'); + self::assertEquals($actual[3], 'translatable'); + self::assertEquals($actual[4], 'Some aggregator'); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php new file mode 100644 index 000000000..e01dc6566 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php @@ -0,0 +1,413 @@ + ['acl' => ['filter_stats_by_center' => $filterStat]]] + ); + } + + public function testGenerateHappyScenario() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [ + 'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']], + 'disabled_filter' => ['enabled' => false], + ], + 'aggregators' => [ + 'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']], + 'disabled_aggregator' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [$centerA = new Center()], 'regroupments' => [(new Regroupment())->addCenter($centerB = new Center())]], + ]; + $user = new User(); + + $export = $this->prophesize(ExportInterface::class); + $filter = $this->prophesize(FilterInterface::class); + $filter->applyOn()->willReturn('tagada'); + $aggregator = $this->prophesize(AggregatorInterface::class); + $aggregator->applyOn()->willReturn('tsointsoin'); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(QueryBuilder::class); + $query->getDQL()->willReturn('dummy'); + $dqlQuery = $this->prophesize(Query::class); + $dqlQuery->getSQL()->willReturn('dummy'); + $query->getQuery()->willReturn($dqlQuery->reveal()); + + // required methods + $export->initiateQuery( + ['tagada', 'tsointsoin'], + Argument::that(function ($arg) use ($centerB, $centerA) { + if (!is_array($arg)) { + return false; + } + if (2 !== count($arg)) { + return false; + } + + foreach ($arg as $item) { + if ([] !== $item['circles']) { + return false; + } + if (!in_array($item['center'], [$centerA, $centerB], true)) { + return false; + } + } + + return true; + }), + $formExportData, + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->requiredRole()->willReturn('dummy_role'); + + $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + ['dummy_filter' => $formFilterData], + ['dummy_aggregator' => $formAggregatorData], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal()); + $exportManager->hasFilter('dummy_filter')->willReturn(true); + $exportManager->hasFilter('disabled_filter')->willReturn(true); + $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal()); + $exportManager->hasAggregator('dummy_aggregator')->willReturn(true); + $exportManager->hasAggregator('disabled_aggregator')->willReturn(true); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } + + public function testGenerateNativeSqlHappyScenario() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [], + 'aggregators' => [], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []], + ]; + $user = new User(); + + $export = $this->prophesize(ExportInterface::class); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(NativeQuery::class); + + // required methods + $export->initiateQuery( + [], + [['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]], + ['key' => 'form1'], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->supportsModifiers()->willReturn([]); + $export->requiredRole()->willReturn('dummy_role'); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + [], + [], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertInstanceOf(FormattedExportGeneration::class, $actual); + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } + + public function testGenerateDirectExportHappyScenario() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [], + 'aggregators' => [], + 'pick_formatter' => 'xlsx', + 'formatter' => ['form' => $formatterData = ['key' => 'form4']], + 'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []], + ]; + $user = new User(); + + $export = $this->prophesize(DirectExportInterface::class); + + // required methods + $export->generate( + [['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]], + ['key' => 'form1'], + Argument::that(static fn (ExportGenerationContext $context) => $user === $context->byUser), + )->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + $export->requiredRole()->willReturn('dummy_role'); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertInstanceOf(FormattedExportGeneration::class, $actual); + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } + + public function testGenerateHappyScenarioWithoutCenterFiltering() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [ + 'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']], + 'disabled_filter' => ['enabled' => false], + ], + 'aggregators' => [ + 'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']], + 'disabled_aggregator' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [], 'regroupments' => []], + ]; + $user = new User(); + $centerA = new Center(); + $centerB = new Center(); + + $export = $this->prophesize(ExportInterface::class); + $filter = $this->prophesize(FilterInterface::class); + $filter->applyOn()->willReturn('tagada'); + $aggregator = $this->prophesize(AggregatorInterface::class); + $aggregator->applyOn()->willReturn('tsointsoin'); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(QueryBuilder::class); + $query->getDQL()->willReturn('dummy'); + $dqlQuery = $this->prophesize(Query::class); + $dqlQuery->getSQL()->willReturn('dummy'); + $query->getQuery()->willReturn($dqlQuery->reveal()); + + // required methods + $export->initiateQuery( + ['tagada', 'tsointsoin'], + Argument::that(function ($arg) use ($centerB, $centerA) { + if (!is_array($arg)) { + return false; + } + if (2 !== count($arg)) { + return false; + } + + foreach ($arg as $item) { + if ([] !== $item['circles']) { + return false; + } + if (!in_array($item['center'], [$centerA, $centerB], true)) { + return false; + } + } + + return true; + }), + ['key' => 'form1'], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->requiredRole()->willReturn('dummy_role'); + + $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + ['dummy_filter' => $formFilterData], + ['dummy_aggregator' => $formAggregatorData], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal()); + $exportManager->hasFilter('dummy_filter')->willReturn(true); + $exportManager->hasFilter('disabled_filter')->willReturn(true); + $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal()); + $exportManager->hasAggregator('dummy_aggregator')->willReturn(true); + $exportManager->hasAggregator('disabled_aggregator')->willReturn(true); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->shouldNotBeCalled(); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->willReturn([$centerA, $centerB])->shouldBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(false), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php index e2d0e1393..6004a6012 100644 --- a/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php @@ -14,10 +14,10 @@ namespace Chill\MainBundle\Tests\Export; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\AggregatorInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Test\PrepareCenterTrait; @@ -30,12 +30,10 @@ use Prophecy\Prophet; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Contracts\Translation\TranslatorInterface; /** * Test the export manager. @@ -194,176 +192,6 @@ final class ExportManagerTest extends KernelTestCase $this->assertNotContains($formatterBar->reveal(), $obtained); } - /** - * Test the generation of an export. - */ - public function testGenerate() - { - $center = $this->prepareCenter(100, 'center'); - $user = $this->prepareUser([]); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - $exports = []; - $filters = []; - $aggregators = []; - - $em = self::getContainer()->get(EntityManagerInterface::class); - - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $export->initiateQuery( - Argument::is(['foo']), - Argument::Type('array'), - Argument::is(['a' => 'b']) - ) - ->will(static function () use ($em) { - $qb = $em->createQueryBuilder(); - - return $qb->addSelect('COUNT(user.id) as export') - ->from(User::class, 'user'); - }); - $export->initiateQuery( - Argument::is(['foo']), - Argument::Type('array'), - Argument::is(['a' => 'b']) - )->shouldBeCalled(); - $export->supportsModifiers()->willReturn(['foo']); - $export->requiredRole()->willReturn('CHILL_STAT_DUMMY'); - $export->getResult(Argument::Type(QueryBuilder::class), Argument::Type('array'))->willReturn([ - [ - 'aggregator' => 'cat a', - 'export' => 0, - ], - [ - 'aggregator' => 'cat b', - 'export' => 1, - ], - ]); - $export->getLabels( - Argument::is('export'), - Argument::is([0, 1]), - Argument::Type('array') - ) - ->willReturn(static function ($value) { - switch ($value) { - case 0: - case 1: - return $value; - - case '_header': - return 'export'; - - default: throw new \RuntimeException(sprintf('The value %s is not valid', $value)); - } - }); - - $export->getQueryKeys(Argument::Type('array'))->willReturn(['export']); - $export->getTitle()->willReturn('dummy title'); - $exports['dummy'] = $export->reveal(); - - $filter = $this->prophet->prophesize(); - $filter->willImplement(FilterInterface::class); - $filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->willReturn(null); - $filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->shouldBeCalled(); - $filter->addRole()->shouldBeCalled(); - $filter->addRole()->willReturn(null); - $filter->applyOn()->willReturn('foo'); - $filter->describeAction(Argument::cetera())->willReturn('filtered string'); - $filters['filter_foo'] = $filter->reveal(); - - $aggregator = $this->prophet->prophesize(); - $aggregator->willImplement(AggregatorInterface::class); - $aggregator->addRole()->willReturn(null); - $aggregator->applyOn()->willReturn('foo'); - $aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->willReturn(null); - $aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->shouldBeCalled(); - $aggregator->getQueryKeys(Argument::Type('array'))->willReturn(['aggregator']); - $aggregator->getLabels( - Argument::is('aggregator'), - Argument::is(['cat a', 'cat b']), - Argument::is([]) - ) - ->willReturn(static fn ($value) => match ($value) { - '_header' => 'foo_header', - 'cat a' => 'label cat a', - 'cat b' => 'label cat b', - default => throw new \RuntimeException(sprintf('This value (%s) is not valid', $value)), - }); - $aggregator->addRole()->willReturn(null); - $aggregator->addRole()->shouldBeCalled(); - $aggregators['aggregator_foo'] = $aggregator->reveal(); - - $exportManager = $this->createExportManager( - null, - null, - $authorizationChecker->reveal(), - null, - $user, - $exports, - $aggregators, - $filters - ); - - // add formatter interface - $formatter = new \Chill\MainBundle\Export\Formatter\SpreadSheetFormatter( - self::getContainer()->get(TranslatorInterface::class), - $exportManager - ); - - $exportManager->addFormatter($formatter, 'spreadsheet'); - - $response = $exportManager->generate( - 'dummy', - [$center], - [ - ExportType::FILTER_KEY => [ - 'filter_foo' => [ - 'enabled' => true, - 'form' => [], - ], - ], - ExportType::AGGREGATOR_KEY => [ - 'aggregator_foo' => [ - 'enabled' => true, - 'form' => [], - ], - ], - ExportType::PICK_FORMATTER_KEY => [ - 'alias' => 'spreadsheet', - ], - ExportType::EXPORT_KEY => [ - 'a' => 'b', - ], - ], - [ - 'format' => 'csv', - 'aggregator_foo' => [ - 'order' => 1, - ], - ] - ); - - $this->assertInstanceOf(Response::class, $response); - $expected = <<<'EOT' - "dummy title","" - "","" - "filtered string","" - "foo_header","export" - "label cat a","0" - "label cat b","1" - - EOT; - - $this->assertEquals($expected, $response->getContent()); - } - public function testIsGrantedForElementWithExportAndUserIsGranted() { $center = $this->prepareCenter(100, 'center A'); @@ -506,6 +334,7 @@ final class ExportManagerTest extends KernelTestCase array $exports = [], array $aggregators = [], array $filters = [], + array $formatters = [], ): ExportManager { $localUser = $user ?? self::getContainer()->get( UserRepositoryInterface::class @@ -516,13 +345,14 @@ final class ExportManagerTest extends KernelTestCase $tokenStorage->setToken($token); return new ExportManager( - $logger ?? self::getContainer()->get('logger'), + $logger ?? self::getContainer()->get(LoggerInterface::class), $authorizationChecker ?? self::getContainer()->get('security.authorization_checker'), $authorizationHelper ?? self::getContainer()->get('chill.main.security.authorization.helper'), $tokenStorage, $exports, $aggregators, - $filters + $filters, + $formatters, ); } } @@ -534,19 +364,34 @@ class DummyFilterWithApplying implements FilterInterface private readonly string $applyOn, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'dummy'; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['dummy filter', []]; } @@ -556,9 +401,9 @@ class DummyFilterWithApplying implements FilterInterface return $this->role; } - public function alterQuery(QueryBuilder $qb, $data) {} + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {} - public function applyOn() + public function applyOn(): string { return $this->applyOn; } @@ -574,13 +419,28 @@ class DummyExport implements ExportInterface private readonly array $supportedModifiers, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'dummy'; } public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -601,24 +461,24 @@ class DummyExport implements ExportInterface return []; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return []; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return []; } - public function getType() + public function getType(): string { return 'dummy'; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { - return null; + throw new \RuntimeException('not implemented'); } public function requiredRole(): string diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php new file mode 100644 index 000000000..bf1da2701 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php @@ -0,0 +1,138 @@ +prophesize(\Symfony\Contracts\Translation\TranslatorInterface::class); + $translator->getLocale()->willReturn('en'); + $exportManager = $this->prophesize(ExportManager::class); + + $result = + [ + ['export_count_activity' => 1, 'person_age' => 65, 'aggregator_some' => 'label0'], // row 0 + ]; + $exportAlias = 'count_activity_linked_to_person'; + $formatterData = + ['format' => 'xlsx', 'person_age_aggregator' => ['order' => 1], 'aggregator2' => ['order' => 2]]; + $exportData = []; + $filtersData = + [ + 'person_age_filter' => ['min_age' => 18, 'max_age' => 120, 'date_calc' => new RollingDate(RollingDate::T_TODAY)], + 'filter2' => [], + ]; + $aggregatorsData = + [ + 'person_age_aggregator' => ['date_age_calculation' => new RollingDate(RollingDate::T_TODAY)], + 'aggregator2' => [], + ]; + $context = + new ExportGenerationContext($user = new User()); + + $export = $this->prophesize(ExportInterface::class); + $export->getTitle()->willReturn('Count activity linked to person'); + $translator->trans('Count activity linked to person')->willReturn('Count activity linked to person'); + $export->getQueryKeys($exportData)->willReturn(['export_count_activity']); + $export->getLabels('export_count_activity', [1], $exportData) + ->willReturn(fn (int|string $value): int|string => '_header' === $value ? 'Count activities' : $value); + $translator->trans('Count activities')->willReturn('Count activities'); + $exportManager->getExport($exportAlias)->willReturn($export->reveal()); + + $aggregator = $this->prophesize(\Chill\MainBundle\Export\AggregatorInterface::class); + $aggregator->getTitle()->willReturn('Person age'); + $aggregator->getQueryKeys($aggregatorsData['person_age_aggregator'])->willReturn(['person_age']); + $aggregator->getLabels('person_age', [65], $aggregatorsData['person_age_aggregator']) + ->willReturn(fn (int|string $value): int|string => '_header' === $value ? 'Group by age' : $value); + $translator->trans('Group by age')->willReturn('Group by age'); + $exportManager->getAggregator('person_age_aggregator')->willReturn($aggregator->reveal()); + + $aggregator2 = $this->prophesize(\Chill\MainBundle\Export\AggregatorInterface::class); + $aggregator2->getTitle()->willReturn(new TranslatableMessage('Some')); + $aggregator2->getQueryKeys($aggregatorsData['aggregator2'])->willReturn(['aggregator_some']); + $aggregator2->getLabels('aggregator_some', ['label0'], $aggregatorsData['aggregator2']) + ->willReturn(fn (int|string $value): TranslatableMessage => new TranslatableMessage('_header' === $value ? 'Aggregator 2 header' : $value)); + $translator->trans('Aggregator 2 header', [], null, 'en')->willReturn('Aggregator 2 header'); + $translator->trans('label0', [], null, 'en')->willReturn('label0'); + $exportManager->getAggregator('aggregator2')->willReturn($aggregator2->reveal()); + + $filter = $this->prophesize(\Chill\MainBundle\Export\FilterInterface::class); + $filter->getTitle()->willReturn('Person by age'); + $filter->describeAction($filtersData['person_age_filter'], $context) + ->willReturn(['Filter by age, from {{ start }} to {{ end }}', ['{{ start }}' => '18', '{{ end }}' => '120']]); + $translator->trans('Filter by age, from {{ start }} to {{ end }}', ['{{ start }}' => '18', '{{ end }}' => '120']) + ->willReturn('Filter by age, from 18 to 120'); + $exportManager->getFilter('person_age_filter')->willReturn($filter->reveal()); + + $filter2 = $this->prophesize(\Chill\MainBundle\Export\FilterInterface::class); + $filter2->getTitle()->willReturn(new TranslatableMessage('Some other filter')); + $filter2->describeAction($filtersData['filter2'], $context) + ->willReturn(new TranslatableMessage('Other filter description')); + $translator->trans('Other filter description', [], null, 'en') + ->willReturn('Some other filter description'); + $exportManager->getFilter('filter2')->willReturn($filter2->reveal()); + + + // create the formatter + $formatter = new SpreadSheetFormatter($translator->reveal()); + $formatter->setExportManager($exportManager->reveal()); + + $result = $formatter->generate( + $result, + $formatterData, + $exportAlias, + $exportData, + $filtersData, + $aggregatorsData, + $context, + ); + + $tempFile = tempnam(sys_get_temp_dir(), 'test_spreadsheet_formatter_'); + file_put_contents($tempFile, $result->content); + $spreadsheet = IOFactory::load($tempFile); + $cells = $spreadsheet->getActiveSheet()->rangeToArray( + 'A1:G6', + null, + false, + true, + true, + ); + unlink($tempFile); + + self::assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $result->contentType); + self::assertEquals($cells[1], ['A' => 'Count activity linked to perso…', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[2], ['A' => null, 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[3], ['A' => 'Filter by age, from 18 to 120', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[4], ['A' => 'Some other filter description', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[5], ['A' => 'Group by age', 'B' => 'Aggregator 2 header', 'C' => 'Count activities', 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[6], ['A' => 65, 'B' => 'label0', 'C' => 1, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php new file mode 100644 index 000000000..ef19ccaaa --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php @@ -0,0 +1,62 @@ +setCreatedAt(new \DateTimeImmutable('10 seconds ago')); + + $repository = $this->prophesize(ExportGenerationRepository::class); + $repository->find($exportGeneration->getId())->willReturn($exportGeneration); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->flush()->shouldBeCalled(); + + $user = $this->prophesize(User::class); + $user->getId()->willReturn(1); + + $subscriber = new OnExportGenerationFails(new NullLogger(), $repository->reveal(), $entityManager->reveal()); + + $subscriber->onMessageFailed(new WorkerMessageFailedEvent( + new Envelope(new ExportRequestGenerationMessage($exportGeneration, $user->reveal())), + 'dummyReceiver', + new ExportGenerationException('dummy_exception'), + )); + + self::assertEquals(StoredObject::STATUS_FAILURE, $exportGeneration->getStoredObject()->getStatus()); + self::assertStringContainsString('dummy_exception', $exportGeneration->getStoredObject()->getGenerationErrors()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php new file mode 100644 index 000000000..70f6f9f75 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php @@ -0,0 +1,76 @@ + 'bar']); + $storedObject = $exportGeneration->getStoredObject(); + + // 3. Mock ExportGenerationRepository to return the ExportGeneration + $exportGenerationRepository = $this->prophesize(ExportGenerationRepository::class); + $exportGenerationRepository + ->find($exportGeneration->getId()) + ->willReturn($exportGeneration); + + // 4. Mock EntityManagerInterface and set expectations + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->remove($exportGeneration)->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + + // 6. Create message + $message = new RemoveExportGenerationMessage($exportGeneration); + + // 7. Handler instantiation + $handler = new RemoveExportGenerationMessageHandler( + $exportGenerationRepository->reveal(), + $entityManager->reveal(), + new NullLogger(), + $clock + ); + + // Pre-condition: deleteAt not set. + $this->assertNull($storedObject->getDeleteAt()); + + // Act + $handler->__invoke($message); + + // Assert + $this->assertEquals($now, $storedObject->getDeleteAt(), 'deleteAt of stored object was updated'); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php new file mode 100644 index 000000000..6cfa21faa --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php @@ -0,0 +1,586 @@ +getTitle() instanceof TranslatableInterface) { + continue; + } + if (null === $previousName) { $previousName = $translator->trans($filter->getTitle()); continue; @@ -119,24 +125,39 @@ class SortExportElementTest extends KernelTestCase return new class ($title) implements AggregatorInterface { public function __construct(private readonly string $title) {} - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return fn ($v) => $v; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return []; } - public function getTitle() + public function getTitle(): string|TranslatableInterface { return $this->title; } @@ -146,11 +167,11 @@ class SortExportElementTest extends KernelTestCase return null; } - public function alterQuery(QueryBuilder $qb, $data) {} + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {} - public function applyOn() + public function applyOn(): string { - return []; + return ''; } }; } @@ -160,19 +181,34 @@ class SortExportElementTest extends KernelTestCase return new class ($title) implements FilterInterface { public function __construct(private readonly string $title) {} - public function getTitle() + public function getTitle(): string|TranslatableInterface { return $this->title; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|TranslatableInterface|array { return ['a', []]; } @@ -182,11 +218,11 @@ class SortExportElementTest extends KernelTestCase return null; } - public function alterQuery(QueryBuilder $qb, $data) {} + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {} - public function applyOn() + public function applyOn(): string { - return []; + return ''; } }; } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php new file mode 100644 index 000000000..f2eefa055 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php @@ -0,0 +1,145 @@ +prophesize(ExportInterface::class); + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy_export')->willReturn($export->reveal()); + $exportManager->isGrantedForElement(Argument::any())->willReturn($isGranted); + + $accessDecisionManager = $this->prophesize(AccessDecisionManagerInterface::class); + + $voter = new SavedExportVoter($exportManager->reveal(), $accessDecisionManager->reveal()); + $token = new UsernamePasswordToken($user, 'default', ['ROLE_USER']); + + self::assertEquals($expectedResult, $voter->vote($token, $savedExport, [$attribute])); + } + + public static function voteProvider(): iterable + { + $alls = [SavedExportVoter::GENERATE, SavedExportVoter::GENERATE, SavedExportVoter::EDIT, SavedExportVoter::DELETE]; + $userA = new User(); + $userB = new User(); + $userC = new User(); + $group = new UserGroup(); + $group->addUser($userC); + + $savedExport = new SavedExport(); + $savedExport->setExportAlias('dummy_export'); + $savedExport->setUser($userA); + + // abstain + foreach ($alls as $attribute) { + yield [ + $attribute, + new \stdClass(), + $userA, + VoterInterface::ACCESS_ABSTAIN, + true, + ]; + } + + yield [ + 'dummy', + $savedExport, + $userA, + VoterInterface::ACCESS_ABSTAIN, + false, + ]; + + foreach ($alls as $attribute) { + yield [ + $attribute, + $savedExport, + $userA, + VoterInterface::ACCESS_GRANTED, + true, + ]; + } + + yield [ + SavedExportVoter::GENERATE, + $savedExport, + $userA, + VoterInterface::ACCESS_DENIED, + false, + ]; + + foreach ($alls as $attribute) { + yield [ + $attribute, + $savedExport, + $userB, + VoterInterface::ACCESS_DENIED, + true, + ]; + } + + $savedExport = new SavedExport(); + $savedExport->setExportAlias('dummy_export'); + $savedExport->setUser($userA); + $savedExport->addShare($userB); + + yield [ + SavedExportVoter::GENERATE, + $savedExport, + $userB, + VoterInterface::ACCESS_DENIED, + false, + ]; + + yield [ + SavedExportVoter::GENERATE, + $savedExport, + $userB, + VoterInterface::ACCESS_GRANTED, + true, + ]; + + foreach ([SavedExportVoter::EDIT, SavedExportVoter::DELETE] as $attribute) { + yield [ + $attribute, + $savedExport, + $userB, + VoterInterface::ACCESS_DENIED, + true, + ]; + } + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php new file mode 100644 index 000000000..4f7c2431f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php @@ -0,0 +1,78 @@ +resolveCenters($groups, $centers); + + self::assertEquals(count($expected), count($actual)); + + foreach ($expected as $center) { + self::assertContains($center, $actual); + } + } + + public static function provideData(): iterable + { + $centerA = new Center(); + $centerB = new Center(); + $centerC = new Center(); + $centerD = new Center(); + + $groupA = new Regroupment(); + $groupA->addCenter($centerA)->addCenter($centerB); + + $groupB = new Regroupment(); + $groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC); + + yield [ + [$groupA], + [], + [$centerA, $centerB], + ]; + + yield [ + [$groupA, $groupB], + [], + [$centerA, $centerB, $centerC], + ]; + + yield [ + [$groupA, $groupB], + [$centerB, $centerD], + [$centerA, $centerB, $centerC, $centerD], + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php new file mode 100644 index 000000000..f6976054f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php @@ -0,0 +1,96 @@ +filterContainsAtLeastOneCenter($groups, $centers); + + self::assertEquals(count($expected), count($actual)); + self::assertTrue(array_is_list($actual)); + + foreach ($expected as $center) { + self::assertContains($center, $actual); + } + } + + public static function provideDataForFilterContainsAtLeastOnCenter(): iterable + { + + $centerA = new Center(); + $centerB = new Center(); + $centerC = new Center(); + $centerD = new Center(); + + $groupA = new Regroupment(); + $groupA->addCenter($centerA)->addCenter($centerB); + + $groupB = new Regroupment(); + $groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC); + + $groupC = new Regroupment(); + $groupC->addCenter($centerA)->addCenter($centerD); + + yield [ + [$groupA, $groupB], + [], + [], + ]; + + yield [ + [$groupA, $groupB], + [$centerA, $centerB, $centerC], + [$groupA, $groupB], + ]; + + yield [ + [$groupA, $groupC], + [$centerD], + [$groupC], + ]; + + yield [ + [$groupA], + [$centerB, $centerD], + [$groupA], + ]; + + yield [ + [$groupA], + [new Center()], + [], + ]; + + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php index bb255f5e7..3031a4d80 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php @@ -14,6 +14,7 @@ namespace Services\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Clock\MockClock; /** * @internal @@ -22,11 +23,9 @@ use PHPUnit\Framework\TestCase; */ final class RollingDateConverterTest extends TestCase { - private RollingDateConverter $converter; - - protected function setUp(): void + private function buildConverter(\DateTimeImmutable|string $pivot = 'now'): RollingDateConverter { - $this->converter = new RollingDateConverter(); + return new RollingDateConverter(new MockClock($pivot)); } public function testConversionFixedDate() @@ -35,7 +34,7 @@ final class RollingDateConverterTest extends TestCase $this->assertEquals( '2022-01-01', - $this->converter->convert($rollingDate)->format('Y-m-d') + $this->buildConverter()->convert($rollingDate)->format('Y-m-d') ); } @@ -43,7 +42,7 @@ final class RollingDateConverterTest extends TestCase { $rollingDate = new RollingDate(RollingDate::T_YEAR_PREVIOUS_START); - $actual = $this->converter->convert($rollingDate); + $actual = $this->buildConverter()->convert($rollingDate); $this->assertEquals( (int) (new \DateTimeImmutable('now'))->format('Y') - 1, @@ -63,7 +62,21 @@ final class RollingDateConverterTest extends TestCase $this->assertEquals( \DateTime::createFromFormat($format, $expectedDateTime), - $this->converter->convert($rollingDate) + $this->buildConverter()->convert($rollingDate) + ); + } + + /** + * @dataProvider generateDataConversionDate + */ + public function testConvertOnClock(string $roll, string $expectedDateTime, string $format) + { + $pivot = \DateTimeImmutable::createFromFormat('Y-m-d His', '2022-11-07 000000'); + $rollingDate = new RollingDate($roll, null); + + $this->assertEquals( + \DateTime::createFromFormat($format, $expectedDateTime), + $this->buildConverter($pivot)->convert($rollingDate) ); } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php new file mode 100644 index 000000000..a700fc642 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php @@ -0,0 +1,45 @@ +normalize()); + + self::assertEquals(RollingDate::T_YEAR_PREVIOUS_START, $actual->getRoll()); + self::assertNull($actual->getFixedDate()); + self::assertEquals($date->getPivotDate()?->getTimestamp(), $actual->getPivotDate()?->getTimestamp()); + } + + public function testNormalizationDenormalizationProcessWithPivotDate(): void + { + $date = new RollingDate(RollingDate::T_FIXED_DATE, $fixed = new \DateTimeImmutable('now')); + + $actual = RollingDate::fromNormalized($date->normalize()); + + self::assertEquals(RollingDate::T_FIXED_DATE, $actual->getRoll()); + self::assertEquals($fixed, $actual->getFixedDate()); + self::assertEquals($date->getPivotDate()?->getTimestamp(), $actual->getPivotDate()?->getTimestamp()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php new file mode 100644 index 000000000..1228aa30f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php @@ -0,0 +1,58 @@ +prophesize(UserGroupRelatedToUserJobSyncInterface::class); + + $cronJob = new UserGroupRelatedToUserJobSyncCronJob($clock, $job->reveal()); + + if (null !== $lastStartExecution) { + $lastExecution = new CronJobExecution('user-group-related-to-user-job-sync'); + $lastExecution->setLastStart($lastStartExecution); + } + + $actual = $cronJob->canRun($lastExecution ?? null); + + self::assertEquals($exected, $actual); + } + + public static function canRunDataProvider(): iterable + { + $now = new \DateTimeImmutable('2025-04-27T00:00:00Z'); + + yield 'never executed' => [$now, null, true]; + yield 'executed 12 hours ago' => [$now, new \DateTimeImmutable('2025-04-26T12:00:00Z'), false]; + yield 'executed more than 12 hours ago' => [$now, new \DateTimeImmutable('2025-04-25T12:00:00Z'), true]; + } +} diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml index 8d204803f..d87a0eb71 100644 --- a/src/Bundle/ChillMainBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -1111,3 +1111,47 @@ paths: 204: description: "resource was deleted successfully" + /1.0/main/export-generation/{id}/object: + get: + tags: + - export + summary: get the object status and details of an export-generation + parameters: + - name: id + in: path + required: true + description: The entity export generation id + schema: + type: string + format: uuid + responses: + 403: + description: Access denied + 200: + description: "ok" + content: + application/json: + schema: + type: object + /1.0/main/export/export-generation/create-from-saved-export/{id}: + post: + tags: + - export + summary: Create an export generation from an existing saved export + parameters: + - name: id + in: path + required: true + description: The entity saved export's id + schema: + type: string + format: uuid + responses: + 403: + description: Access denied + 200: + description: "ok" + content: + application/json: + schema: + type: object diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 6cc890797..011fc9559 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -34,7 +34,7 @@ module.exports = function (encore, entries) { ); encore.addEntry( "page_download_exports", - __dirname + "/Resources/public/page/export/download-export.js", + __dirname + "/Resources/public/vuejs/DownloadExport/index.ts", ); // Modules entrypoints @@ -106,6 +106,10 @@ module.exports = function (encore, entries) { "mod_workflow_attachment", __dirname + "/Resources/public/vuejs/WorkflowAttachment/index", ); + encore.addEntry( + "mod_saved_export_button", + __dirname + "/Resources/public/vuejs/SavedExportButtons/index.ts", + ); // Vue entrypoints encore.addEntry( @@ -116,4 +120,5 @@ module.exports = function (encore, entries) { "vue_onthefly", __dirname + "/Resources/public/vuejs/OnTheFly/index.js", ); + }; diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index a9829f99d..14f0f851a 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -89,10 +89,7 @@ services: $exports: !tagged_iterator { tag: chill.export, index_by: alias } $aggregators: !tagged_iterator { tag: chill.export_aggregator, index_by: alias } $filters: !tagged_iterator { tag: chill.export_filter, index_by: alias } - # for an unknown reason, iterator_to_array($formatter) cause a segmentation fault error (php-fpm code 11). removed temporarily - # $formatters: !tagged_iterator { tag: chill.export_formatter, index_by: alias } - # remove until we can properly test it - # $exportElementProvider: !tagged_iterator { tag: chill.export_elements_provider, index_by: prefix } + $formatters: !tagged_iterator { tag: chill.export_formatter, index_by: alias } Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher' diff --git a/src/Bundle/ChillMainBundle/config/services/export.yaml b/src/Bundle/ChillMainBundle/config/services/export.yaml index ece7ae902..72d862bf7 100644 --- a/src/Bundle/ChillMainBundle/config/services/export.yaml +++ b/src/Bundle/ChillMainBundle/config/services/export.yaml @@ -6,8 +6,25 @@ services: Chill\MainBundle\Export\Helper\: resource: '../../Export/Helper' + Chill\MainBundle\Export\Cronjob\: + resource: '../../Export/Cronjob' + + Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessageHandler: ~ + + Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessageHandler: ~ + + Chill\MainBundle\Export\Messenger\OnExportGenerationFails: ~ + Chill\MainBundle\Export\ExportFormHelper: ~ + Chill\MainBundle\Export\ExportGenerator: ~ + + Chill\MainBundle\Export\ExportConfigNormalizer: ~ + + Chill\MainBundle\Export\ExportConfigProcessor: ~ + + Chill\MainBundle\Export\ExportDescriptionHelper: ~ + chill.main.export_element_validator: class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator tags: @@ -23,36 +40,14 @@ services: chill.main.export.spreadsheet_formatter: class: Chill\MainBundle\Export\Formatter\SpreadSheetFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'spreadsheet' } - chill.main.export.list_formatter: - class: Chill\MainBundle\Export\Formatter\CSVListFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' - tags: - - { name: chill.export_formatter, alias: 'csvlist' } - chill.main.export.list_spreadsheet_formatter: class: Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'spreadlist' } - chill.main.export.pivoted_list_formatter: - class: Chill\MainBundle\Export\Formatter\CSVPivotedListFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' - tags: - - { name: chill.export_formatter, alias: 'csv_pivoted_list' } - Chill\MainBundle\Export\AccompanyingCourseExportHelper: ~ Chill\MainBundle\Export\SortExportElement: ~ diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index f6b50cb57..f31829915 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -146,3 +146,5 @@ services: Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer: ~ Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer: ~ Chill\MainBundle\Form\DataTransformer\IdToUsersDataTransformer: ~ + + Chill\MainBundle\Form\SavedExportType: ~ diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250219130532.php b/src/Bundle/ChillMainBundle/migrations/Version20250219130532.php new file mode 100644 index 000000000..140254acc --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250219130532.php @@ -0,0 +1,40 @@ +addSql('CREATE TABLE chill_main_export_generation (id UUID NOT NULL, deleteAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, exportAlias TEXT DEFAULT \'\' NOT NULL, options JSON DEFAULT \'[]\' NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, storedObject_id INT NOT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_E644B77D6C99C13A ON chill_main_export_generation (storedObject_id)'); + $this->addSql('CREATE INDEX IDX_E644B77D3174800F ON chill_main_export_generation (createdBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.id IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.deleteAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77D6C99C13A FOREIGN KEY (storedObject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77D3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE chill_main_export_generation'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250313165611.php b/src/Bundle/ChillMainBundle/migrations/Version20250313165611.php new file mode 100644 index 000000000..159f12b66 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250313165611.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE chill_main_export_generation ADD savedExport_id UUID DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.savedExport_id IS \'(DC2Type:uuid)\''); + $this->addSql('ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77DA61D6F69 FOREIGN KEY (savedExport_id) REFERENCES chill_main_saved_export (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_E644B77DA61D6F69 ON chill_main_export_generation (savedExport_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_export_generation DROP CONSTRAINT FK_E644B77DA61D6F69'); + $this->addSql('DROP INDEX IDX_E644B77DA61D6F69'); + $this->addSql('ALTER TABLE chill_main_export_generation DROP savedExport_id'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250404123326.php b/src/Bundle/ChillMainBundle/migrations/Version20250404123326.php new file mode 100644 index 000000000..84330385e --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250404123326.php @@ -0,0 +1,49 @@ +addSql('ALTER TABLE chill_main_saved_export ADD COLUMN options_backup JSONB default \'[]\''); + $this->addSql('UPDATE chill_main_saved_export SET options_backup = options'); + + $result = $this->connection->executeQuery('SELECT id, options FROM chill_main_saved_export'); + + foreach ($result->iterateAssociative() as $row) { + $options = json_decode((string) $row['options'], true, 512, JSON_THROW_ON_ERROR); + $this->addSql( + 'UPDATE chill_main_saved_export SET options = :new_options WHERE id = :id', + ['id' => $row['id'], 'new_options' => SavedExportOptionsMigrator::migrate($options)], + ['id' => Types::STRING, 'new_options' => Types::JSON], + ); + } + + } + + public function down(Schema $schema): void + { + $this->addSql('UPDATE chill_main_saved_export SET options = options_backup'); + $this->addSql('ALTER TABLE chill_main_saved_export DROP COLUMN options_backup'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250410145342.php b/src/Bundle/ChillMainBundle/migrations/Version20250410145342.php new file mode 100644 index 000000000..21f4c6d0a --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250410145342.php @@ -0,0 +1,49 @@ +addSql('CREATE TABLE chill_main_saved_export_users (savedexport_id UUID NOT NULL, user_id INT NOT NULL, PRIMARY KEY(savedexport_id, user_id))'); + $this->addSql('CREATE INDEX IDX_4A2B71EC24ECEDCA ON chill_main_saved_export_users (savedexport_id)'); + $this->addSql('CREATE INDEX IDX_4A2B71ECA76ED395 ON chill_main_saved_export_users (user_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_saved_export_users.savedexport_id IS \'(DC2Type:uuid)\''); + $this->addSql('CREATE TABLE chill_main_saved_export_usergroups (savedexport_id UUID NOT NULL, usergroup_id INT NOT NULL, PRIMARY KEY(savedexport_id, usergroup_id))'); + $this->addSql('CREATE INDEX IDX_A12F30824ECEDCA ON chill_main_saved_export_usergroups (savedexport_id)'); + $this->addSql('CREATE INDEX IDX_A12F308D2112630 ON chill_main_saved_export_usergroups (usergroup_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_saved_export_usergroups.savedexport_id IS \'(DC2Type:uuid)\''); + $this->addSql('ALTER TABLE chill_main_saved_export_users ADD CONSTRAINT FK_4A2B71EC24ECEDCA FOREIGN KEY (savedexport_id) REFERENCES chill_main_saved_export (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_saved_export_users ADD CONSTRAINT FK_4A2B71ECA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups ADD CONSTRAINT FK_A12F30824ECEDCA FOREIGN KEY (savedexport_id) REFERENCES chill_main_saved_export (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups ADD CONSTRAINT FK_A12F308D2112630 FOREIGN KEY (usergroup_id) REFERENCES chill_main_user_group (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_saved_export_users DROP CONSTRAINT FK_4A2B71EC24ECEDCA'); + $this->addSql('ALTER TABLE chill_main_saved_export_users DROP CONSTRAINT FK_4A2B71ECA76ED395'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups DROP CONSTRAINT FK_A12F30824ECEDCA'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups DROP CONSTRAINT FK_A12F308D2112630'); + $this->addSql('DROP TABLE chill_main_saved_export_users'); + $this->addSql('DROP TABLE chill_main_saved_export_usergroups'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php b/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php new file mode 100644 index 000000000..2ba6e2d82 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php @@ -0,0 +1,67 @@ +addSql( + <<<'SQL' + CREATE TEMPORARY TABLE to_create AS ( + SELECT DISTINCT permissionsgroup_rolescope.permissionsgroup_id, 'CHILL_MAIN_COMPOSE_EXPORT' AS role + FROM permissionsgroup_rolescope + JOIN public.role_scopes rs on permissionsgroup_rolescope.rolescope_id = rs.id + WHERE role LIKE '%STATS%' or role LIKE '%LIST%' + ) + SQL + ); + + $this->addSql( + <<<'SQL' + INSERT INTO role_scopes(id, scope_id, role) + SELECT nextval('role_scopes_id_seq'), null, 'CHILL_MAIN_COMPOSE_EXPORT' + WHERE NOT EXISTS (SELECT 1 FROM role_scopes s WHERE role like 'CHILL_MAIN_COMPOSE_EXPORT') + SQL + ); + + $this->addSql('ALTER TABLE to_create ADD COLUMN rolescope_id INT'); + + $this->addSql( + <<<'SQL' + UPDATE to_create SET rolescope_id = ( + SELECT id FROM role_scopes + WHERE to_create.role = role_scopes.role) + SQL + ); + + $this->addSql( + <<<'SQL' + INSERT INTO permissionsgroup_rolescope (permissionsgroup_id, rolescope_id) + SELECT to_create.permissionsgroup_id, to_create.rolescope_id FROM to_create + SQL + ); + } + + public function down(Schema $schema): void + { + $this->throwIrreversibleMigrationException(); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250425093948.php b/src/Bundle/ChillMainBundle/migrations/Version20250425093948.php new file mode 100644 index 000000000..ebd0f89b3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250425093948.php @@ -0,0 +1,49 @@ +addSql(<<<'SQL' + ALTER TABLE chill_main_user_group ADD userJob_id INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_user_group ADD CONSTRAINT FK_6576E74D64B65C5B FOREIGN KEY (userJob_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6576E74D64B65C5B ON chill_main_user_group (userJob_id) + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_user_group DROP CONSTRAINT FK_6576E74D64B65C5B + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_6576E74D64B65C5B + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_user_group DROP userJob_id + SQL); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250617141354.php b/src/Bundle/ChillMainBundle/migrations/Version20250617141354.php new file mode 100644 index 000000000..69c2076b3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250617141354.php @@ -0,0 +1,43 @@ +addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation DROP CONSTRAINT FK_E644B77DA61D6F69 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77DA61D6F69 FOREIGN KEY (savedExport_id) REFERENCES chill_main_saved_export (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation DROP CONSTRAINT fk_e644b77da61d6f69 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation ADD CONSTRAINT fk_e644b77da61d6f69 FOREIGN KEY (savedexport_id) REFERENCES chill_main_saved_export (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index e86484114..2982d94db 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -15,6 +15,7 @@ user_group: } user_removed: L'utilisateur {user} est enlevé du groupe {user_group} avec succès user_added: L'utilisateur {user} est ajouté groupe {user_group} avec succès + label_related_to_user_job: Groupe {job} (Groupe métier) notification: My notifications with counter: >- diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index c883cb91c..d3498cba9 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -15,7 +15,7 @@ Login to %installation_name%: Connexion à %installation_name% Enabled: Activé enabled: activé disabled: désactivé -Disabled: Désacdtivé +Disabled: Désactivé Id: identifiant Homepage: Accueil Welcome: Bienvenue @@ -49,6 +49,7 @@ Name: Nom Label: Nom user: + current_user: Utilisateur courant profile: title: Mon profil Phonenumber successfully updated!: Numéro de téléphone mis à jour! @@ -715,8 +716,20 @@ notification: mark_as_read: Marquer comme lu mark_as_unread: Marquer comme non-lu +CHILL_MAIN_COMPOSE_EXPORT: Exécuter des exports et les sauvegarder +CHILL_MAIN_GENERATE_SAVED_EXPORT: Exécuter et modifier des exports préalablement sauvegardés export: + role: + export_role: Exports + generation: + Export generation is pending: La génération de l'export est en cours + Export generation is pending_short: En cours + Come back later: Retour à l'index + Too many retries: Le nombre de vérification de la disponibilité de l'export a échoué. Essayez de recharger la page. + Error while generating export: Erreur interne lors de la génération de l'export + Error_short: En erreur + Export ready: L'export est prêt à être téléchargé address_helper: id: Identifiant de l'adresse street: Voie @@ -781,14 +794,23 @@ saved_export: Edit: Modifier un export enregistré Delete saved ?: Supprimer un export enregistré ? Are you sure you want to delete this saved ?: Êtes-vous sûr·e de vouloir supprimer cet export ? - My saved exports: Mes exports enregistrés + Saved exports: Exports enregistrés Export is deleted: L'export est supprimé Saved export is saved!: L'export est enregistré Created on %date%: Créé le %date% update_title_and_description: Modifier le titre et la description update_filters_aggregators_and_execute: Modifier les filtres et regroupements et télécharger - execute: Télécharger + execute: Générer Update existing: Mettre à jour le rapport enregistré existant + Owner: Propriétaire + Shared with others: Partagé + Save to new saved export: Créer un nouvel export enregistré + Update current saved export: Modifier la configuration de l'export existant + Duplicate: Dupliquer + Duplicated: Dupliqué + Options updated successfully: La configuration de l'export a été mise à jour + Share: Partage + Alert auto generated description: La description ci-dessous a été générée automatiquement, comme si l'export était exécutée immédiatement. Veillez à l'adapter pour tenir compte des paramètres qui peuvent être modifiés (utilisateurs courant, dates glissantes, etc.). absence: # single letter for absence diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php index 084fe8ff5..de1eac37d 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php @@ -25,7 +25,7 @@ class UserRefEventSubscriber implements EventSubscriberInterface { public function __construct(private readonly Security $security, private readonly TranslatorInterface $translator, private readonly \Twig\Environment $engine, private readonly NotificationPersisterInterface $notificationPersister) {} - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ 'workflow.accompanying_period_lifecycle.entered' => [ diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 40393f79e..ffff789c2 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\DependencyInjection; use Chill\MainBundle\DependencyInjection\MissingBundleException; -use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\PersonBundle\Controller\AccompanyingPeriodCommentApiController; use Chill\PersonBundle\Controller\AccompanyingPeriodResourceApiController; use Chill\PersonBundle\Controller\AdministrativeStatusController; @@ -1027,8 +1026,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role_hierarchy' => [ PersonVoter::UPDATE => [PersonVoter::SEE], PersonVoter::CREATE => [PersonVoter::SEE], - PersonVoter::LISTS => [ChillExportVoter::EXPORT], - PersonVoter::STATS => [ChillExportVoter::EXPORT], // accompanying period AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE], AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS], diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php index 522b2ecdd..859e9868b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php @@ -27,7 +27,7 @@ class AdministrativeLocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acploc', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.administrativeLocation', 'acploc'); @@ -42,17 +42,32 @@ class AdministrativeLocationAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class AdministrativeLocationAggregator implements AggregatorInterface return ['location_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group by administrative location'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php index 8f4797302..ae0a09930 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php @@ -24,7 +24,7 @@ class ByActionNumberAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('(SELECT COUNT(acp_by_action_action.id) FROM '.AccompanyingPeriodWork::class.' acp_by_action_action WHERE acp_by_action_action.accompanyingPeriod = acp) AS acp_by_action_number_aggregator') ->addGroupBy('acp_by_action_number_aggregator'); @@ -40,12 +40,27 @@ class ByActionNumberAggregator implements AggregatorInterface // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php index ab6bb6170..27f0a3351 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php @@ -39,6 +39,21 @@ final readonly class ClosingDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return [ @@ -46,7 +61,7 @@ final readonly class ClosingDateAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $value): string { if ('_header' === $value) { @@ -57,12 +72,12 @@ final readonly class ClosingDateAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_closing_date']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by_closing_date.title'; } @@ -72,7 +87,7 @@ final readonly class ClosingDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -81,7 +96,7 @@ final readonly class ClosingDateAggregator implements AggregatorInterface $qb->addOrderBy("{$p}_closing_date", 'DESC'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php index 259d5fb66..f8ded2b9c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php @@ -27,7 +27,7 @@ class ClosingMotiveAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(acp.closingMotive) AS closingmotive_aggregator'); $qb->addGroupBy('closingmotive_aggregator'); @@ -38,17 +38,32 @@ class ClosingMotiveAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -67,7 +82,7 @@ class ClosingMotiveAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['closingmotive_aggregator']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php index 0e3e5f735..dfd125824 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php @@ -26,7 +26,7 @@ class ConfidentialAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('acp.confidential AS confidential_aggregator'); $qb->addGroupBy('confidential_aggregator'); @@ -37,17 +37,32 @@ class ConfidentialAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php index 94202d958..236504735 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php @@ -34,7 +34,7 @@ class CreatorJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -78,14 +78,29 @@ class CreatorJobAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php index f5dc99115..83148ec0b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php @@ -33,7 +33,7 @@ final readonly class DurationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { match ($data['precision']) { 'day' => $qb->addSelect('(COALESCE(acp.closingDate, :now) - acp.openingDate) AS duration_aggregator'), @@ -54,7 +54,7 @@ final readonly class DurationAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('precision', ChoiceType::class, [ 'choices' => array_combine(self::CHOICES, self::CHOICES), @@ -65,12 +65,27 @@ final readonly class DurationAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['precision' => $formData['precision']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['precision' => $formData['precision']]; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) use ($data) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php index 0217166d2..557ae37df 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php @@ -26,7 +26,7 @@ class EmergencyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('acp.emergency AS emergency_aggregator'); $qb->addGroupBy('emergency_aggregator'); @@ -37,17 +37,32 @@ class EmergencyAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php index a90896ccd..c21e711a9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php @@ -27,7 +27,7 @@ final readonly class EvaluationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpw', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.works', 'acpw'); @@ -46,17 +46,32 @@ final readonly class EvaluationAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php index fde825a88..3bfc6d740 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php @@ -28,14 +28,20 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class GeographicalUnitStatAggregator implements AggregatorInterface { - public function __construct(private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, private TranslatableStringHelperInterface $translatableStringHelper, private RollingDateConverterInterface $rollingDateConverter) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('acp.locationHistories', 'acp_geog_agg_location_history'); @@ -105,7 +111,7 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -123,12 +129,27 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize(), 'level' => $this->normalizeDoctrineEntity($formData['level'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'level' => $this->denormalizeDoctrineEntity($formData['level'], $this->geographicalUnitLayerRepository)]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return match ($key) { 'acp_geog_agg_unitname' => static function ($value): string { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php index de42039c1..4736a8394 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php @@ -26,7 +26,7 @@ class IntensityAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('acp.intensity AS intensity_aggregator'); $qb->addGroupBy('intensity_aggregator'); @@ -37,17 +37,32 @@ class IntensityAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php index bac34096f..c153d5036 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php @@ -35,7 +35,7 @@ final readonly class JobWorkingOnCourseAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,14 +72,29 @@ final readonly class JobWorkingOnCourseAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $jobId) { if (null === $jobId || '' === $jobId) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php index d0d121c2a..8ed8f9b89 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php @@ -39,6 +39,21 @@ final readonly class OpeningDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return [ @@ -46,7 +61,7 @@ final readonly class OpeningDateAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $value): string { if ('_header' === $value) { @@ -57,12 +72,12 @@ final readonly class OpeningDateAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_opening_date']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by_opening_date.title'; } @@ -72,7 +87,7 @@ final readonly class OpeningDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -81,7 +96,7 @@ final readonly class OpeningDateAggregator implements AggregatorInterface $qb->addOrderBy("{$p}_opening_date", 'DESC'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php index 070122f2e..1198cd2d8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php @@ -36,7 +36,7 @@ final readonly class OriginAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acporigin', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.origin', 'acporigin'); @@ -51,17 +51,32 @@ final readonly class OriginAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php index 3859eded5..1e719a176 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php @@ -25,17 +25,32 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac private LabelPersonHelper $labelPersonHelper, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to do here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return match ($key) { self::KEY => $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.course.by-user.header'), @@ -43,12 +58,12 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by-user.title'; } @@ -58,7 +73,7 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $k = self::KEY; @@ -70,7 +85,7 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac ->addGroupBy($k); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php index 2df724517..e94869747 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php @@ -39,7 +39,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('IDENTITY('.self::A.'.user) AS referrer_aggregator') @@ -66,7 +66,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -79,6 +79,26 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $defaultData = $this->getFormDefaultData(); + + return [ + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $defaultData['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $defaultData['end_date'], + ]; + } + public function getFormDefaultData(): array { return [ @@ -102,7 +122,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran return $data; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php index 30ac95027..e2420cc63 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php @@ -39,7 +39,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -76,7 +76,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -89,6 +89,26 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $default = $this->getFormDefaultData(); + + return [ + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $default['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $default['end_date'], + ]; + } + public function getFormDefaultData(): array { return [ @@ -108,7 +128,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans return $data; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php index ec168ceaf..c71e3e525 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php @@ -26,7 +26,7 @@ final readonly class RequestorAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acppart', $qb->getAllAliases(), true)) { $qb->join('acp.participations', 'acppart'); @@ -58,17 +58,32 @@ final readonly class RequestorAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php index 06dbc906d..d28399acd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php @@ -27,7 +27,7 @@ final readonly class ScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpscope', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.scopes', 'acpscope'); @@ -42,17 +42,32 @@ final readonly class ScopeAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php index dd33603b4..3ca9be3c3 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php @@ -35,7 +35,7 @@ final readonly class ScopeWorkingOnCourseAggregator implements AggregatorInterfa return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,14 +72,29 @@ final readonly class ScopeWorkingOnCourseAggregator implements AggregatorInterfa return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $scopeId) { if (null === $scopeId || '' === $scopeId) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php index 7abad2602..a67895b0c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php @@ -27,7 +27,7 @@ final readonly class SocialActionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpw', $qb->getAllAliases(), true)) { // here, we will only see accompanying period linked with a socialAction @@ -43,17 +43,32 @@ final readonly class SocialActionAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php index 8c0cbfbd5..b42dbebc5 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php @@ -27,7 +27,7 @@ final readonly class SocialIssueAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpsocialissue', $qb->getAllAliases(), true)) { // we will see accompanying period linked with social issues @@ -43,17 +43,32 @@ final readonly class SocialIssueAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php index a9439a63f..3c6f2aaaa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php @@ -34,7 +34,7 @@ final readonly class StepAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array(self::A, $qb->getAllAliases(), true)) { $qb->leftJoin('acp.stepHistories', self::A); @@ -63,17 +63,32 @@ final readonly class StepAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { switch ($value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php index 903d3c9f4..d8a5fbd22 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php @@ -39,7 +39,7 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -76,7 +76,7 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -89,6 +89,21 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -108,7 +123,7 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans return $data; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php index 321801e28..3bac86567 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php @@ -29,17 +29,32 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac private UserRepositoryInterface $userRepository, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $userId) { if (null === $userId || '' === $userId) { @@ -58,12 +73,12 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::COLUMN_NAME]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by_user_working.title'; } @@ -73,7 +88,7 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!in_array('acpinfo', $qb->getAllAliases(), true)) { $qb->leftJoin( @@ -92,7 +107,7 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac $qb->addGroupBy('acpinfo_user.id'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php index 6b6a5ce4a..c17fea02d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php @@ -27,17 +27,32 @@ final readonly class ByClosingMotiveAggregator implements AggregatorInterface private ClosingMotiveRender $closingMotiveRender, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -52,14 +67,14 @@ final readonly class ByClosingMotiveAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ self::KEY, ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.step_history.by_closing_motive.title'; } @@ -69,14 +84,14 @@ final readonly class ByClosingMotiveAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('IDENTITY(acpstephistory.closingMotive) AS '.self::KEY) ->addGroupBy(self::KEY); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php index fbd80c7a5..20bdec4ec 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php @@ -26,7 +26,7 @@ final readonly class ByDateAggregator implements AggregatorInterface { private const KEY = 'acpstephistory_by_date_agg'; - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => array_combine( @@ -39,12 +39,27 @@ final readonly class ByDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => DateGroupingChoiceEnum::YEAR->value]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $value): string { if ('_header' === $value) { @@ -59,12 +74,12 @@ final readonly class ByDateAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.step_history.by_date.title'; } @@ -74,7 +89,7 @@ final readonly class ByDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::KEY; @@ -83,7 +98,7 @@ final readonly class ByDateAggregator implements AggregatorInterface $qb->addOrderBy($p, 'DESC'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php index b3be83da2..09924a9e0 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php @@ -29,17 +29,32 @@ final readonly class ByStepAggregator implements AggregatorInterface private TranslatorInterface $translator, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing in this form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $step): string { if ('_header' === $step) { @@ -54,14 +69,14 @@ final readonly class ByStepAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ self::KEY, ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.step_history.by_step.title'; } @@ -71,14 +86,14 @@ final readonly class ByStepAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('acpstephistory.step AS '.self::KEY) ->addGroupBy(self::KEY); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php index 07c182421..9d9139c43 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php @@ -32,7 +32,7 @@ final class ByEndDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $fmt = match ($data['frequency']) { 'week' => 'YYYY-IW', @@ -51,7 +51,7 @@ final class ByEndDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -61,12 +61,27 @@ final class ByEndDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php index 9193dc5a9..421632c76 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php @@ -32,7 +32,7 @@ final class ByMaxDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $fmt = match ($data['frequency']) { 'week' => 'YYYY-IW', @@ -51,7 +51,7 @@ final class ByMaxDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -61,12 +61,27 @@ final class ByMaxDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php index e797d5ae7..d07cd60e9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php @@ -32,7 +32,7 @@ final class ByStartDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $fmt = match ($data['frequency']) { 'week' => 'YYYY-IW', @@ -51,7 +51,7 @@ final class ByStartDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -61,12 +61,27 @@ final class ByStartDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php index 9ff2ad50a..d4e337581 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php @@ -27,7 +27,7 @@ class EvaluationTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(workeval.evaluation) AS eval_evaluationtype_aggregator'); $qb->addGroupBy('eval_evaluationtype_aggregator'); @@ -38,17 +38,32 @@ class EvaluationTypeAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php index 4dfddab81..1af0e6678 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php @@ -26,7 +26,7 @@ class HavingEndDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('CASE WHEN workeval.endDate IS NULL THEN true ELSE false END AS eval_enddate_aggregator') @@ -38,17 +38,32 @@ class HavingEndDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php index e013c2f0f..f4d2cb5db 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php @@ -29,7 +29,7 @@ class ChildrenNumberAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('composition_children', $qb->getAllAliases(), true)) { $clause = $qb->expr()->andX( @@ -58,17 +58,32 @@ class ChildrenNumberAggregator implements AggregatorInterface return Declarations::HOUSEHOLD_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function (int|string|null $value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php index 3dc3a1398..88e68feb3 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php @@ -31,7 +31,7 @@ class CompositionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('composition_type', $qb->getAllAliases(), true)) { $clause = $qb->expr()->andX( @@ -60,17 +60,32 @@ class CompositionAggregator implements AggregatorInterface return Declarations::HOUSEHOLD_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php index 8bdc74f7f..313d7bcb8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php @@ -27,7 +27,7 @@ final readonly class AdministrativeStatusAggregator implements AggregatorInterfa return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('person.administrativeStatus', 'admin_status'); $qb->addSelect('admin_status.id as administrative_status_aggregator'); @@ -35,19 +35,34 @@ final readonly class AdministrativeStatusAggregator implements AggregatorInterfa $qb->addGroupBy('administrative_status_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -64,12 +79,12 @@ final readonly class AdministrativeStatusAggregator implements AggregatorInterfa }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['administrative_status_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by administrative status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php index 2ca286b57..dc1913f7d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php @@ -31,31 +31,46 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('DATE_DIFF(:date_age_calculation, person.birthdate)/365 as person_age'); $qb->setParameter('date_age_calculation', $this->rollingDateConverter->convert($data['date_age_calculation'])); $qb->addGroupBy('person_age'); } - public function applyOn() + public function applyOn(): string { return 'person'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_age_calculation', PickRollingDateType::class, [ 'label' => 'Calculate age in relation to this date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_age_calculation' => $formData['date_age_calculation']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_age_calculation' => RollingDate::fromNormalized($formData['date_age_calculation'])]; + } + public function getFormDefaultData(): array { return ['date_age_calculation' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -66,19 +81,19 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ 'person_age', ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by age'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['date_age_calculation']) { $context->buildViolation('The date should not be empty') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php index af1018f6e..763c99088 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php @@ -34,7 +34,7 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -71,24 +71,39 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface ->addGroupBy("{$p}_select"); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_calc', PickRollingDateType::class, [ 'label' => 'export.aggregator.person.by_household_composition.Calc date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -107,12 +122,12 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_select']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_household_composition.Group course by household composition'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php index cd48a3f51..6f4082d46 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php @@ -29,13 +29,28 @@ final readonly class CenterAggregator implements AggregatorInterface private RollingDateConverterInterface $rollingDateConverter, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('at_date', PickRollingDateType::class, [ 'label' => 'export.aggregator.person.by_center.at_date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['at_date' => $formData['at_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['at_date' => RollingDate::fromNormalized($formData['at_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -43,7 +58,7 @@ final readonly class CenterAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value) { if (null === $value || '' === $value) { @@ -58,12 +73,12 @@ final readonly class CenterAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::COLUMN_NAME]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_center.title'; } @@ -73,7 +88,7 @@ final readonly class CenterAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $alias = 'pers_center_agg'; $atDate = 'pers_center_agg_at_date'; @@ -94,7 +109,7 @@ final readonly class CenterAggregator implements AggregatorInterface $qb->addGroupBy(self::COLUMN_NAME); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php index b8d204dc5..fbdcb6455 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php @@ -32,7 +32,7 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add a clause in select part if ('country' === $data['group_by_level']) { @@ -75,12 +75,12 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex $qb->addGroupBy('country_of_birth_aggregator'); } - public function applyOn() + public function applyOn(): string { return 'person'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('group_by_level', ChoiceType::class, [ 'choices' => [ @@ -92,12 +92,27 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { $labels = []; @@ -138,17 +153,17 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex return static fn (?string $value): string => $labels[(string) $value]; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['country_of_birth_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by country of birth'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['group_by_level']) { $context->buildViolation('You should select an option') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php index 359e48cf3..caf174cb3 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php @@ -27,7 +27,7 @@ final readonly class EmploymentStatusAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('person.employmentStatus', 'es'); $qb->addSelect('es.id as employment_status_aggregator'); @@ -35,19 +35,34 @@ final readonly class EmploymentStatusAggregator implements AggregatorInterface $qb->addGroupBy('employment_status_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -64,12 +79,12 @@ final readonly class EmploymentStatusAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['employment_status_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by employment status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php index f7ae26dca..4333e7f1d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php @@ -28,7 +28,7 @@ final readonly class GenderAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('person.gender', 'g'); $qb->addSelect('g.id as gender'); @@ -36,19 +36,34 @@ final readonly class GenderAggregator implements AggregatorInterface $qb->addGroupBy('gender'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value) { if (null === $value || '' === $value) { @@ -67,12 +82,12 @@ final readonly class GenderAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['gender']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by gender'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php index 8ce51a3ab..3227099b0 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php @@ -24,16 +24,22 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class GeographicalUnitAggregator implements AggregatorInterface +final readonly class GeographicalUnitAggregator implements AggregatorInterface { - public function __construct(private readonly GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly RollingDateConverterInterface $rollingDateConverter) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->leftJoin( @@ -73,12 +79,12 @@ class GeographicalUnitAggregator implements AggregatorInterface ->addGroupBy('geog_unit_key'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -96,6 +102,21 @@ class GeographicalUnitAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize(), 'level' => $this->normalizeDoctrineEntity($formData['level'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'level' => $this->denormalizeDoctrineEntity($formData['level'], $this->geographicalUnitLayerRepository)]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; @@ -106,7 +127,7 @@ class GeographicalUnitAggregator implements AggregatorInterface return 'person_geog_agg'; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return match ($key) { 'geog_unit_name' => static function ($value): string { @@ -135,12 +156,12 @@ class GeographicalUnitAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['geog_unit_name', 'geog_unit_key']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by geographical unit based on his address'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php index eb1e52d9b..f28ccfd61 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php @@ -35,7 +35,7 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface, return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('householdmember', $qb->getAllAliases(), true)) { $qb->join(HouseholdMember::class, 'householdmember', Expr\Join::WITH, 'householdmember.person = person'); @@ -62,24 +62,39 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface, $qb->addGroupBy('household_position_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_position', PickRollingDateType::class, [ 'label' => 'Household position in relation to this date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_position' => $formData['date_position']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_position' => RollingDate::fromNormalized($formData['date_position'])]; + } + public function getFormDefaultData(): array { return ['date_position' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -96,19 +111,19 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface, }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ 'household_position_aggregator', ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by household position'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['date_position']) { $context->buildViolation('The date should not be empty') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php index 1555a5a12..4a193fbd7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php @@ -27,7 +27,7 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('personmarital', $qb->getAllAliases(), true)) { $qb->join('person.maritalStatus', 'personmarital'); @@ -37,22 +37,37 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface $qb->addGroupBy('marital_status_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -69,12 +84,12 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['marital_status_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by marital status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php index 9daca5b34..a7b11f28f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php @@ -31,7 +31,7 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add a clause in select part if ('country' === $data['group_by_level']) { @@ -70,12 +70,12 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor $qb->addGroupBy('nationality_aggregator'); } - public function applyOn() + public function applyOn(): string { return 'person'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('group_by_level', ChoiceType::class, [ 'choices' => [ @@ -87,6 +87,21 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + public function getFormDefaultData(): array { return [ @@ -94,7 +109,7 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor ]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { $labels = []; @@ -135,17 +150,17 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor return static fn ($value): string => $labels[$value]; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['nationality_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by nationality'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['group_by_level']) { $context->buildViolation('You should select an option') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php index a8ec614d4..7a0240e9c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php @@ -28,7 +28,7 @@ final readonly class PostalCodeAggregator implements AggregatorInterface private RollingDateConverterInterface $rollingDateConverter, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('calc_date', PickRollingDateType::class, [ @@ -36,12 +36,27 @@ final readonly class PostalCodeAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -56,12 +71,12 @@ final readonly class PostalCodeAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_postal_code_code', self::PREFIX.'_postal_code_label']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_postal_code.title'; } @@ -71,7 +86,7 @@ final readonly class PostalCodeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -92,7 +107,7 @@ final readonly class PostalCodeAggregator implements AggregatorInterface ; } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php index 9abf5c1e7..a667a8a43 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php @@ -29,7 +29,7 @@ final readonly class ActionTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpwsocialaction', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.socialAction', 'acpwsocialaction'); @@ -46,22 +46,37 @@ final readonly class ActionTypeAggregator implements AggregatorInterface ->addGroupBy('social_action_type_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return match ($key) { 'action_type_aggregator' => function ($value): string { @@ -90,12 +105,12 @@ final readonly class ActionTypeAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['social_action_type_aggregator', 'action_type_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group social work actions by action type'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php index 46cc85de6..6f3faeece 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php @@ -32,7 +32,7 @@ class CreatorAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -46,14 +46,29 @@ class CreatorAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php index c1b7c248b..e5f08c03d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php @@ -34,7 +34,7 @@ class CreatorJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -63,14 +63,29 @@ class CreatorJobAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php index b38096079..1e59ad991 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php @@ -34,7 +34,7 @@ class CreatorScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -63,14 +63,29 @@ class CreatorScopeAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php index a9f8e020a..b6c74c2c4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php @@ -26,7 +26,7 @@ class CurrentActionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect(' @@ -46,12 +46,27 @@ class CurrentActionAggregator implements AggregatorInterface // No form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php index ce1e381f2..d5be32ca8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php @@ -27,7 +27,7 @@ final readonly class GoalAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('goal', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.goals', 'goal'); @@ -42,17 +42,32 @@ final readonly class GoalAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php index e1549f315..befdf8904 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php @@ -28,7 +28,7 @@ class GoalResultAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('goal', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.goals', 'goal'); @@ -48,17 +48,32 @@ class GoalResultAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) use ($key): string { if (null === $value || '' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php index f58246a25..d5f335184 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php @@ -27,27 +27,42 @@ final readonly class HandlingThirdPartyAggregator implements AggregatorInterface public function __construct(private LabelThirdPartyHelper $labelThirdPartyHelper) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form needed here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return $this->labelThirdPartyHelper->getLabel($key, $values, 'export.aggregator.course_work.by_handling_third_party.header'); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_h3party']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course_work.by_handling_third_party.title'; } @@ -57,7 +72,7 @@ final readonly class HandlingThirdPartyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -66,7 +81,7 @@ final readonly class HandlingThirdPartyAggregator implements AggregatorInterface ->addGroupBy("{$p}_h3party"); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php index 282a36db4..c795e0193 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php @@ -35,7 +35,7 @@ final readonly class JobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -58,14 +58,29 @@ final readonly class JobAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php index 7543da857..c2ccbed8a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php @@ -36,7 +36,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -56,13 +56,36 @@ final readonly class ReferrerAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('referrer_at', PickRollingDateType::class, [ 'label' => 'export.aggregator.course_work.by_treating_agent.Calc date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + if (!array_key_exists('referrer_at', $formData)) { + return ['referrer_at' => (new RollingDate(RollingDate::T_TODAY))->normalize()]; + } + + return ['referrer_at' => $formData['referrer_at']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + if (!array_key_exists('referrer_at', $formData)) { + return ['referrer_at' => new RollingDate(RollingDate::T_TODAY)]; + } + + return ['referrer_at' => RollingDate::fromNormalized($formData['referrer_at'])]; + } + public function getFormDefaultData(): array { return [ @@ -70,7 +93,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php index 63a037f21..df856cc56 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php @@ -27,7 +27,7 @@ final readonly class ResultAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('result', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.results', 'result'); @@ -42,17 +42,32 @@ final readonly class ResultAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php index 0bedaf267..9c1e37469 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php @@ -35,7 +35,7 @@ final readonly class ScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -58,14 +58,29 @@ final readonly class ScopeAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php index 18a0c54b0..e7f93a062 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php @@ -36,6 +36,21 @@ class AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod implements ExportInt public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -70,7 +85,7 @@ class AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod implements ExportInt return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -85,7 +100,7 @@ class AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod implements ExportInt return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php index a8750e42c..3448e93bc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php @@ -36,6 +36,21 @@ class AvgDurationAPWorkPersonAssociatedOnWork implements ExportInterface, Groupe public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -70,7 +85,7 @@ class AvgDurationAPWorkPersonAssociatedOnWork implements ExportInterface, Groupe return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -85,7 +100,7 @@ class AvgDurationAPWorkPersonAssociatedOnWork implements ExportInterface, Groupe return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php index 1118b2cc4..73055c6a2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -25,6 +26,7 @@ use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatableMessage; class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface { @@ -44,11 +46,26 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface // Nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + public function getFormDefaultData(): array { return []; } + public function normalizeFormData(array $formData): array + { + return $formData; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return $formData; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -81,14 +98,14 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle(): string + public function getTitle(): TranslatableMessage { - return 'Count accompanying courses'; + return new TranslatableMessage('Count accompanying courses'); } public function getType(): string @@ -96,7 +113,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface return Declarations::ACP_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php index 4f620fa34..5fcc8a6f7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php @@ -44,6 +44,21 @@ class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExpo // Nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -78,7 +93,7 @@ class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExpo return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExpo return Declarations::ACP_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php index a73e4f037..922101f95 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php @@ -41,6 +41,21 @@ class CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements // No form necessary? } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -78,7 +93,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php index 4f43a78b0..ea7f1c805 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php @@ -41,6 +41,21 @@ class CountAccompanyingPeriodWorkAssociatePersonOnWork implements ExportInterfac // No form necessary? } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -78,7 +93,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnWork implements ExportInterfac return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnWork implements ExportInterfac return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php b/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php index 0cbe7b3e8..acf035424 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php @@ -37,6 +37,21 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -74,7 +89,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -89,7 +104,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface return Declarations::EVAL_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php index a65dcb217..e7e2d20da 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php @@ -49,6 +49,21 @@ class CountHouseholdInPeriod implements ExportInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -93,7 +108,7 @@ class CountHouseholdInPeriod implements ExportInterface, GroupedExportInterface return ['household_export_result', 'acp_export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -108,7 +123,7 @@ class CountHouseholdInPeriod implements ExportInterface, GroupedExportInterface return Declarations::HOUSEHOLD_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php index a1e855b0e..9a620148a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php @@ -39,6 +39,21 @@ class CountPerson implements ExportInterface, GroupedExportInterface // No form necessary } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -71,32 +86,30 @@ class CountPerson implements ExportInterface, GroupedExportInterface return static fn ($value) => $labels[$value]; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count people'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } /** * Initiate the query. - * - * @return QueryBuilder */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php index 13dd0d512..b3d08a4d8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php @@ -41,6 +41,21 @@ class CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork implements Export // No form necessary? } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -78,7 +93,7 @@ class CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork implements Export return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork implements Export return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php index ef000603a..51dcfdb90 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php @@ -43,6 +43,21 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor // TODO: Implement buildForm() method. } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; @@ -80,7 +95,7 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -95,7 +110,7 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor return Declarations::HOUSEHOLD_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php index 7d0e52169..8ac6ffeef 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\ListInterface; @@ -46,6 +47,21 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -73,27 +89,27 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return $this->listAccompanyingPeriodHelper->getQueryKeys($data); } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.list.acp.List of accompanying periods'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->entityManager->createQueryBuilder(); @@ -102,7 +118,7 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp ->andWhere('acp.step != :list_acp_step') ->setParameter('list_acp_step', AccompanyingPeriod::STEP_DRAFT); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date'])); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php index 8f63b1584..f93e030f7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\AggregateStringHelper; @@ -103,6 +104,21 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -177,12 +193,12 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return self::FIELDS; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -197,7 +213,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY); @@ -217,7 +233,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri ->andWhere('acppart.startDate <= :calc_date AND (acppart.endDate > :calc_date OR acppart.endDate IS NULL)') ->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate)); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php index fe200ca02..13822697d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\AggregateStringHelper; @@ -103,6 +104,21 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -177,12 +193,12 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return self::FIELDS; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -197,7 +213,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY); @@ -212,7 +228,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ->join('acpw.persons', 'person') ; - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php b/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php index 71d9924be..201e5a234 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\AggregateStringHelper; @@ -93,6 +94,21 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -165,12 +181,12 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return self::FIELDS; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -185,7 +201,7 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter return Declarations::EVAL_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY); @@ -206,7 +222,7 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter ->andWhere('acppart.startDate <= :calc_date AND (acppart.endDate > :calc_date OR acppart.endDate IS NULL)') ->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate)); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php index b69330002..b455d3f65 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php @@ -67,6 +67,21 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -114,7 +129,7 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface ); } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -129,7 +144,7 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface return Declarations::HOUSEHOLD_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php index 2b8bfad20..55ced4cc2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php @@ -33,6 +33,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ class ListPerson implements ListInterface, GroupedExportInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private array $slugs = []; private readonly bool $filterStatsByCenters; @@ -56,6 +57,21 @@ class ListPerson implements ListInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['address_date' => $this->normalizeDate($formData['address_date'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['address_date' => $this->denormalizeDate($formData['address_date'])]; + } + public function getFormDefaultData(): array { return ['address_date' => new \DateTimeImmutable()]; @@ -85,7 +101,7 @@ class ListPerson implements ListInterface, GroupedExportInterface return $this->getLabelForCustomField($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { $fields = []; @@ -97,17 +113,17 @@ class ListPerson implements ListInterface, GroupedExportInterface return [...$fields, ...\array_keys($this->slugs)]; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'List peoples'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } @@ -115,7 +131,7 @@ class ListPerson implements ListInterface, GroupedExportInterface /** * param array{fields: string[], address_date: DateTimeImmutable} $data. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php index 69d00a612..c4b178111 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\DirectExportInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; @@ -20,9 +21,6 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -65,12 +63,27 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['precision' => $formData['precision']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['precision' => $formData['precision']]; + } + public function getFormDefaultData(): array { return ['precision' => self::PRECISION_DEFAULT_VALUE]; } - public function generate(array $acl, array $data = []): Response + public function generate(array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): FormattedExportGeneration { $values = []; $values[] = $this->getHeaders(); @@ -105,11 +118,10 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat $temp_file = sys_get_temp_dir().'/'.uniqid('export_').'.xlsx'; $writer->save($temp_file); - $response = new BinaryFileResponse($temp_file); - $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'export_duplicate.xlsx'); + $formatted = new FormattedExportGeneration(file_get_contents($temp_file), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + unlink($temp_file); - return $response; + return $formatted; } public function getDescription(): string @@ -125,7 +137,7 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat /** * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'List duplicates'; } @@ -135,7 +147,7 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat return PersonVoter::DUPLICATE; } - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} protected function getHeaders(): array { diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php index 4a44f2dc5..de1f495cd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php @@ -56,6 +56,21 @@ final readonly class ListPersonHavingAccompanyingPeriod implements ListInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['address_date_rolling' => $formData['address_date_rolling']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['address_date_rolling' => RollingDate::fromNormalized($formData['address_date_rolling'])]; + } + public function getFormDefaultData(): array { return ['address_date_rolling' => new RollingDate(RollingDate::T_TODAY)]; @@ -81,22 +96,22 @@ final readonly class ListPersonHavingAccompanyingPeriod implements ListInterface return $this->listPersonHelper->getLabels($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return $this->listPersonHelper->getAllKeys(); } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.list.person_with_acp.List peoples having an accompanying period'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } @@ -104,7 +119,7 @@ final readonly class ListPersonHavingAccompanyingPeriod implements ListInterface /** * param array{fields: string[], address_date: DateTimeImmutable} $data. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php index 42a2205a1..6c641914b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\ListInterface; @@ -51,6 +52,21 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['address_date' => $formData['address_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['address_date' => RollingDate::fromNormalized($formData['address_date'])]; + } + public function getFormDefaultData(): array { return ['address_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -80,7 +96,7 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return array_merge( $this->listPersonHelper->getAllKeys(), @@ -88,17 +104,17 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte ); } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.list.person_with_acp.List peoples having an accompanying period with period details'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } @@ -106,7 +122,7 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte /** * param array{fields: string[], address_date: DateTimeImmutable} $data. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); @@ -117,7 +133,7 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte ->join('acppart.accompanyingPeriod', 'acp') ->andWhere($qb->expr()->neq('acp.step', "'".AccompanyingPeriod::STEP_DRAFT."'")); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); $this->listPersonHelper->addSelect($qb, $this->rollingDateConverter->convert($data['address_date'])); $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['address_date'])); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php b/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php index 58f06ab7e..5c5b5eee6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php @@ -51,6 +51,21 @@ final readonly class StatAccompanyingCourseDuration implements ExportInterface, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['closingdate_rolling' => $formData['closingdate_rolling']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['closingdate_rolling' => RollingDate::fromNormalized($formData['closingdate_rolling'])]; + } + public function getFormDefaultData(): array { return ['closingdate_rolling' => new RollingDate(RollingDate::T_TODAY)]; @@ -97,7 +112,7 @@ final readonly class StatAccompanyingCourseDuration implements ExportInterface, return ['avg_export_result', 'count_acp_export_result', 'count_acppart_export_result', 'count_pers_export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -112,7 +127,7 @@ final readonly class StatAccompanyingCourseDuration implements ExportInterface, return Declarations::ACP_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php index 6bcb4f8b8..f0571bc32 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class ActiveOnDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -59,18 +60,33 @@ class ActiveOnDateFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by actives courses: active on %ondate%', [ '%ondate%' => $this->rollingDateConverter->convert($data['on_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php index 3b7c4fe25..1a319b3ea 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class ActiveOneDayBetweenDatesFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = "OVERLAPSI (acp.openingDate, acp.closingDate), (:datefrom, :dateto) = 'TRUE'"; @@ -48,19 +49,34 @@ class ActiveOneDayBetweenDatesFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by actives courses: at least one day between %datefrom% and %dateto%', [ '%datefrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php index 915a2c9e6..cd502421e 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php @@ -11,23 +11,27 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; +use Chill\MainBundle\Repository\LocationTypeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class AdministrativeLocationFilter implements FilterInterface +final readonly class AdministrativeLocationFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private LocationTypeRepository $locationTypeRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('acp.administrativeLocation', ':locations'); $qb @@ -40,7 +44,7 @@ class AdministrativeLocationFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_locations', PickUserLocationType::class, [ 'label' => 'Accepted locations', @@ -48,12 +52,27 @@ class AdministrativeLocationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_locations' => $this->normalizeDoctrineEntity($formData['accepted_locations'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_locations' => $this->denormalizeDoctrineEntity($formData['accepted_locations'], $this->locationTypeRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $locations = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php index 7fb032050..3813a780f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php @@ -11,25 +11,29 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepositoryInterface; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class ClosingMotiveFilter implements FilterInterface +final readonly class ClosingMotiveFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private ClosingMotiveRepositoryInterface $closingMotiveRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('acp.closingMotive', ':closingmotive'); @@ -49,7 +53,7 @@ class ClosingMotiveFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_closingmotives', EntityType::class, [ 'class' => ClosingMotive::class, @@ -59,12 +63,27 @@ class ClosingMotiveFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_closingmotives' => $this->normalizeDoctrineEntity($formData['accepted_closingmotives'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_closingmotives' => $this->denormalizeDoctrineEntity($formData['accepted_closingmotives'], $this->closingMotiveRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $motives = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php index 4c8baf147..bfd454eea 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr\Andx; @@ -35,7 +36,7 @@ class ConfidentialFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->eq('acp.confidential', ':confidential'); @@ -55,7 +56,7 @@ class ConfidentialFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_confidentials', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -65,12 +66,27 @@ class ConfidentialFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_confidentials' => $formData['accepted_confidentials']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_confidentials' => $formData['accepted_confidentials']]; + } + public function getFormDefaultData(): array { return ['accepted_confidentials' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by confidential: only %confidential%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php index 4fa2c2d1d..8dc10381a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php @@ -12,21 +12,27 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class CreatorFilter implements FilterInterface +final readonly class CreatorFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private readonly UserRepositoryInterface $userRepository) {} + public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acp_creator', $qb->getAllAliases(), true)) { $qb->join('acp.createdBy', 'acp_creator'); @@ -34,7 +40,7 @@ class CreatorFilter implements FilterInterface $qb ->andWhere($qb->expr()->in('acp_creator', ':creators')) - ->setParameter('creators', $data['accepted_creators']); + ->setParameter('creators', $this->userOrMe($data['accepted_creators'], $exportGenerationContext)); } public function applyOn(): string @@ -42,21 +48,36 @@ class CreatorFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_creators', PickUserDynamicType::class, [ + ->add('accepted_creators', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => false, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_creators' => $this->normalizeUserOrMe($formData['accepted_creators'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_creators' => $this->denormalizeUserOrMe($formData['accepted_creators'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by creator: only %creators%', [ @@ -64,7 +85,7 @@ class CreatorFilter implements FilterInterface ', ', array_map( static fn (User $u) => $u->getLabel(), - $data['accepted_creators'] instanceof Collection ? $data['accepted_creators']->toArray() : $data['accepted_creators'] + $data['accepted_creators'] instanceof Collection ? $this->userOrMe($data['accepted_creators']->toArray(), $context) : $this->userOrMe($data['accepted_creators'], $context) ) ), ], ]; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php index dd1264bd2..45bdfc15d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -21,14 +22,16 @@ use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatableMessage; -class CreatorJobFilter implements FilterInterface +final readonly class CreatorJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_creator_job'; public function __construct( - private readonly TranslatableStringHelper $translatableStringHelper, - private readonly UserJobRepositoryInterface $userJobRepository, + private TranslatableStringHelper $translatableStringHelper, + private UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string @@ -36,7 +39,7 @@ class CreatorJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -83,7 +86,7 @@ class CreatorJobFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('creator_job', EntityType::class, [ @@ -98,19 +101,34 @@ class CreatorJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['creator_job' => $this->normalizeDoctrineEntity($formData['creator_job'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['creator_job' => $this->denormalizeDoctrineEntity($formData['creator_job'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): TranslatableMessage { $creatorJobs = []; foreach ($data['creator_job'] as $j) { $creatorJobs[] = $this->translatableStringHelper->localize( - $j->getLabel() + $j->getLabel(), ); } - return ['export.filter.course.creator_job.Filtered by creator job: only %jobs%', [ + return new TranslatableMessage('export.filter.course.creator_job.Filtered by creator job: only %jobs%', [ '%jobs%' => implode(', ', $creatorJobs), - ]]; + ]); } public function getFormDefaultData(): array @@ -120,8 +138,8 @@ class CreatorJobFilter implements FilterInterface ]; } - public function getTitle(): string + public function getTitle(): TranslatableMessage { - return 'export.filter.course.creator_job.Filter by creator job'; + return new TranslatableMessage('export.filter.course.creator_job.Filter by creator job'); } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php index 671b87407..516996f44 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr\Andx; @@ -35,7 +36,7 @@ class EmergencyFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->eq('acp.emergency', ':emergency'); @@ -55,7 +56,7 @@ class EmergencyFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_emergency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -65,12 +66,27 @@ class EmergencyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + public function getFormDefaultData(): array { return ['accepted_emergency' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by emergency: only %emergency%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php index bec01c249..f50b2eedc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\Evaluation; @@ -20,16 +21,18 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class EvaluationFilter implements FilterInterface +final readonly class EvaluationFilter implements FilterInterface { - public function __construct(private readonly EvaluationRepositoryInterface $evaluationRepository, private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private EvaluationRepositoryInterface $evaluationRepository, private TranslatableStringHelper $translatableStringHelper) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpw', $qb->getAllAliases(), true)) { $qb->join('acp.works', 'acpw'); @@ -53,7 +56,7 @@ class EvaluationFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_evaluations', EntityType::class, [ 'class' => Evaluation::class, @@ -65,12 +68,27 @@ class EvaluationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_evaluations' => $this->normalizeDoctrineEntity($formData['accepted_evaluations'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_evaluations' => $this->denormalizeDoctrineEntity($formData['accepted_evaluations'], $this->evaluationRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $evaluations = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php index 7d6d90e64..48fccb882 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface; @@ -45,7 +46,7 @@ class GeographicalUnitStatFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $subQueryDql = 'SELECT @@ -86,7 +87,7 @@ class GeographicalUnitStatFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -106,12 +107,38 @@ class GeographicalUnitStatFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'date_calc' => $formData['date_calc']->normalize(), + 'units' => array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $formData['units']), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $units = array_filter( + array_map( + fn (int $id) => $this->geographicalUnitRepository->findSimpleGeographicalUnit($id), + $formData['units'], + ), + static fn (?SimpleGeographicalUnitDTO $unitDTO) => null !== $unitDTO, + ); + + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'units' => $units]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by geographic unit: computed at %date%, only in %units%', [ '%date%' => $this->rollingDateConverter->convert($data['date_calc'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php index 8a9f734aa..eb3376247 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php @@ -11,10 +11,12 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; @@ -22,18 +24,20 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class HandlingThirdPartyFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_handling_3party_filter'; public function __construct( private ThirdPartyRender $thirdPartyRender, + private ThirdPartyRepository $thirdPartyRepository, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.work.by_handling3party.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('handling_3parties', PickThirdpartyDynamicType::class, [ 'label' => 'export.filter.work.by_handling3party.pick_3parties', @@ -41,12 +45,27 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['handling_3parties' => $this->normalizeDoctrineEntity($formData['handling_3parties'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['handling_3parties' => $this->denormalizeDoctrineEntity($formData['handling_3parties'], $this->thirdPartyRepository)]; + } + public function getFormDefaultData(): array { return ['handling_3parties' => []]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.work.by_handling3party.Only 3 parties %3parties%', @@ -67,7 +86,7 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -75,7 +94,7 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface $qb->setParameter("{$p}_3ps", $data['handling_3parties']); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php index 3ac083b32..7a87a10b2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Export\Declarations; @@ -24,7 +25,7 @@ class HasNoActionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere('NOT EXISTS (SELECT 1 FROM '.AccompanyingPeriodWork::class.' work WHERE work.accompanyingPeriod = acp)'); } @@ -39,12 +40,27 @@ class HasNoActionFilter implements FilterInterface // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered acp which has no actions']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php index 72bcce39e..9724ea9ed 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class HasNoReferrerFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere(' @@ -54,7 +55,7 @@ class HasNoReferrerFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('calc_date', PickRollingDateType::class, [ @@ -62,12 +63,27 @@ class HasNoReferrerFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered acp which has no referrer on date: %date%', [ '%date%' => $this->rollingDateConverter->convert($data['calc_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php index 778e4181a..64e319f35 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class HasTemporaryLocationFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->join('acp.locationHistories', 'acp_having_temporarily_location') @@ -52,7 +53,7 @@ class HasTemporaryLocationFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('having_temporarily', ChoiceType::class, [ @@ -73,12 +74,27 @@ class HasTemporaryLocationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['having_temporarily' => $formData['having_temporarily'], 'calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['having_temporarily' => $formData['having_temporarily'], 'calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return match ($data['having_temporarily']) { true => ['export.filter.course.having_temporarily.Having a temporarily location', []], diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php index d6436f8b4..37e6648a1 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -42,6 +43,21 @@ final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -52,7 +68,7 @@ final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements return 'export.filter.course.having_info_within_interval.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.having_info_within_interval.Only course with events between %startDate% and %endDate%', @@ -68,7 +84,7 @@ final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $ai = 'having_ai_within_interval_acc_info'; $as = 'having_ai_within_interval_start_date'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php index 3eb8bbb24..7a48fe635 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr\Andx; @@ -35,7 +36,7 @@ class IntensityFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->eq('acp.intensity', ':intensity'); @@ -55,7 +56,7 @@ class IntensityFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_intensities', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -65,12 +66,27 @@ class IntensityFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_intensities' => $formData['accepted_intensities']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_intensities' => $formData['accepted_intensities']]; + } + public function getFormDefaultData(): array { return ['accepted_intensities' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by intensity: only %intensity%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php index 622aa5801..80a0b2fed 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\UserJobRepositoryInterface; @@ -33,6 +34,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ readonly class JobWorkingOnCourseFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_user_job_working_on_course'; public function __construct( @@ -46,7 +48,7 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -105,7 +107,22 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository), 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.by_job_working.Filtered by job working on course: only %jobs%, between %start_date% and %end_date%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php index 60486371e..4fc2af355 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,18 +29,33 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI private RollingDateConverterInterface $rollingDateConverter, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.course.not_having_address_reference.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_calc', PickRollingDateType::class, [ 'label' => 'export.filter.course.not_having_address_reference.adress_at', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return [ @@ -47,7 +63,7 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.course.not_having_address_reference.describe', @@ -62,7 +78,7 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $k = 'acp_not_associated_ref_filter'; @@ -91,7 +107,7 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI $qb->setParameter("{$k}_date_calc", $this->rollingDateConverter->convert($data['date_calc'])); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php index 1b85f6cd7..d116f3d5c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class OpenBetweenDatesFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->andX( $qb->expr()->gte('acp.openingDate', ':datefrom'), @@ -46,19 +47,34 @@ class OpenBetweenDatesFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_MONTH_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by opening dates: between %datefrom% and %dateto%', [ '%datefrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php index 617577cde..b0b2093bd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php @@ -11,25 +11,29 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\AccompanyingPeriod\OriginRepository; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class OriginFilter implements FilterInterface +final readonly class OriginFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private OriginRepository $originRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('acp.origin', ':origin'); @@ -49,7 +53,7 @@ class OriginFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_origins', EntityType::class, [ 'class' => Origin::class, @@ -59,12 +63,27 @@ class OriginFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_origins' => $this->normalizeDoctrineEntity($formData['accepted_origins'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_origins' => $this->denormalizeDoctrineEntity($formData['accepted_origins'], $this->originRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $origins = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php index 30f67f664..fd668a1fa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php @@ -11,31 +11,36 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; +use Chill\MainBundle\Templating\Entity\UserRender; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class ReferrerFilter implements FilterInterface +final readonly class ReferrerFilter implements FilterInterface { - private const A = 'acp_referrer_filter_uhistory'; + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + private const A = 'acp_referrer_filter_uhistory_x'; private const P = 'acp_referrer_filter_date'; private const PU = 'acp_referrer_filter_users'; - public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter) {} + public function __construct(private RollingDateConverterInterface $rollingDateConverter, private UserRepositoryInterface $userRepository, private UserRender $userRender) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->join('acp.userHistories', self::A) @@ -51,7 +56,7 @@ class ReferrerFilter implements FilterInterface ->andWhere( $qb->expr()->in(self::A.'.user', ':'.self::PU) ) - ->setParameter(self::PU, $data['accepted_referrers']) + ->setParameter(self::PU, $this->userOrMe($data['accepted_referrers'], $exportGenerationContext)) ->setParameter( self::P, $this->rollingDateConverter->convert($data['date_calc']) @@ -63,10 +68,10 @@ class ReferrerFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_referrers', PickUserDynamicType::class, [ + ->add('accepted_referrers', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]) ->add('date_calc', PickRollingDateType::class, [ @@ -75,17 +80,32 @@ class ReferrerFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_referrers' => $this->normalizeUserOrMe($formData['accepted_referrers']), 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_referrers' => $this->denormalizeUserOrMe($formData['accepted_referrers'], $this->userRepository), 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY), 'accepted_referrers' => []]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_referrers'] as $r) { - $users[] = $r; + foreach ($this->userOrMe($data['accepted_referrers'], $context) as $r) { + $users[] = $this->userRender->renderString($r, []); } return [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php index 6116e968b..929336feb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\Entity\UserRender; @@ -32,6 +34,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ final readonly class ReferrerFilterBetweenDates implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const A = 'acp_referrer_filter_uhistory'; private const P = 'acp_referrer_filter_date_start'; @@ -42,6 +45,7 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface public function __construct( private RollingDateConverterInterface $rollingDateConverter, private UserRender $userRender, + private UserRepository $userRepository, ) {} public function addRole(): ?string @@ -49,7 +53,7 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $history = self::A; $start = self::P; @@ -64,7 +68,7 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface ->andWhere( "{$history}.user IN (:{$users})", ) - ->setParameter($users, $data['accepted_referrers']) + ->setParameter($users, $this->userOrMe($data['accepted_referrers'], $exportGenerationContext)) ->setParameter($start, $this->rollingDateConverter->convert($data['start_date'])) ->setParameter($end, $this->rollingDateConverter->convert($data['end_date'])); } @@ -74,10 +78,10 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_referrers', PickUserDynamicType::class, [ + ->add('accepted_referrers', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]) ->add('start_date', PickRollingDateType::class, [ @@ -90,6 +94,23 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_referrers' => $this->normalizeUserOrMe($formData['accepted_referrers']), + 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_referrers' => $this->denormalizeUserOrMe($formData['accepted_referrers'], $this->userRepository), + 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -99,11 +120,11 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_referrers'] as $r) { + foreach ($this->userOrMe($data['accepted_referrers'], $context) as $r) { $users[] = $this->userRender->renderString($r, []); } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php index 0b7ce6994..8fa1ae766 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\EntityManagerInterface; @@ -38,7 +39,7 @@ final readonly class RequestorFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -107,7 +108,7 @@ final readonly class RequestorFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_choices', ChoiceType::class, [ 'choices' => self::REQUESTOR_CHOICES, @@ -117,12 +118,27 @@ final readonly class RequestorFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_choices' => $formData['accepted_choices']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_choices' => $formData['accepted_choices']]; + } + public function getFormDefaultData(): array { return ['accepted_choices' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $choice = array_flip(self::REQUESTOR_CHOICES)[$data['accepted_choices']]; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php index 63d93805f..f2240ae2c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\ScopeRepositoryInterface; @@ -33,6 +34,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ readonly class ScopeWorkingOnCourseFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_user_scope_working_on_course'; public function __construct( @@ -46,7 +48,7 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -100,7 +102,22 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository), 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.by_scope_working.Filtered by scope working on course: only %scopes%, between %start_date% and %end_date%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php index 545c35f5e..e3ce3928c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; @@ -18,6 +20,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Form\Type\PickSocialActionType; +use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -25,12 +28,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; final readonly class SocialActionFilter implements FilterInterface { + use ExportDataNormalizerTrait; + private const PREFIX = 'acp_by_social_action_filter'; public function __construct( private SocialActionRender $actionRender, private RollingDateConverterInterface $rollingDateConverter, private TranslatorInterface $translator, + private SocialActionRepository $socialActionRepository, ) {} public function addRole(): ?string @@ -38,7 +44,7 @@ final readonly class SocialActionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -81,13 +87,18 @@ final readonly class SocialActionFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { + $actions = $this->socialActionRepository->findAllOrdered(); + $builder ->add('accepted_socialactions', PickSocialActionType::class, [ 'multiple' => true, 'label' => 'export.filter.course.by_social_action.Accepted socialactions', 'help' => 'export.filter.course.by_social_action.accepted socialations help', + 'show_social_issue_parenthesis' => true, + 'show_deactivated' => true, + 'choices' => $actions, ]) ->add('start_date_after', PickRollingDateType::class, [ 'label' => 'export.filter.course.by_social_action.start date after', @@ -112,6 +123,29 @@ final readonly class SocialActionFilter implements FilterInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_socialactions' => $this->normalizeDoctrineEntity($formData['accepted_socialactions']), + 'start_date_after' => $formData['start_date_after']?->normalize(), + 'start_date_before' => $formData['start_date_before']?->normalize(), + 'end_date_after' => $formData['end_date_after']?->normalize(), + 'end_date_before' => $formData['end_date_before']?->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialactions' => $this->denormalizeDoctrineEntity($formData['accepted_socialactions'], $this->socialActionRepository), + 'start_date_after' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['start_date_after'] ?? null), + 'start_date_before' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['start_date_before'] ?? null), + 'end_date_after' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['end_date_after'] ?? null), + 'end_date_before' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['end_date_before'] ?? null)]; + } + public function getFormDefaultData(): array { return [ @@ -123,7 +157,7 @@ final readonly class SocialActionFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $actions = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php index afcdd15bc..30b00efb2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php @@ -11,36 +11,33 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Form\Type\PickSocialIssueType; +use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; class SocialIssueFilter implements FilterInterface { - /** - * @var TranslatorInterface - */ - protected $translator; + use ExportDataNormalizerTrait; public function __construct( - TranslatorInterface $translator, private readonly SocialIssueRender $socialIssueRender, - ) { - $this->translator = $translator; - } + private readonly SocialIssueRepository $socialIssueRepository, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpsocialissue', $qb->getAllAliases(), true)) { $qb->join('acp.socialIssues', 'acpsocialissue'); @@ -57,24 +54,39 @@ class SocialIssueFilter implements FilterInterface ); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_socialissues', PickSocialIssueType::class, [ 'multiple' => true, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_socialissues' => $this->normalizeDoctrineEntity($formData['accepted_socialissues'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialissues' => $this->denormalizeDoctrineEntity($formData['accepted_socialissues'], $this->socialIssueRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $issues = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php index dfd627eda..176fa99d1 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -46,7 +47,7 @@ class StepFilterBetweenDates implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $alias = 'acp_filter_by_step_between_dat_alias'; $steps = 'acp_filter_by_step_between_dat_steps'; @@ -66,12 +67,12 @@ class StepFilterBetweenDates implements FilterInterface ->setParameter($steps, $data['accepted_steps_multi']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('accepted_steps_multi', ChoiceType::class, [ @@ -88,6 +89,21 @@ class StepFilterBetweenDates implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_steps_multi' => $formData['accepted_steps_multi'], 'date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_steps_multi' => $formData['accepted_steps_multi'], 'date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return [ @@ -97,7 +113,7 @@ class StepFilterBetweenDates implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $steps = array_map( fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), @@ -111,7 +127,7 @@ class StepFilterBetweenDates implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.course.by_step.Filter by step between dates'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php index 8b36b1b5b..54cdbd0dd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -25,6 +27,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class StepFilterOnDate implements FilterInterface { + use ExportDataNormalizerTrait; + private const A = 'acp_filter_bystep_stephistories'; private const DEFAULT_CHOICE = [ @@ -50,7 +54,7 @@ class StepFilterOnDate implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array(self::A, $qb->getAllAliases(), true)) { $qb->leftJoin('acp.stepHistories', self::A); @@ -73,12 +77,12 @@ class StepFilterOnDate implements FilterInterface ->setParameter('acp_filter_by_step_steps', $data['accepted_steps_multi']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('accepted_steps_multi', ChoiceType::class, [ @@ -92,6 +96,27 @@ class StepFilterOnDate implements FilterInterface ]); } + public function normalizeFormData(array $formData): array + { + return [ + 'accepted_steps_multi' => $formData['accepted_steps_multi'], + 'calc_date' => $formData['calc_date']->normalize(), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'accepted_steps_multi' => $formData['accepted_steps_multi'], + 'calc_date' => RollingDate::fromNormalized($formData['calc_date']), + ]; + } + + public function getNormalizationVersion(): int + { + return 1; + } + public function getFormDefaultData(): array { return [ @@ -100,7 +125,7 @@ class StepFilterOnDate implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): array { $steps = array_map( fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), @@ -112,7 +137,7 @@ class StepFilterOnDate implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string { return 'export.filter.course.by_step.Filter by step'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php index c328e9b21..b3da6d50b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php @@ -14,6 +14,8 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\DataTransformerInterface; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\UserJobRepositoryInterface; @@ -29,6 +31,8 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class UserJobFilter implements FilterInterface, DataTransformerInterface { + use ExportDataNormalizerTrait; + private const PREFIX = 'acp_filter_user_job'; public function __construct( @@ -42,7 +46,7 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -74,12 +78,12 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn ; } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -99,7 +103,7 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn ; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.course.by_user_job.Filtered by user job: only job', [ @@ -125,6 +129,31 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn ]; } + public function normalizeFormData(array $formData): array + { + return [ + 'jobs' => $this->normalizeDoctrineEntity($formData['jobs']), + 'start_date' => $formData['start_date']->normalize(), + 'end_date' => $formData['end_date']->normalize(), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $default = $this->getFormDefaultData(); + + return [ + 'jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository), + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $default['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $default['end_date'], + ]; + } + + public function getNormalizationVersion(): int + { + return 1; + } + public function transformData(?array $before): array { $default = $this->getFormDefaultData(); diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php index 3489b8cb3..4bd8a1abb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\DataTransformerInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\ScopeRepositoryInterface; @@ -29,6 +30,7 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class UserScopeFilter implements FilterInterface, DataTransformerInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_main_scope'; public function __construct( @@ -42,7 +44,7 @@ final readonly class UserScopeFilter implements FilterInterface, DataTransformer return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -78,7 +80,7 @@ final readonly class UserScopeFilter implements FilterInterface, DataTransformer return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -96,7 +98,28 @@ final readonly class UserScopeFilter implements FilterInterface, DataTransformer ]); } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $default = $this->getFormDefaultData(); + + return [ + 'scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository), + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $default['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $default['end_date'], + ]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.course.by_user_scope.Filtered by user main scope: only scopes', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php index 85d5db9d6..30aefbce7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php @@ -12,15 +12,16 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Export\Declarations; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -31,15 +32,18 @@ use Symfony\Component\Form\FormBuilderInterface; */ final readonly class UserWorkingOnCourseFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private UserRender $userRender, private RollingDateConverterInterface $rollingDateConverter, + private UserRepositoryInterface $userRepository, ) {} public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('users', PickUserDynamicType::class, [ + ->add('users', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]) ->add('start_date', PickRollingDateType::class, [ @@ -51,6 +55,21 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['users' => $this->normalizeUserOrMe($formData['users']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['users' => $this->denormalizeUserOrMe($formData['users'], $this->userRepository), 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -65,7 +84,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface return 'export.filter.course.by_user_working.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.by_user_working.Filtered by user working on course: only %users%, between %start_date% and %end_date%', [ @@ -73,7 +92,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface ', ', array_map( fn (User $u) => $this->userRender->renderString($u, []), - $data['users'] instanceof Collection ? $data['users']->toArray() : $data['users'] + $this->userOrMe($data['users'], $context) ) ), '%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'), @@ -87,7 +106,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $ai_alias = 'user_working_on_course_filter_acc_info'; $ai_users = 'user_working_on_course_filter_users'; @@ -101,7 +120,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface "WHERE {$ai_alias}.user IN (:{$ai_users}) AND IDENTITY({$ai_alias}.accompanyingPeriod) = acp.id AND {$ai_alias}.infoDate >= :{$start} and {$ai_alias}.infoDate < :{$end}" ) ) - ->setParameter($ai_users, $data['users']) + ->setParameter($ai_users, $this->userOrMe($data['users'], $exportGenerationContext)) ->setParameter($start, $this->rollingDateConverter->convert($data['start_date'])) ->setParameter($end, $this->rollingDateConverter->convert($data['end_date'])) ; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php index 84a5814b3..79f5288fa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -35,12 +36,12 @@ final readonly class ByDateFilter implements FilterInterface private RollingDateConverterInterface $rollingDateConverter, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.step_history.by_date.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -51,6 +52,21 @@ final readonly class ByDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -59,7 +75,7 @@ final readonly class ByDateFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.step_history.by_date.description', @@ -75,7 +91,7 @@ final readonly class ByDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $startDate = 'acp_step_history_by_date_start_filter'; $endDate = 'acp_step_history_by_date_end_filter'; @@ -88,7 +104,7 @@ final readonly class ByDateFilter implements FilterInterface ->setParameter($endDate, $this->rollingDateConverter->convert($data['end_date'])); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php index 337c30a89..0ead9d2c5 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Export\Declarations; @@ -25,12 +26,12 @@ final readonly class ByStepFilter implements FilterInterface private TranslatorInterface $translator, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.step_history.by_step.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $steps = [ AccompanyingPeriod::STEP_CONFIRMED, @@ -50,6 +51,21 @@ final readonly class ByStepFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['steps' => $formData['steps']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['steps' => $formData['steps']]; + } + public function getFormDefaultData(): array { return [ @@ -57,7 +73,7 @@ final readonly class ByStepFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.step_history.by_step.description', @@ -72,14 +88,14 @@ final readonly class ByStepFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('acpstephistory.step IN (:acpstephistory_by_step_filter_steps)') ->setParameter('acpstephistory_by_step_filter_steps', $data['steps']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php index a0d8d0033..e3b254295 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class ByEndDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('workeval.endDate BETWEEN :work_eval_by_end_date_start_date and :work_eval_by_end_date_end_date') @@ -58,12 +59,27 @@ class ByEndDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by end date: between %start_date% and %end_date%', [ '%start_date%' => $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php index 401cd79a5..206597ef6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class ByStartDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('workeval.startDate BETWEEN :work_eval_by_start_date_start_date and :work_eval_by_start_date_end_date') @@ -58,12 +59,27 @@ class ByStartDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by start date: between %start_date% and %end_date%', [ '%start_date%' => $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php index 360b47f2d..11302acc4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -23,7 +24,7 @@ class CurrentEvaluationsFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere('workeval.endDate IS NULL'); } @@ -38,12 +39,27 @@ class CurrentEvaluationsFilter implements FilterInterface // no form needed } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by current evaluations']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php index 20472420c..ceed94352 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\Evaluation; @@ -22,6 +23,8 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class EvaluationTypeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct(private TranslatableStringHelper $translatableStringHelper, private EvaluationRepositoryInterface $evaluationRepository) {} public function addRole(): ?string @@ -29,7 +32,7 @@ final readonly class EvaluationTypeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere( $qb->expr()->in('workeval.evaluation', ':evaluationtype') @@ -42,7 +45,7 @@ final readonly class EvaluationTypeFilter implements FilterInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $evaluations = $this->evaluationRepository->findAllActive(); @@ -58,12 +61,27 @@ final readonly class EvaluationTypeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_evaluationtype' => $this->normalizeDoctrineEntity($formData['accepted_evaluationtype'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_evaluationtype' => $this->denormalizeDoctrineEntity($formData['accepted_evaluationtype'], $this->evaluationRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $evals = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php index 6094d56ee..3c67e7f66 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -32,7 +33,7 @@ class MaxDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (true === $data['maxdate']) { $clause = $qb->expr()->isNotNull('workeval.maxDate'); @@ -48,7 +49,7 @@ class MaxDateFilter implements FilterInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('maxdate', ChoiceType::class, [ 'choices' => self::MAXDATE_CHOICES, @@ -57,12 +58,27 @@ class MaxDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['maxdate' => $formData['maxdate']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['maxdate' => $formData['maxdate']]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by maxdate: only %choice%', [ '%choice%' => $this->translator->trans( diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php index b9f9075ed..0a1b6c3f2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\HouseholdFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -18,6 +19,7 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\Household\HouseholdCompositionType; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepositoryInterface; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -25,9 +27,12 @@ use Symfony\Component\Form\FormBuilderInterface; readonly class CompositionFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private TranslatableStringHelper $translatableStringHelper, private RollingDateConverterInterface $rollingDateConverter, + private HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository, ) {} public function addRole(): ?string @@ -35,7 +40,7 @@ readonly class CompositionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { // there is no test on the aliases here: the name should be unique $clause = @@ -78,12 +83,27 @@ readonly class CompositionFilter implements FilterInterface ->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_composition' => $this->normalizeDoctrineEntity($formData['accepted_composition']), 'on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_composition' => $this->denormalizeDoctrineEntity($formData['accepted_composition'], $this->householdCompositionTypeRepository), 'on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $compositions = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php index 8cd675abe..b46eb2853 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; @@ -30,7 +31,7 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $subQuery = 'SELECT 1 @@ -57,12 +58,12 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface ->setParameter('person_filter_having_address_to_review_ref_statuses', $data['ref_statuses'] ?? [Address::ADDR_REFERENCE_STATUS_TO_REVIEW]); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -78,12 +79,27 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize(), 'ref_statuses' => $formData['ref_statuses']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'ref_statuses' => $formData['ref_statuses']]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY), 'ref_statuses' => [Address::ADDR_REFERENCE_STATUS_TO_REVIEW]]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.person.by_address_ref_status.Filtered by person\'s address status computed at %datecalc%, only %statuses%', diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php index aa97ad54c..475e3a9fc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -32,7 +33,7 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -65,12 +66,12 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface $qb->setParameter('max_date', $maxDate); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('min_age', IntegerType::class, [ 'label' => 'Minimum age', @@ -86,6 +87,21 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['min_age' => $formData['min_age'], 'max_age' => $formData['max_age'], 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['min_age' => $formData['min_age'], 'max_age' => $formData['max_age'], 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return [ @@ -95,7 +111,7 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s age: ' .'between %min_age% and %max_age%', [ @@ -104,12 +120,12 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s age'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $min = $data['min_age']; $max = $data['max_age']; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php index dd14c71ef..55eb45211 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -31,7 +32,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -57,12 +58,12 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_from', PickRollingDateType::class, [ 'label' => 'Born after this date', @@ -73,12 +74,27 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s birthdate: ' .'between %date_from% and %date_to%', [ @@ -87,12 +103,12 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s birthdate'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $date_from = $data['date_from']; $date_to = $data['date_to']; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php index e873a6598..fb88a0087 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,6 +30,8 @@ use Symfony\Component\Form\FormBuilderInterface; class ByHouseholdCompositionFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct(private readonly HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository, private readonly RollingDateConverterInterface $rollingDateConverter, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} public function addRole(): ?string @@ -36,7 +39,7 @@ class ByHouseholdCompositionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'person_by_household_compo_filter'; @@ -55,12 +58,12 @@ class ByHouseholdCompositionFilter implements FilterInterface ->setParameter("{$p}_date", $this->rollingDateConverter->convert($data['calc_date']), Types::DATE_IMMUTABLE); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('compositions', EntityType::class, [ @@ -76,12 +79,27 @@ class ByHouseholdCompositionFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['compositions' => $this->normalizeDoctrineEntity($formData['compositions']), 'calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['compositions' => $this->denormalizeDoctrineEntity($formData['compositions'], $this->householdCompositionTypeRepository), 'calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $compos = array_map( fn (HouseholdCompositionType $compositionType) => $this->translatableStringHelper->localize($compositionType->getLabel()), @@ -94,7 +112,7 @@ class ByHouseholdCompositionFilter implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.by_composition.Filter by household composition'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php index 82cda25ed..f224e16c4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class DeadOrAliveFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -78,12 +79,12 @@ class DeadOrAliveFilter implements FilterInterface $qb->setParameter('date_calc', $calc); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('person_state', ChoiceType::class, [ 'choices' => [ @@ -99,12 +100,27 @@ class DeadOrAliveFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['person_state' => $formData['person_state'], 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['person_state' => $formData['person_state'], 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by a state of %deadOrAlive%: ' .'at this date %date_calc%', [ @@ -113,7 +129,7 @@ class DeadOrAliveFilter implements FilterInterface ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's that are alive or have deceased at a certain date"; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php index 1bc32d9bb..2d4420490 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -31,7 +32,7 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -57,12 +58,12 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac ); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_from', PickRollingDateType::class, [ 'label' => 'Death after this date', @@ -73,12 +74,27 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s deathdate: ' .'between %date_from% and %date_to%', [ @@ -87,12 +103,12 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s deathdate'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $date_from = $data['date_from']; $date_to = $data['date_to']; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php index fc09e385a..6b73275cd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\Gender; use Chill\MainBundle\Export\DataTransformerInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\GenderRepository; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -41,9 +42,8 @@ class GenderFilter implements return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { - $where = $qb->getDQLPart('where'); $isIn = $qb->expr()->in('person.gender', ':person_gender'); $acceptedGenders = $data['accepted_genders_entity']; @@ -59,12 +59,12 @@ class GenderFilter implements $qb->setParameter('person_gender', array_filter($acceptedGenders ?? [], fn ($gender) => null !== $gender)); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $genderChoices = $this->genderRepository->findByActiveOrdered(); $choices = ['None' => null]; @@ -81,6 +81,55 @@ class GenderFilter implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + if (array_key_exists('accepted_genders', $formData)) { + $genders = []; + foreach ($formData['accepted_genders'] as $beforeGender) { + foreach ($this->genderRepository->findByGenderTranslation(match ($beforeGender) { + 'both' => 'neutral', default => $beforeGender, + }) as $gender) { + if (null !== $gender) { + if ('null' === $beforeGender) { + $genders[] = null; + } + $genders[] = $gender->getId(); + } + } + } + + return ['accepted_genders_entity' => array_unique($genders)]; + } + + return ['accepted_genders_entity' => $formData['accepted_genders_entity']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + if (array_key_exists('accepted_genders', $formData)) { + $transformedData = []; + foreach ($formData['accepted_genders'] as $genderBefore) { + foreach ($this->genderRepository->findByGenderTranslation( + match ($genderBefore) { + 'both' => 'neutral', + default => $genderBefore, + } + ) as $gender) { + $transformedData[] = $gender; + } + } + + return ['accepted_genders_entity' => $transformedData]; + } + + return ['accepted_genders_entity' => $formData['accepted_genders_entity']]; + } + public function transformData(?array $before): array { $transformedData = []; @@ -107,7 +156,7 @@ class GenderFilter implements return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $genders = []; @@ -115,7 +164,11 @@ class GenderFilter implements if (null === $g) { $genders[] = $this->translator->trans('export.filter.person.gender.no_gender'); } else { - $genders[] = $this->translatableStringHelper->localize($this->genderRepository->find($g)->getLabel()); + $g = $this->genderRepository->find($g); + if (null === $g) { + continue; + } + $genders[] = $this->translatableStringHelper->localize($g->getLabel()); } } @@ -130,12 +183,12 @@ class GenderFilter implements * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person gender'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (!\is_iterable($data['accepted_genders_entity']) || 0 === \count($data['accepted_genders_entity'])) { $context->buildViolation('You should select an option') diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php index e2041c3fa..d06604af4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface; use Chill\MainBundle\Repository\GeographicalUnitRepositoryInterface; @@ -25,16 +26,21 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; -class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface +final readonly class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface { - public function __construct(private readonly GeographicalUnitRepositoryInterface $geographicalUnitRepository, private readonly GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly RollingDateConverterInterface $rollingDateConverter) {} + public function __construct( + private GeographicalUnitRepositoryInterface $geographicalUnitRepository, + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $subQuery = 'SELECT 1 @@ -63,12 +69,12 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface ->setParameter('person_filter_geog_units', array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $data['units'])); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -88,12 +94,38 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'date_calc' => $formData['date_calc']->normalize(), + 'units' => array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $formData['units']), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $units = array_filter( + array_map( + fn (int $id) => $this->geographicalUnitRepository->findSimpleGeographicalUnit($id), + $formData['units'], + ), + static fn (?SimpleGeographicalUnitDTO $unitDTO) => null !== $unitDTO, + ); + + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'units' => $units]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.by_geog_unit.Filtered by person\'s geographical unit (based on address) computed at %datecalc%, only %units%', diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php index 6c4cdf802..f7c8865a5 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php @@ -11,22 +11,26 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\MaritalStatus; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\MaritalStatusRepositoryInterface; use Symfony\Bridge\Doctrine\Form\Type\EntityType; -class MaritalStatusFilter implements FilterInterface +final readonly class MaritalStatusFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private MaritalStatusRepositoryInterface $maritalStatusRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere( $qb->expr()->in('person.maritalStatus', ':maritalStatus') @@ -34,12 +38,12 @@ class MaritalStatusFilter implements FilterInterface $qb->setParameter('maritalStatus', $data['maritalStatus']); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('maritalStatus', EntityType::class, [ 'class' => MaritalStatus::class, @@ -51,17 +55,32 @@ class MaritalStatusFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['maritalStatus' => $this->normalizeDoctrineEntity($formData['maritalStatus'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['maritalStatus' => $this->denormalizeDoctrineEntity($formData['maritalStatus'], $this->maritalStatusRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s marital status']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s marital status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php index 4c83fafe3..ad1818f4b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php @@ -13,8 +13,10 @@ namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\Select2CountryType; +use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr; @@ -22,18 +24,20 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -class NationalityFilter implements +final readonly class NationalityFilter implements ExportElementValidatedInterface, FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private CountryRepository $countryRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('person.nationality', ':person_nationality'); @@ -48,24 +52,39 @@ class NationalityFilter implements $qb->setParameter('person_nationality', [$data['nationalities']]); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('nationalities', Select2CountryType::class, [ 'placeholder' => 'Choose countries', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['nationalities' => $this->normalizeDoctrineEntity($formData['nationalities'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['nationalities' => $this->denormalizeDoctrineEntity($formData['nationalities'], $this->countryRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $countries = $data['nationalities']; @@ -77,12 +96,12 @@ class NationalityFilter implements ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's nationality"; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['nationalities']) { $context->buildViolation('A nationality must be selected') diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php index ea8e01baf..36f62e3c9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -19,21 +20,28 @@ use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\Person\ResidentialAddress; use Chill\PersonBundle\Export\Declarations; use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory; +use Chill\ThirdPartyBundle\Repository\ThirdPartyCategoryRepository; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class ResidentialAddressAtThirdpartyFilter implements FilterInterface +final readonly class ResidentialAddressAtThirdpartyFilter implements FilterInterface { - public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter, private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private RollingDateConverterInterface $rollingDateConverter, + private TranslatableStringHelper $translatableStringHelper, + private ThirdPartyCategoryRepository $thirdPartyCategoryRepository, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('resaddr', $qb->getAllAliases(), true)) { $qb->join(ResidentialAddress::class, 'resaddr', Expr\Join::WITH, 'resaddr.person = person'); @@ -79,12 +87,12 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface $qb->add('where', $where); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('thirdparty_cat', EntityType::class, [ 'class' => ThirdPartyCategory::class, @@ -99,12 +107,27 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['thirdparty_cat' => $this->normalizeDoctrineEntity($formData['thirdparty_cat']), 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['thirdparty_cat' => $this->denormalizeDoctrineEntity($formData['thirdparty_cat'], $this->thirdPartyCategoryRepository), 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%', [ '%thirdparty_type%' => $this->translatableStringHelper->localize($data['thirdparty_cat'][0]->getName()), @@ -112,7 +135,7 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's who have a residential address located at a thirdparty of type"; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php index 3c2d7a3a6..12b6fac8e 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class ResidentialAddressAtUserFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('resaddr', $qb->getAllAliases(), true)) { $qb->join(ResidentialAddress::class, 'resaddr', Expr\Join::WITH, 'resaddr.person = person'); @@ -66,29 +67,44 @@ class ResidentialAddressAtUserFilter implements FilterInterface $qb->add('where', $where); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('date_calc', PickRollingDateType::class, [ 'label' => 'Date during which residential address was valid', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ["Filtered by person's who have a residential address located at another user"]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's who have a residential address located at another user"; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php index 289af5f07..792851137 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -32,7 +33,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'with_participation_between_dates_filter'; @@ -54,7 +55,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_after', PickRollingDateType::class, [ 'label' => 'export.filter.person.with_participation_between_dates.date_after', @@ -65,6 +66,21 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_after' => $formData['date_after']->normalize(), 'date_before' => $formData['date_before']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_after' => RollingDate::fromNormalized($formData['date_after']), 'date_before' => RollingDate::fromNormalized($formData['date_before'])]; + } + public function getFormDefaultData(): array { return [ @@ -73,7 +89,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.person.with_participation_between_dates.Filtered by participations during period: between %dateafter% and %datebefore%', [ '%dateafter%' => $this->rollingDateConverter->convert($data['date_after'])->format('d-m-Y'), @@ -81,7 +97,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.with_participation_between_dates.title'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php index c34c7eb40..340aa041f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -31,7 +32,7 @@ class WithoutHouseholdComposition implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'person_by_household_no_compo_filter'; @@ -50,12 +51,12 @@ class WithoutHouseholdComposition implements FilterInterface ->setParameter("{$p}_date", $this->rollingDateConverter->convert($data['calc_date']), Types::DATE_IMMUTABLE); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('calc_date', PickRollingDateType::class, [ @@ -63,19 +64,34 @@ class WithoutHouseholdComposition implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.person.by_no_composition.Persons filtered by no composition at %date%', [ '%date%' => $this->rollingDateConverter->convert($data['calc_date'])->format('d-m-Y'), ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.by_no_composition.Filter persons without household composition'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php index d1e3e9b2f..fbd8f2acb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -32,7 +33,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'without_participation_between_dates_filter'; @@ -56,7 +57,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_after', PickRollingDateType::class, [ 'label' => 'export.filter.person.without_participation_between_dates.date_after', @@ -67,6 +68,21 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_after' => $formData['date_after']->normalize(), 'date_before' => $formData['date_before']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_after' => RollingDate::fromNormalized($formData['date_after']), 'date_before' => RollingDate::fromNormalized($formData['date_before'])]; + } + public function getFormDefaultData(): array { return [ @@ -75,7 +91,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['exports.filter.person.without_participation_between_dates.Filtered by having no participations during period: between', [ 'dateafter' => $this->rollingDateConverter->convert($data['date_after']), @@ -83,7 +99,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.without_participation_between_dates.title'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php index 05d4633e2..801c64df2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -41,9 +42,28 @@ final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements F ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize(), 'keep_null' => $formData['keep_null']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'start_date' => RollingDate::fromNormalized($formData['start_date']), + 'end_date' => RollingDate::fromNormalized($formData['end_date']), + 'keep_null' => (bool) ($formData['keep_null'] ?? true), + ]; + } + public function getFormDefaultData(): array { - return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; + return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY), 'keep_null' => true]; } public function getTitle(): string @@ -51,7 +71,7 @@ final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements F return 'export.filter.work.end_between_dates.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.end_between_dates.Only where start date is between %startDate% and %endDate%', @@ -67,7 +87,7 @@ final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements F return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $as = 'acc_pe_work_end_between_filter_start'; $ae = 'acc_pe_work_end_between_filter_end'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php index 880543355..b061ab550 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -41,9 +42,28 @@ final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize(), 'keep_null' => $formData['keep_null']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'start_date' => RollingDate::fromNormalized($formData['start_date']), + 'end_date' => RollingDate::fromNormalized($formData['end_date']), + 'keep_null' => (bool) ($formData['keep_null'] ?? true), + ]; + } + public function getFormDefaultData(): array { - return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; + return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY), 'keep_null' => true]; } public function getTitle(): string @@ -51,7 +71,7 @@ final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements return 'export.filter.work.start_between_dates.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.start_between_dates.Only where start date is between %startDate% and %endDate%', @@ -67,7 +87,7 @@ final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $as = 'acc_pe_work_start_between_filter_start'; $ae = 'acc_pe_work_start_between_filter_end'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php index e0c4da1e9..9bca9bc26 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -37,6 +38,21 @@ final readonly class AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter impl ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -47,7 +63,7 @@ final readonly class AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter impl return 'export.filter.work.evaluation_between_dates.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.evaluation_between_dates.description', @@ -63,7 +79,7 @@ final readonly class AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter impl return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $s = 'workeval_between_filter_start'; $e = 'workeval_between_filter_end'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php index c990e658e..36f7989cc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php @@ -12,30 +12,35 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class CreatorFilter implements FilterInterface +final readonly class CreatorFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_filter_creator'; + public function __construct(private UserRepositoryInterface $userRepository) {} + public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; $qb ->leftJoin('acpw.createdBy', "{$p}_creator") ->andWhere($qb->expr()->in("{$p}_creator", ":{$p}_creators")) - ->setParameter("{$p}_creators", $data['creators']); + ->setParameter("{$p}_creators", $this->userOrMe($data['creators'], $exportGenerationContext)); } public function applyOn(): string @@ -43,23 +48,38 @@ class CreatorFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('creators', PickUserDynamicType::class, [ + ->add('creators', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'export.filter.work.by_creator.Creators', ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['creators' => $this->normalizeUserOrMe($formData['creators'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['creators' => $this->denormalizeUserOrMe($formData['creators'], $this->userRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.by_creator.Filtered by creator: only %creators%', [ '%creators%' => implode( ', ', array_map( - static fn (User $u) => $u->getLabel(), + fn (User|string $u) => $this->userOrMe($u, $context)->getLabel(), $data['creators'] instanceof Collection ? $data['creators']->toArray() : $data['creators'] ) ), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php index 3087d156e..fe6eac7e9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -22,13 +23,14 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class CreatorJobFilter implements FilterInterface +final readonly class CreatorJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_filter_creator_job'; public function __construct( - private readonly UserJobRepository $userJobRepository, - private readonly TranslatableStringHelper $translatableStringHelper, + private UserJobRepository $userJobRepository, + private TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string @@ -36,7 +38,7 @@ class CreatorJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class CreatorJobFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -78,7 +80,22 @@ class CreatorJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $creatorJobs = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php index 414740c7a..d6185c287 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; class CreatorScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_filter_creator_scope'; public function __construct( @@ -36,7 +38,7 @@ class CreatorScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class CreatorScopeFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -78,7 +80,22 @@ class CreatorScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $creatorScopes = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php index b11d2a93c..38da6bd7c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -23,7 +24,7 @@ class CurrentActionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere('acpw.endDate IS NULL'); } @@ -38,12 +39,27 @@ class CurrentActionFilter implements FilterInterface // no form } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered actions without end date']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php index 25e762a9c..4da612506 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php @@ -12,25 +12,25 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; -use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; -class JobFilter implements FilterInterface +final readonly class JobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_work_action_filter_user_job'; public function __construct( - protected TranslatorInterface $translator, - private readonly TranslatableStringHelper $translatableStringHelper, - private readonly UserJobRepositoryInterface $userJobRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string @@ -38,7 +38,7 @@ class JobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -57,7 +57,7 @@ class JobFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('job', EntityType::class, [ @@ -72,7 +72,22 @@ class JobFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['job' => $this->normalizeDoctrineEntity($formData['job'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['job' => $this->denormalizeDoctrineEntity($formData['job'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $userjobs = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php index 4d456fdb2..1b3b3a6c6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\PersonBundle\Export\Declarations; @@ -22,16 +24,17 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class ReferrerFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_referrer_filter'; - public function __construct(private RollingDateConverterInterface $rollingDateConverter) {} + public function __construct(private RollingDateConverterInterface $rollingDateConverter, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -41,7 +44,7 @@ final readonly class ReferrerFilter implements FilterInterface ->andWhere("{$p}_acpwusers_history.user IN (:{$p}_agents)"); $qb - ->setParameter("{$p}_agents", $data['accepted_agents']) + ->setParameter("{$p}_agents", $this->userOrMe($data['accepted_agents'], $exportGenerationContext)) ->setParameter("{$p}_calc_date", $this->rollingDateConverter->convert( $data['agent_at'] ?? new RollingDate(RollingDate::T_TODAY) )) @@ -53,10 +56,10 @@ final readonly class ReferrerFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_agents', PickUserDynamicType::class, [ + ->add('accepted_agents', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'export.filter.work.by_treating_agent.Accepted agents', ]) @@ -67,6 +70,27 @@ final readonly class ReferrerFilter implements FilterInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'accepted_agents' => $this->normalizeUserOrMe($formData['accepted_agents']), + 'agent_at' => array_key_exists('agent_at', $formData) ? $formData['agent_at']?->normalize() : (new RollingDate(RollingDate::T_TODAY))->normalize(), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'accepted_agents' => $this->denormalizeUserOrMe($formData['accepted_agents'], $this->userRepository), + 'agent_at' => array_key_exists('agent_at', $formData) ? RollingDate::fromNormalized($formData['agent_at']) : new RollingDate(RollingDate::T_TODAY), + ]; + } + public function getFormDefaultData(): array { return [ @@ -75,11 +99,11 @@ final readonly class ReferrerFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_agents'] as $r) { + foreach ($this->userOrMe($data['accepted_agents'], $context) as $r) { $users[] = $r; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php index 9f2041aaa..42c666879 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -21,16 +22,15 @@ use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; -class ScopeFilter implements FilterInterface +final readonly class ScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_work_action_filter_user_scope'; public function __construct( - protected TranslatorInterface $translator, - private readonly TranslatableStringHelper $translatableStringHelper, - private readonly ScopeRepositoryInterface $scopeRepository, + private TranslatableStringHelper $translatableStringHelper, + private ScopeRepositoryInterface $scopeRepository, ) {} public function addRole(): ?string @@ -38,7 +38,7 @@ class ScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -51,12 +51,12 @@ class ScopeFilter implements FilterInterface ->setParameter("{$p}_scope", $data['scope']); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scope', EntityType::class, [ @@ -71,7 +71,22 @@ class ScopeFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scope' => $this->normalizeDoctrineEntity($formData['scope'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scope' => $this->denormalizeDoctrineEntity($formData['scope'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $scopes = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php index c84ed7f40..85ea60f3d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\Goal; @@ -26,14 +28,20 @@ use Symfony\Component\Form\FormBuilderInterface; class SocialWorkTypeFilter implements FilterInterface { - public function __construct(private readonly SocialActionRender $socialActionRender, private readonly TranslatableStringHelper $translatableStringHelper, private readonly EntityManagerInterface $em) {} + use ExportDataNormalizerTrait; + + public function __construct( + private readonly SocialActionRender $socialActionRender, + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly EntityManagerInterface $em, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (\count($data['actionType']) > 0) { $qb @@ -73,7 +81,7 @@ class SocialWorkTypeFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('actionType', HiddenType::class) @@ -95,12 +103,44 @@ class SocialWorkTypeFilter implements FilterInterface ); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'actionType' => $this->normalizeDoctrineEntity($formData['actionType']), + 'goal' => $this->normalizeDoctrineEntity($formData['goal']), + 'result' => $this->normalizeDoctrineEntity($formData['result']), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'actionType' => $this->denormalizeDoctrineEntity($this->denormalizeStringRepresentation($formData['actionType']), $this->em->getRepository(SocialAction::class)), + 'goal' => $this->denormalizeDoctrineEntity($this->denormalizeStringRepresentation($formData['goal']), $this->em->getRepository(Goal::class)), + 'result' => $this->denormalizeDoctrineEntity($this->denormalizeStringRepresentation($formData['result']), $this->em->getRepository(Result::class)), + ]; + } + + private function denormalizeStringRepresentation(array|string $ids): array + { + if (is_array($ids)) { + return $ids; + } + + return array_map(fn (string $id) => (int) $id, explode(',', $ids)); + } + public function getFormDefaultData(): array { return ['action_type' => [], 'goal' => [], 'result' => []]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $actionTypes = []; $goals = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php index 2c42d931f..e631051a0 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php @@ -13,13 +13,12 @@ namespace Chill\PersonBundle\Export\Helper; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\QueryBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; -use Symfony\Component\Security\Core\Security; /** * Filter accompanying period list and related, removing confidential ones @@ -30,27 +29,20 @@ final readonly class FilterListAccompanyingPeriodHelper implements FilterListAcc private bool $filterStatsByCenters; public function __construct( - private Security $security, private CenterRepositoryInterface $centerRepository, - private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, + private AuthorizationHelperInterface $authorizationHelper, ParameterBagInterface $parameterBag, ) { $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, array $data = []): void + public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, User $user, array $data = []): void { $centers = match ($this->filterStatsByCenters) { true => array_map(static fn ($el) => $el['center'], $acl), false => $this->centerRepository->findAll(), }; - $user = $this->security->getUser(); - - if (!$user instanceof User) { - throw new \RuntimeException('only a regular user can run this export'); - } - // add filtering on confidential accompanying period. The confidential is applyed on the current status of // the accompanying period (we do not use the 'calc_date' here $aclConditionsOrX = $qb->expr()->orX( @@ -61,22 +53,23 @@ final readonly class FilterListAccompanyingPeriodHelper implements FilterListAcc $i = 0; foreach ($centers as $center) { - $scopes = $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, $center); + $scopes = $this->authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_DETAILS, $center); $scopesConfidential = - $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center); + $this->authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center); $orScopes = $qb->expr()->orX(); foreach ($scopes as $scope) { $scopeCondition = match (in_array($scope, $scopesConfidential, true)) { - true => ":scope_{$i} MEMBER OF acp.scopes", + true => sprintf(':scope_%s MEMBER OF acp.scopes', $i), false => $qb->expr()->andX( 'acp.confidential = FALSE', - ":scope_{$i} MEMBER OF acp.scopes", + sprintf(':scope_%s MEMBER OF acp.scopes', $i), ), }; - $orScopes->add($scopeCondition); + $orScopes->add($qb->expr()->orX(sprintf('acp.user = :user_%d', $i), $scopeCondition)); $qb->setParameter("scope_{$i}", $scope); + $qb->setParameter("user_{$i}", $user); ++$i; } diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php index cde6aceec..eafa6b8fb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Helper; +use Chill\MainBundle\Entity\User; use Doctrine\ORM\QueryBuilder; /** @@ -19,5 +20,5 @@ use Doctrine\ORM\QueryBuilder; */ interface FilterListAccompanyingPeriodHelperInterface { - public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, array $data = []): void; + public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, User $user, array $data = []): void; } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php b/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php index 19258f23d..d6115bd26 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php @@ -16,6 +16,7 @@ use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class PickSocialActionType extends AbstractType @@ -28,14 +29,23 @@ class PickSocialActionType extends AbstractType ->setDefaults([ 'class' => SocialAction::class, 'choices' => $this->actionRepository->findAllActive(), - 'choice_label' => fn (SocialAction $sa) => $this->actionRender->renderString($sa, []), 'placeholder' => 'Pick a social action', 'required' => false, 'attr' => ['class' => 'select2'], 'label' => 'Social actions', 'multiple' => false, + 'show_social_issue_parenthesis' => false, + 'show_deactivated' => false, ]) - ->setAllowedTypes('multiple', ['bool']); + ->setNormalizer('choice_label', fn (Options $options, $value) => fn (SocialAction $sa) => $this->actionRender->renderString( + $sa, + [ + SocialActionRender::SHOW_SOCIAL_ISSUE => $options['show_social_issue_parenthesis'], + SocialActionRender::SHOW_DEACTIVATED => $options['show_deactivated'], + ] + )) + ->setAllowedTypes('multiple', ['bool']) + ->setAllowedTypes('show_social_issue_parenthesis', ['bool']); } public function getParent(): string diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php index 2e50386c8..726418b12 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php @@ -12,15 +12,16 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\EntityRepository; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; -final readonly class OriginRepository +/** + * @extends ServiceEntityRepository + */ +final class OriginRepository extends ServiceEntityRepository { - private EntityRepository $repository; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(ManagerRegistry $registry) { - $this->repository = $entityManager->getRepository(Origin::class); + parent::__construct($registry, Origin::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php index 1f51060e9..d5ff1c6bd 100644 --- a/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php @@ -12,8 +12,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\MaritalStatus; +use Doctrine\Persistence\ObjectRepository; -interface MaritalStatusRepositoryInterface +interface MaritalStatusRepositoryInterface extends ObjectRepository { public function find($id): ?MaritalStatus; diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php index 4e609a620..3db5ac709 100644 --- a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php @@ -147,7 +147,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH return []; } - protected function supports($attribute, $subject) + protected function supports($attribute, $subject): bool { return $this->voterHelper->supports($attribute, $subject); } diff --git a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php index 657ed0f53..13e93cf22 100644 --- a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php +++ b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Templating\Entity; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\SocialAction; +use Symfony\Component\Clock\ClockInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -28,6 +29,8 @@ class SocialActionRender implements ChillEntityRenderInterface self::NO_BADGE => false, self::SHOW_AND_CHILDREN => false, self::AND_CHILDREN_MENTION => 'social_action.and children', + self::SHOW_SOCIAL_ISSUE => false, + self::SHOW_DEACTIVATED => false, ]; /** @@ -43,7 +46,27 @@ class SocialActionRender implements ChillEntityRenderInterface */ final public const SHOW_AND_CHILDREN = 'show_and_children'; - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {} + /** + * Append the related social issue next to the social action name, in parenthesis. + * + * Currently only in string rendering. + */ + final public const SHOW_SOCIAL_ISSUE = 'show_social_issue'; + + /** + * Append a mention "deactivated" next to the social action name, in parenthesis, if the social action is deactivated. + * + * Currently only in string rendering. + */ + final public const SHOW_DEACTIVATED = 'show_deactivated'; + + public function __construct( + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly \Twig\Environment $engine, + private readonly TranslatorInterface $translator, + private readonly SocialIssueRender $socialIssueRender, + private readonly ClockInterface $clock, + ) {} public function renderBox($socialAction, array $options): string { @@ -79,6 +102,14 @@ class SocialActionRender implements ChillEntityRenderInterface $title .= ' ('.$this->translator->trans($options[self::AND_CHILDREN_MENTION]).')'; } + if ($options[self::SHOW_SOCIAL_ISSUE]) { + $title .= ' ('.$this->socialIssueRender->renderString($socialAction->getIssue(), []).')'; + } + + if ($options[self::SHOW_DEACTIVATED] && $socialAction->isDesactivated(\DateTime::createFromImmutable($this->clock->now()))) { + $title .= ' ('.$this->translator->trans('Disabled').')'; + } + return $title; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php index 738769b7b..848cee27e 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php @@ -23,6 +23,7 @@ use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ReferrerS use Doctrine\ORM\EntityManagerInterface; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\Clock\MockClock; /** * @internal @@ -49,7 +50,7 @@ final class ReferrerScopeAggregatorTest extends AbstractAggregatorTest return new ReferrerScopeAggregator( $scopeRepository->reveal(), $translatableStringHelper->reveal(), - new RollingDateConverter(), + new RollingDateConverter(new MockClock()), ); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php index fd1f0ffc5..f8ba356d2 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php @@ -60,4 +60,29 @@ final class ReferrerAggregatorTest extends AbstractAggregatorTest ->from(AccompanyingPeriodWork::class, 'acpw'), ]; } + + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $aggregator = $this->getAggregator(); + + $normalized = $aggregator->normalizeFormData($data); + $actual = $aggregator->denormalizeFormData($normalized, $version); + + self::assertIsArray($actual); + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (self::getFormData() as $data) { + yield [$data, 1, []]; + } + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php index 220ba0ad7..181d24548 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php @@ -15,7 +15,7 @@ use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Test\Export\AbstractExportTest; @@ -27,7 +27,6 @@ use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\EntityManagerInterface; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\Security\Core\Security; /** * @internal @@ -57,22 +56,13 @@ class ListAccompanyingPeriodTest extends AbstractExportTest $centerRepository = self::getContainer()->get(CenterRepositoryInterface::class); $scopeRepository = self::getContainer()->get(ScopeRepositoryInterface::class); - // mock security - $user = $em->createQuery('SELECT u FROM '.User::class.' u') - ->setMaxResults(1)->getSingleResult(); - if (null === $user) { - throw new \RuntimeException('no user found'); - } - $security = $this->prophesize(Security::class); - $security->getUser()->willReturn($user); - // mock authorization helper $scopes = $scopeRepository->findAll(); $scopesConfidentials = [] !== $scopes ? [$scopes[0]] : []; - $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableScopes(Argument::type(User::class), AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) ->willReturn($scopes); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) + $authorizationHelper->getReachableScopes(Argument::type(User::class), AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) ->willReturn($scopesConfidentials); yield new ListAccompanyingPeriod( @@ -80,7 +70,6 @@ class ListAccompanyingPeriodTest extends AbstractExportTest $rollingDateConverter, $listAccompanyingPeriodHelper, new FilterListAccompanyingPeriodHelper( - $security->reveal(), $centerRepository, $authorizationHelper->reveal(), $this->getParameters(true) @@ -92,7 +81,6 @@ class ListAccompanyingPeriodTest extends AbstractExportTest $rollingDateConverter, $listAccompanyingPeriodHelper, new FilterListAccompanyingPeriodHelper( - $security->reveal(), $centerRepository, $authorizationHelper->reveal(), $this->getParameters(false) diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php index 8415e2017..ae74c6fca 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace src\Bundle\ChillPersonBundle\Tests\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\Entity\UserRender; @@ -30,17 +31,20 @@ class ReferrerFilterBetweenDatesTest extends AbstractFilterTest private RollingDateConverterInterface $rollingDateConverter; private UserRender $userRender; + private UserRepositoryInterface $userRepository; + protected function setUp(): void { parent::setUp(); self::bootKernel(); $this->rollingDateConverter = self::getContainer()->get(RollingDateConverterInterface::class); $this->userRender = self::getContainer()->get(UserRender::class); + $this->userRepository = self::getContainer()->get(UserRepositoryInterface::class); } public function getFilter() { - return new ReferrerFilterBetweenDates($this->rollingDateConverter, $this->userRender); + return new ReferrerFilterBetweenDates($this->rollingDateConverter, $this->userRender, $this->userRepository); } public static function getFormData(): array @@ -61,6 +65,11 @@ class ReferrerFilterBetweenDatesTest extends AbstractFilterTest 'start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'end_date' => new RollingDate(RollingDate::T_TODAY), ], + [ + 'accepted_referrers' => 'me', + 'start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + ], ]; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php index 45bb6df46..73088a9d4 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php @@ -55,6 +55,8 @@ final class ReferrerFilterTest extends AbstractFilterTest $data[] = ['accepted_referrers' => $u, 'date_calc' => new RollingDate(RollingDate::T_TODAY)]; } + $data[] = ['accepted_referrers' => 'me', 'date_calc' => new RollingDate(RollingDate::T_TODAY)]; + return $data; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterOnDateTest.php similarity index 96% rename from src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php rename to src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterOnDateTest.php index c0c2ec249..ad12a48ef 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterOnDateTest.php @@ -22,7 +22,7 @@ use Doctrine\ORM\EntityManagerInterface; * * @coversNothing */ -final class StepFilterTest extends AbstractFilterTest +final class StepFilterOnDateTest extends AbstractFilterTest { private StepFilterOnDate $filter; diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php index 7273e9abc..baf46de26 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php @@ -41,17 +41,44 @@ final class GenderFilterTest extends AbstractFilterTest { return [ [ - 'accepted_genders' => ['man'], - ], - [ - 'accepted_genders' => ['woman'], - ], - [ - 'accepted_genders' => ['man', 'both'], + 'accepted_genders_entity' => [null, '1'], ], ]; } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $filter = $this->getFilter(); + + $normalized = $filter->normalizeFormData($data); + $actual = $filter->denormalizeFormData($normalized, $version); + + self::assertIsArray($actual); + self::assertArrayHasKey('accepted_genders_entity', $actual); + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + yield [ + ['accepted_genders_entity' => [null, '1']], + 1, + [], + ]; + yield [ + ['accepted_genders' => ['man']], + 1, + [], + ]; + } + public static function getQueryBuilders(): iterable { self::bootKernel(); diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php index e7c4ee8e6..ae7a6c244 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php @@ -50,6 +50,9 @@ class CreatorFilterTest extends AbstractFilterTest [ 'creators' => $creators, ], + [ + 'creators' => [...$creators, 'me'], + ], ]; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php index 46a1a95e1..9e071adb1 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\HandlingThirdPartyFilter; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\EntityManagerInterface; @@ -27,16 +28,19 @@ class HandlingThirdPartyFilterTest extends AbstractFilterTest { private ThirdPartyRender $thirdPartyRender; + private ThirdPartyRepository $thirdPartyRepository; + protected function setUp(): void { parent::setUp(); self::bootKernel(); $this->thirdPartyRender = self::getContainer()->get(ThirdPartyRender::class); + $this->thirdPartyRepository = self::getContainer()->get(ThirdPartyRepository::class); } public function getFilter() { - return new HandlingThirdPartyFilter($this->thirdPartyRender); + return new HandlingThirdPartyFilter($this->thirdPartyRender, $this->thirdPartyRepository); } public static function getFormData(): array diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php index d185ac499..f298dea79 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php @@ -56,9 +56,57 @@ final class ReferrerFilterTest extends AbstractFilterTest ]; } + $data[] = [ + 'accepted_agents' => 'me', + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ]; + return $data; } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $expected): void + { + $filter = $this->getFilter(); + + $normalized = $filter->normalizeFormData($data); + $actual = $filter->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($expected), array_keys($actual)); + } + + public static function dataProviderFormDataToNormalize(): iterable + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $users = array_slice($em->getRepository(User::class)->findAll(), 0, 1); + + yield [ + [ + 'accepted_agents' => [$users[0]], + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ], + 1, + [ + 'accepted_agents' => [$users[0]], + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + + yield [ + [ + 'accepted_agents' => [$users[0]], + ], + 1, + [ + 'accepted_agents' => [$users[0]], + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + public static function getQueryBuilders(): iterable { self::bootKernel(); diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php index 0cdbea864..68df9b427 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php @@ -16,7 +16,7 @@ use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Export\Helper\FilterListAccompanyingPeriodHelper; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; @@ -63,26 +63,23 @@ final class FilterListAccompanyingPeriodHelperTest extends KernelTestCase if (null === $user) { throw new \RuntimeException('no user found'); } - $security = $this->prophesize(Security::class); - $security->getUser()->willReturn($user); // mock authorization helper $scopes = $this->scopeRepository->findAll(); $scopesConfidentials = [] !== $scopes ? [$scopes[0]] : []; - $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) ->willReturn($scopes); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) + $authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) ->willReturn($scopesConfidentials); $filter = new FilterListAccompanyingPeriodHelper( - $security->reveal(), $this->centerRepository, $authorizationHelper->reveal(), $parameterBag ); - $filter->addFilterAccompanyingPeriods($qb, [], $this->getACL(), []); + $filter->addFilterAccompanyingPeriods($qb, [], $this->getACL(), $user, []); $qb->setMaxResults(1); $result = $qb->getQuery()->getResult(); diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php index 82a2d1d40..306889547 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php @@ -18,7 +18,7 @@ class AccompanyingPeriodValidity extends Constraint { public $messageSocialIssueCannotBeDeleted = 'The social %name% issue cannot be deleted because it is associated with an activity or an action'; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php index 97e5be385..31525b939 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php @@ -18,7 +18,10 @@ class ConfidentialCourseMustHaveReferrer extends Constraint { public string $message = 'A confidential parcours must have a referrer'; - public function getTargets() + /** + * @return array + */ + public function getTargets(): array { return [self::CLASS_CONSTRAINT]; } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php index 8e832da6e..74e5dafcd 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php @@ -20,7 +20,7 @@ class LocationValidity extends Constraint public $messagePersonLocatedMustBeAssociated = "The person where the course is located must be associated to the course. Change course's location before removing the person."; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php index 5f45f69ac..b30a389c8 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php @@ -20,7 +20,7 @@ class MaxHolder extends Constraint public $messageInfinity = 'household.max_holder_overflowed_infinity'; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php index 1761e3522..da91602de 100644 --- a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php +++ b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php @@ -26,15 +26,21 @@ use Chill\PersonBundle\Export\Declarations; use Chill\ReportBundle\Entity\Report; use Chill\ReportBundle\Security\Authorization\ReportVoter; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NativeQuery; use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; 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\TranslatorInterface; +/** + * @template-implements ListInterface, address_date: \DateTime}> + */ class ReportList implements ExportElementValidatedInterface, ListInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; protected array $fields = [ 'person_id', 'person_firstName', 'person_lastName', 'person_birthdate', 'person_placeOfBirth', 'person_gender', 'person_memo', 'person_email', 'person_phonenumber', @@ -97,6 +103,21 @@ class ReportList implements ExportElementValidatedInterface, ListInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'address_date' => $this->normalizeDate($formData['address_date'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'address_date' => $this->denormalizeDate($formData['address_date'])]; + } + public function getFormDefaultData(): array { return ['address_date' => new \DateTime()]; @@ -253,7 +274,7 @@ class ReportList implements ExportElementValidatedInterface, ListInterface } } - public function getQueryKeys($data) + public function getQueryKeys($data): array { $fields = []; @@ -267,12 +288,12 @@ class ReportList implements ExportElementValidatedInterface, ListInterface return [...$fields, ...\array_keys($this->slugs)]; } - public function getResult($query, $data) + public function getResult(QueryBuilder|NativeQuery $query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return $this->translator->trans( "List for report '%type%'", @@ -282,12 +303,12 @@ class ReportList implements ExportElementValidatedInterface, ListInterface ); } - public function getType() + public function getType(): string { return 'report'; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); @@ -413,7 +434,7 @@ class ReportList implements ExportElementValidatedInterface, ListInterface return [Declarations::PERSON_TYPE, 'report']; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { // get the field starting with address_ $addressFields = array_filter( diff --git a/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php b/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php index fcc21e726..55218f3ce 100644 --- a/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php +++ b/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php @@ -53,7 +53,7 @@ class ReportListProvider implements ExportElementsProviderInterface $this->customFieldProvider = $customFieldProvider; } - public function getExportElements() + public function getExportElements(): iterable { $groups = $this->em->getRepository(CustomFieldsGroup::class) ->findBy(['entity' => Report::class]); diff --git a/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php b/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php index 0346f2ab3..bbfcdbb1b 100644 --- a/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php +++ b/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ReportBundle\Export\Filter; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -26,7 +27,7 @@ class ReportDateFilter implements FilterInterface return null; } - public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -52,12 +53,12 @@ class ReportDateFilter implements FilterInterface ); } - public function applyOn() + public function applyOn(): string { return 'report'; } - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('date_from', PickRollingDateType::class, [ 'label' => 'Report is after this date', @@ -68,12 +69,27 @@ class ReportDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by report\'s date: ' .'between %date_from% and %date_to%', [ @@ -82,7 +98,7 @@ class ReportDateFilter implements FilterInterface ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by report\'s date'; } diff --git a/utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php b/utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php new file mode 100644 index 000000000..e4897f8f3 --- /dev/null +++ b/utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php @@ -0,0 +1,359 @@ +classAnalyzer->hasImplements($node, FilterInterface::class) + && !$this->classAnalyzer->hasImplements($node, AggregatorInterface::class) + && !$this->classAnalyzer->hasImplements($node, ExportInterface::class) + && !$this->classAnalyzer->hasImplements($node, DirectExportInterface::class) + && !$this->classAnalyzer->hasImplements($node, ListInterface::class) + && !$this->classAnalyzer->hasImplements($node, FormatterInterface::class) + ) { + return null; + } + + $hasDenormalizeMethod = false; + $hasNormalizedMethod = false; + $hasGetVersionMethod = false; + $buildFormStmtIndex = null; + $buildForm = null; + $hasTraitHelper = false; + + foreach ($node->stmts as $k => $stmt) { + if ($stmt instanceof Node\Stmt\TraitUse) { + $trait = $stmt->traits[0]; + + if (str_contains($trait->toString(), ExportDataNormalizerTrait::class)) { + $hasTraitHelper = true; + } + + continue; + } + if (!$stmt instanceof Node\Stmt\ClassMethod) { + continue; + } + + if ('buildForm' === $stmt->name->name) { + $buildForm = $stmt; + $buildFormStmtIndex = $k; + } elseif ('normalizeFormData' === $stmt->name->name) { + $hasNormalizedMethod = true; + } elseif ('denormalizeFormData' === $stmt->name->name) { + $hasDenormalizeMethod = true; + } elseif ('getNormalizationVersion' === $stmt->name->name) { + $hasGetVersionMethod = true; + } + } + + if ($hasDenormalizeMethod && $hasNormalizedMethod && $hasGetVersionMethod) { + return null; + } + + $toAddBefore = []; + $toAdd = []; + + $stmtBefore = array_slice($node->stmts, 0, $buildFormStmtIndex, false); + $stmtAfter = array_slice($node->stmts, $buildFormStmtIndex + 1); + $propertiesOnForm = $this->getPropertiesOnForm($buildForm); + + // if the trait is not present, we add it into the statements + if ( + !$hasTraitHelper + && (!$hasNormalizedMethod || !$hasDenormalizeMethod) + && array_reduce( + $propertiesOnForm, + function (bool $carry, string $item): bool { + if ('entity' === $item || 'date' === $item) { + return true; + } + + return $carry; + }, + false, + )) { + $toAddBefore[] = new Node\Stmt\TraitUse([ + new Node\Name('\\'.ExportDataNormalizerTrait::class), + ]); + } + + // if we do not have the `getNormalizerVersion` method + if (!$hasGetVersionMethod) { + $toAdd[] = $this->buildGetNormalizationVersionMethod(); + } + + // if we do not have the `normalizeFormData` method + if (!$hasNormalizedMethod) { + $toAdd[] = $this->buildNormalizeFormDataMethod($propertiesOnForm); + } + + if (!$hasDenormalizeMethod) { + $toAdd[] = $this->buildDenormalizeFormDataMethod($propertiesOnForm); + } + + $node->stmts = [ + ...array_values($toAddBefore), + ...array_values($stmtBefore), + $buildForm, + ...array_values($toAdd), + ...array_values($stmtAfter), + ]; + + return $node; + } + + private function buildDenormalizeFormDataMethod(array $propertiesOnForm): Node\Stmt\ClassMethod + { + $array = new Node\Expr\Array_([]); + + foreach ($propertiesOnForm as $name => $kind) { + $arrayDimFetch = new Node\Expr\ArrayDimFetch( + new Node\Expr\Variable('formData'), + new Node\Scalar\String_($name), + ); + + $array->items[] = new Node\Expr\ArrayItem( + match ($kind) { + 'entity' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('denormalizeDoctrineEntity'), + [ + new Node\Arg($arrayDimFetch), + new Node\Arg( + new PropertyFetch( + new Node\Expr\Variable('this'), + 'someRepository', + ), + ), + ], + ), + 'rolling_date' => new Node\Expr\StaticCall( + new Node\Name\FullyQualified(RollingDate::class), + 'fromNormalized', + [new Node\Arg($arrayDimFetch)], + ), + 'date' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('denormalizeDate'), + [ + new Node\Arg($arrayDimFetch), + ], + ), + 'scalar' => $arrayDimFetch, + default => $arrayDimFetch, + }, + new Node\Scalar\String_($name), + ); + } + + return new Node\Stmt\ClassMethod( + 'denormalizeFormData', + [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'returnType' => new Node\Identifier('array'), + 'params' => [ + new Node\Param(new Node\Expr\Variable('formData'), type: new Node\Identifier('array')), + new Node\Param(new Node\Expr\Variable('fromVersion'), type: new Node\Identifier('int')), + ], + 'stmts' => [ + new Node\Stmt\Return_($array), + ], + ], + ); + } + + private function buildNormalizeFormDataMethod(array $propertiesOnForm): Node\Stmt\ClassMethod + { + $array = new Node\Expr\Array_([]); + + foreach ($propertiesOnForm as $name => $kind) { + $arrayDimFetch = new Node\Expr\ArrayDimFetch( + new Node\Expr\Variable('formData'), + new Node\Scalar\String_($name), + ); + + $array->items[] = new Node\Expr\ArrayItem( + match ($kind) { + 'entity' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('normalizeDoctrineEntity'), + [ + new Node\Arg($arrayDimFetch), + ], + ), + 'rolling_date' => new Node\Expr\MethodCall( + $arrayDimFetch, + new Node\Identifier('normalize'), + ), + 'date' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('normalizeDate'), + [ + new Node\Arg($arrayDimFetch), + ], + ), + 'scalar' => $arrayDimFetch, + default => $arrayDimFetch, + }, + new Node\Scalar\String_($name), + ); + } + + return new Node\Stmt\ClassMethod( + 'normalizeFormData', + [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'returnType' => new Node\Identifier('array'), + 'params' => [ + new Node\Param(new Node\Expr\Variable('formData'), type: new Node\Identifier('array')), + ], + 'stmts' => [ + new Node\Stmt\Return_($array), + ], + ], + ); + } + + private function buildGetNormalizationVersionMethod(): Node\Stmt\ClassMethod + { + return new Node\Stmt\ClassMethod( + 'getNormalizationVersion', + [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'returnType' => new Node\Identifier('int'), + 'stmts' => [ + new Node\Stmt\Return_( + new Node\Scalar\LNumber(1) + ), + ], + ], + ); + } + + /** + * @return array + */ + private function getPropertiesOnForm(Node\Stmt\ClassMethod $buildFormMethod): array + { + $builderName = $buildFormMethod->params[0]->var->name; + $values = []; + + foreach ($buildFormMethod->stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Expression + // it must be a method call + && $stmt->expr instanceof Node\Expr\MethodCall + && 'add' === $stmt->expr->name->toString() + ) { + $newValues = $this->handleMethodCallAdd($stmt->expr, $builderName); + + if (false === $newValues) { + continue; + } + + $values = [...$values, ...$newValues]; + } + } + + return $values; + } + + private function handleMethodCallAdd(Node\Expr\MethodCall $call, string $builderName): array|false + { + if ($call->var instanceof Node\Expr\Variable) { + // in this case, the call is done on the form builder: $formBuilder->add + // or the last method call on the form builder, for instance $formBuilder->add( )->add( ) + if ($builderName !== $call->var->name) { + return false; + } + } elseif ($call->var instanceof Node\Expr\MethodCall && 'add' === $call->var->name->toString()) { + // in this case, we have a chained method call: something like $formbuilder->add()->add(). + // we have to go deeper into the call to get the information from them + $previous = $this->handleMethodCallAdd($call->var, $builderName); + + if (false === $previous) { + return false; + } + } + + $arg0 = $call->args[0] ?? null; + + if (null === $arg0) { + throw new \UnexpectedValueException("The first argument of an 'add' call method is empty"); + } + if (!$arg0->value instanceof Node\Scalar\String_) { + throw new \UnexpectedValueException("The first argument of an 'add' call is not a string"); + } + + $key = $arg0->value->value; + /** @var Node\Expr\ClassConstFetch $argType */ + $argType = $call->args[1]->value; + + return [ + ...$previous ?? [], + $key => match($argType->class->toString()) { + EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class => 'entity', + PickRollingDateType::class => 'rolling_date', + ChillDateType::class, ChillDateTimeType::class, DateTimeType::class, DateType::class => 'date', + default => 'scalar', + }, + ]; + } +} diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc index aa373e629..ea2c43cc3 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc @@ -46,7 +46,7 @@ class MyClass implements ExportInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } @@ -115,7 +115,7 @@ class MyClass implements ExportInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc index ed46f6381..a99243521 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -11,7 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -57,6 +58,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -66,7 +68,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc index 5429d3c82..6b6f22424 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -11,7 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -55,6 +56,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -64,7 +66,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc index 285c16b50..326c9d2c6 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -11,7 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -53,6 +54,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -62,7 +64,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc index b2e78e49c..9b0666f36 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -10,7 +11,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -49,6 +50,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -57,7 +59,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc index 687bd9d0c..ceb855d10 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc @@ -2,13 +2,14 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -44,13 +45,14 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc index 99cea7155..44f7f78d1 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc @@ -47,7 +47,7 @@ class MyClass implements ListInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } @@ -117,7 +117,7 @@ class MyClass implements ListInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc index 2fefc908d..517df6392 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc @@ -2,13 +2,14 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php new file mode 100644 index 000000000..81d3655a6 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php @@ -0,0 +1,40 @@ +doTestFile($file); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/config.php'; + } +} diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc new file mode 100644 index 000000000..22c29577a --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc @@ -0,0 +1,120 @@ +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]) + ->add('entity', EntityType::class, []) + ->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []) + ->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class) + ->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> +----- +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]) + ->add('entity', EntityType::class, []) + ->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []) + ->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class) + ->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['field' => $formData['field'], 'entity' => $this->normalizeDoctrineEntity($formData['entity']), 'user' => $this->normalizeDoctrineEntity($formData['user']), 'rolling_date' => $formData['rolling_date']->normalize(), 'date' => $this->normalizeDate($formData['date'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['field' => $formData['field'], 'entity' => $this->denormalizeDoctrineEntity($formData['entity'], $this->someRepository), 'user' => $this->denormalizeDoctrineEntity($formData['user'], $this->someRepository), 'rolling_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['rolling_date']), 'date' => $this->denormalizeDate($formData['date'])]; + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc new file mode 100644 index 000000000..94b8a9c28 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc @@ -0,0 +1,126 @@ +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]); + + $builder->add('entity', EntityType::class, []); + + $builder->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []); + + $builder->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class); + + $builder->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> +----- +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]); + + $builder->add('entity', EntityType::class, []); + + $builder->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []); + + $builder->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class); + + $builder->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['field' => $formData['field'], 'entity' => $this->normalizeDoctrineEntity($formData['entity']), 'user' => $this->normalizeDoctrineEntity($formData['user']), 'rolling_date' => $formData['rolling_date']->normalize(), 'date' => $this->normalizeDate($formData['date'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['field' => $formData['field'], 'entity' => $this->denormalizeDoctrineEntity($formData['entity'], $this->someRepository), 'user' => $this->denormalizeDoctrineEntity($formData['user'], $this->someRepository), 'rolling_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['rolling_date']), 'date' => $this->denormalizeDate($formData['date'])]; + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc new file mode 100644 index 000000000..0e74e7280 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc @@ -0,0 +1,109 @@ +add('entity', EntityType::class, []); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> +----- +add('entity', EntityType::class, []); + } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['entity' => $this->normalizeDoctrineEntity($formData['entity'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['entity' => $this->denormalizeDoctrineEntity($formData['entity'], $this->someRepository)]; + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc new file mode 100644 index 000000000..56ee32b29 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc @@ -0,0 +1,55 @@ +rule(Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class); +};