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;
|
namespace Chill\MainBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\UserGroup;
|
||||||
use Chill\MainBundle\Entity\UserJob;
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
@ -53,12 +54,20 @@ readonly class UserJobRepository implements UserJobRepositoryInterface
|
|||||||
return $jobs;
|
return $jobs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function findAllNotAssociatedWithUserGroup(): array
|
||||||
* @param mixed|null $limit
|
{
|
||||||
* @param mixed|null $offset
|
$qb = $this->repository->createQueryBuilder('u');
|
||||||
*
|
$qb->select('u');
|
||||||
* @return array|object[]|UserJob[]
|
|
||||||
*/
|
$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)
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
||||||
{
|
{
|
||||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
@ -33,11 +33,14 @@ interface UserJobRepositoryInterface extends ObjectRepository
|
|||||||
public function findAllOrderedByName(): array;
|
public function findAllOrderedByName(): array;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed|null $limit
|
* Find all the user job which are not related to a UserGroup.
|
||||||
* @param mixed|null $offset
|
|
||||||
*
|
*
|
||||||
* @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 findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
|
||||||
|
|
||||||
public function findOneBy(array $criteria): ?UserJob;
|
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_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
|
user_added: L'utilisateur {user} est ajouté groupe {user_group} avec succès
|
||||||
|
label_related_to_user_job: Groupe {job} (Groupe métier)
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
My notifications with counter: >-
|
My notifications with counter: >-
|
||||||
|
Loading…
x
Reference in New Issue
Block a user