mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-04 12:29:43 +00:00
Partage d'export enregistré et génération asynchrone des exports
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\Regroupement;
|
||||
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Entity\Regroupment;
|
||||
|
||||
class CenterRegroupementResolver
|
||||
{
|
||||
/**
|
||||
* Resolves and returns a unique list of centers by merging those from the provided
|
||||
* groups and the additional centers while eliminating duplicates.
|
||||
*
|
||||
* @param list<Regroupment> $groups
|
||||
* @param list<Center> $centers
|
||||
*
|
||||
* @return list<Center>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\Regroupement;
|
||||
|
||||
use Chill\MainBundle\Entity\Regroupment;
|
||||
|
||||
/**
|
||||
* Class RegroupementFiltering.
|
||||
*
|
||||
* Provides methods to filter and manage groups based on specific criteria.
|
||||
*/
|
||||
class RegroupementFiltering
|
||||
{
|
||||
/**
|
||||
* Filters the provided groups and returns only those that contain at least one of the specified centers.
|
||||
*
|
||||
* @param array $groups an array of groups to filter
|
||||
* @param array $centers an array of centers to check against the groups
|
||||
*
|
||||
* @return array an array of filtered groups containing at least one of the specified centers
|
||||
*/
|
||||
public function filterContainsAtLeastOneCenter(array $groups, array $centers): array
|
||||
{
|
||||
return array_values(
|
||||
array_filter($groups, static fn (Regroupment $group) => $group->containsAtLeastOneCenter($centers)),
|
||||
);
|
||||
}
|
||||
}
|
@@ -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');
|
||||
}
|
||||
}
|
||||
|
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,129 @@
|
||||
<?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\Service\UserGroup;
|
||||
|
||||
use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final readonly class UserGroupRelatedToUserJobSync implements UserGroupRelatedToUserJobSyncInterface
|
||||
{
|
||||
private const LOG_PREFIX = '[UserGroupRelatedToUserJobSync] ';
|
||||
|
||||
public function __construct(
|
||||
private UserJobRepositoryInterface $userJobRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private TranslatorInterface $translator,
|
||||
private TranslatableStringHelperInterface $translatableStringHelper,
|
||||
private LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
public function __invoke(): array
|
||||
{
|
||||
$created = $this->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();
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\UserGroup;
|
||||
|
||||
use Chill\MainBundle\Cron\CronJobInterface;
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
final readonly class UserGroupRelatedToUserJobSyncCronJob implements CronJobInterface
|
||||
{
|
||||
private const KEY = 'user-group-related-to-user-job-sync';
|
||||
|
||||
public function __construct(private ClockInterface $clock, private UserGroupRelatedToUserJobSyncInterface $userJobSync) {}
|
||||
|
||||
public function canRun(?CronJobExecution $cronJobExecution): bool
|
||||
{
|
||||
if (null === $cronJobExecution) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $cronJobExecution->getLastStart() < $this->clock->now()->sub(new \DateInterval('P1D'));
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return self::KEY;
|
||||
}
|
||||
|
||||
public function run(array $lastExecutionData): ?array
|
||||
{
|
||||
return ($this->userJobSync)();
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\UserGroup;
|
||||
|
||||
interface UserGroupRelatedToUserJobSyncInterface
|
||||
{
|
||||
/**
|
||||
* @return array{userjob_created: int, association_removed: int, association_created: int}
|
||||
*/
|
||||
public function __invoke(): array;
|
||||
}
|
Reference in New Issue
Block a user