Partage d'export enregistré et génération asynchrone des exports

This commit is contained in:
2025-07-08 13:53:25 +00:00
parent c4cc0baa8e
commit 8bc16dadb0
447 changed files with 14134 additions and 3854 deletions

View File

@@ -0,0 +1,85 @@
<?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\Repository;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\AssociatedEntityToStoredObjectInterface;
use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Entity\SavedExport;
use Chill\MainBundle\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<ExportGeneration>
*
* @implements AssociatedEntityToStoredObjectInterface<ExportGeneration>
*/
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<ExportGeneration>
*/
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<ExportGeneration>
*/
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();
}
}

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -0,0 +1,34 @@
<?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\Repository;
use Chill\MainBundle\Entity\Regroupment;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\Persistence\ObjectRepository;
/**
* @template-extends ObjectRepository<Regroupment>
*/
interface RegroupmentRepositoryInterface extends ObjectRepository
{
/**
* @throws NonUniqueResultException
* @throws NoResultException
*/
public function findOneByName(string $name): ?Regroupment;
/**
* @return array<Regroupment>
*/
public function findRegroupmentAssociatedToNoCenter(): array;
}

View File

@@ -0,0 +1,32 @@
<?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\Repository;
use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Entity\SavedExport;
readonly class SavedExportOrExportGenerationRepository
{
public function __construct(
private SavedExportRepositoryInterface $savedExportRepository,
private ExportGenerationRepository $exportGenerationRepository,
) {}
public function findById(string $uuid): SavedExport|ExportGeneration|null
{
if (null !== $savedExport = $this->savedExportRepository->find($uuid)) {
return $savedExport;
}
return $this->exportGenerationRepository->find($uuid);
}
}

View File

@@ -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<SavedExport>
@@ -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);
}

View File

@@ -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<int, mixed> $filters filters where keys are one of the constant starting with FILTER_
*
* @return list<SavedExport>
*/
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;

View File

@@ -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);

View File

@@ -14,18 +14,15 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\UserJob;
use Doctrine\Persistence\ObjectRepository;
/**
* @template-extends ObjectRepository<UserJob>
*/
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<UserJob>
*/
public function findAllNotAssociatedWithUserGroup(): array;
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null);
public function findOneBy(array $criteria): ?UserJob;