mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Add UserGroup and UserJob synchronization feature
Implement UserGroupRelatedToUserJobSync to manage associations between UserGroups and UserJobs, including creating, updating, and removing relationships. Introduce a cron job to automate the synchronization process and add tests to ensure functionality. Update translations and repository logic as part of the implementation.
This commit is contained in:
parent
d506409d93
commit
fb1c34f9c1
@ -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;
|
||||
@ -53,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);
|
||||
|
@ -33,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<UserJob>
|
||||
*/
|
||||
public function findAllNotAssociatedWithUserGroup(): array;
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
|
||||
|
||||
public function findOneBy(array $criteria): ?UserJob;
|
||||
|
@ -0,0 +1,114 @@
|
||||
<?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, chill_main_user_job_history jh
|
||||
WHERE
|
||||
chill_main_user_group_user.usergroup_id = ug.id
|
||||
AND chill_main_user_group_user.user_id = u.id
|
||||
AND jh.user_id = u.id AND tsrange(jh.startdate, jh.enddate) @> localtimestamp
|
||||
-- only when the user's jobid is different than the user_group id
|
||||
AND ug.userjob_id IS NOT NULL
|
||||
AND jh.job_id <> ug.userjob_id
|
||||
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
|
||||
WHERE cmug.userjob_id IS NOT NULL
|
||||
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;
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?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\Tests\Services\UserGroup;
|
||||
|
||||
use Chill\MainBundle\Entity\CronJobExecution;
|
||||
use Chill\MainBundle\Service\UserGroup\UserGroupRelatedToUserJobSyncCronJob;
|
||||
use Chill\MainBundle\Service\UserGroup\UserGroupRelatedToUserJobSyncInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class UserGroupRelatedToUserJobSyncCronJobTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @dataProvider canRunDataProvider
|
||||
*/
|
||||
public function testCanRun(\DateTimeImmutable $now, ?\DateTimeImmutable $lastStartExecution, bool $exected): void
|
||||
{
|
||||
$clock = new MockClock($now);
|
||||
$job = $this->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];
|
||||
}
|
||||
}
|
@ -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: >-
|
||||
|
Loading…
x
Reference in New Issue
Block a user