Add SavedExportOptionsMigrator for migrating export options

Introduced SavedExportOptionsMigrator to handle the migration of saved export options with structures ensuring versioning. Added unit tests to validate transformation logic and edge cases for better reliability.
This commit is contained in:
Julien Fastré 2025-04-05 00:35:16 +02:00
parent bb30ddc876
commit b5fd9cf4af
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 759 additions and 0 deletions

View File

@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Export\Migrator;
use Chill\MainBundle\Service\RollingDate\RollingDate;
class SavedExportOptionsMigrator
{
public static function migrate(array $fromOptions): array
{
$to = [];
$to['aggregators'] = array_map(
self::mapEnabledStatus(...),
$fromOptions['export']['export']['aggregators'] ?? [],
);
$to['filters'] = array_map(
self::mapEnabledStatus(...),
$fromOptions['export']['export']['filters'] ?? [],
);
$to['export'] = [
'form' => 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 $e) {
return $formData;
}
}
return $formData;
}
private static function refactorRollingDate(array $formData): ?array
{
if ('' === $formData['roll']) {
return null;
}
$fixedDate = null !== ($formData['fixedDate'] ?? null) ?
\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();
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Main;
use Chill\MainBundle\Export\Migrator\SavedExportOptionsMigrator;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Types;
use Doctrine\Migrations\AbstractMigration;
final class Version20250404123326 extends AbstractMigration
{
public function getDescription(): string
{
return 'Update option representation of saved exports';
}
public function up(Schema $schema): void
{
$this->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($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');
}
}