mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-22 07:33:50 +00:00
Merge remote-tracking branch 'origin/master' into rector/rules-up-to-php80
Conflicts: src/Bundle/ChillActivityBundle/Controller/ActivityController.php src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php src/Bundle/ChillActivityBundle/Repository/ActivityACLAwareRepository.php src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php src/Bundle/ChillEventBundle/Search/EventSearch.php src/Bundle/ChillMainBundle/Controller/ExportController.php src/Bundle/ChillMainBundle/Controller/PermissionsGroupController.php src/Bundle/ChillMainBundle/Cron/CronManager.php src/Bundle/ChillMainBundle/Entity/CronJobExecution.php src/Bundle/ChillMainBundle/Export/ExportManager.php src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php src/Bundle/ChillMainBundle/Form/Type/Listing/FilterOrderType.php src/Bundle/ChillMainBundle/Repository/NotificationRepository.php src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelper.php src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperBuilder.php src/Bundle/ChillMainBundle/Templating/Listing/FilterOrderHelperFactory.php src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseWorkController.php src/Bundle/ChillPersonBundle/Controller/SocialWorkSocialActionApiController.php src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodContext.php src/Bundle/ChillPersonBundle/Service/DocGenerator/AccompanyingPeriodWorkEvaluationContext.php src/Bundle/ChillPersonBundle/Service/DocGenerator/PersonContext.php src/Bundle/ChillReportBundle/DataFixtures/ORM/LoadReports.php src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
This commit is contained in:
@@ -11,8 +11,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocStoreBundle;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class ChillDocStoreBundle extends Bundle
|
||||
{
|
||||
public function build(ContainerBuilder $container)
|
||||
{
|
||||
$container->registerForAutoconfiguration(GenericDocForAccompanyingPeriodProviderInterface::class)
|
||||
->addTag('chill_doc_store.generic_doc_accompanying_period_provider');
|
||||
$container->registerForAutoconfiguration(GenericDocForPersonProviderInterface::class)
|
||||
->addTag('chill_doc_store.generic_doc_person_provider');
|
||||
$container->registerForAutoconfiguration(GenericDocRendererInterface::class)
|
||||
->addTag('chill_doc_store.generic_doc_renderer');
|
||||
}
|
||||
}
|
||||
|
@@ -33,11 +33,23 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
*/
|
||||
class DocumentAccompanyingCourseController extends AbstractController
|
||||
{
|
||||
protected AuthorizationHelper $authorizationHelper;
|
||||
|
||||
protected EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
/**
|
||||
* DocumentAccompanyingCourseController constructor.
|
||||
*/
|
||||
public function __construct(protected TranslatorInterface $translator, protected EventDispatcherInterface $eventDispatcher, protected AuthorizationHelper $authorizationHelper, private PaginatorFactory $paginatorFactory, private AccompanyingCourseDocumentRepository $courseRepository)
|
||||
{
|
||||
public function __construct(
|
||||
TranslatorInterface $translator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
) {
|
||||
$this->translator = $translator;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +74,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
return $this->redirect($request->query->get('returnPath'));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
|
||||
return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]);
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
@@ -116,40 +128,6 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", name="accompanying_course_document_index", methods="GET")
|
||||
*/
|
||||
public function index(AccompanyingPeriod $course): Response
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
if (null === $course) {
|
||||
throw $this->createNotFoundException('Accompanying period not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course);
|
||||
|
||||
$total = $this->courseRepository->countByCourse($course);
|
||||
$pagination = $this->paginatorFactory->create($total);
|
||||
|
||||
$documents = $this->courseRepository
|
||||
->findBy(
|
||||
['course' => $course],
|
||||
['date' => 'DESC', 'id' => 'DESC'],
|
||||
$pagination->getItemsPerPage(),
|
||||
$pagination->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig',
|
||||
[
|
||||
'documents' => $documents,
|
||||
'accompanyingCourse' => $course,
|
||||
'pagination' => $pagination,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="accompanying_course_document_new", methods="GET|POST")
|
||||
*/
|
||||
@@ -182,7 +160,7 @@ class DocumentAccompanyingCourseController extends AbstractController
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('The document is successfully registered'));
|
||||
|
||||
return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
|
||||
return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]);
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
|
@@ -39,11 +39,23 @@ use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
*/
|
||||
class DocumentPersonController extends AbstractController
|
||||
{
|
||||
protected AuthorizationHelper $authorizationHelper;
|
||||
|
||||
protected EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
protected TranslatorInterface $translator;
|
||||
|
||||
/**
|
||||
* DocumentPersonController constructor.
|
||||
*/
|
||||
public function __construct(protected TranslatorInterface $translator, protected EventDispatcherInterface $eventDispatcher, protected AuthorizationHelper $authorizationHelper, private PaginatorFactory $paginatorFactory, private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository)
|
||||
{
|
||||
public function __construct(
|
||||
TranslatorInterface $translator,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
AuthorizationHelper $authorizationHelper,
|
||||
) {
|
||||
$this->translator = $translator;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +80,7 @@ class DocumentPersonController extends AbstractController
|
||||
return $this->redirect($request->query->get('returnPath'));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]);
|
||||
return $this->redirectToRoute('chill_docstore_generic-doc_by-person_index', ['id' => $person->getId()]);
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
@@ -140,45 +152,6 @@ class DocumentPersonController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", name="person_document_index", methods="GET")
|
||||
*/
|
||||
public function index(Person $person): Response
|
||||
{
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
if (null === $person) {
|
||||
throw $this->createNotFoundException('Person not found');
|
||||
}
|
||||
|
||||
$this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
|
||||
|
||||
$total = $this->personDocumentACLAwareRepository->countByPerson($person);
|
||||
$pagination = $this->paginatorFactory->create($total);
|
||||
|
||||
$documents = $this->personDocumentACLAwareRepository->findByPerson(
|
||||
$person,
|
||||
['date' => 'DESC', 'id' => 'DESC'],
|
||||
$pagination->getItemsPerPage(),
|
||||
$pagination->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
$event = new PrivacyEvent($person, [
|
||||
'element_class' => PersonDocument::class,
|
||||
'action' => 'index',
|
||||
]);
|
||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||
|
||||
return $this->render(
|
||||
'ChillDocStoreBundle:PersonDocument:index.html.twig',
|
||||
[
|
||||
'documents' => $documents,
|
||||
'person' => $person,
|
||||
'pagination' => $pagination,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/new", name="person_document_new", methods="GET|POST")
|
||||
*/
|
||||
@@ -213,7 +186,7 @@ class DocumentPersonController extends AbstractController
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('The document is successfully registered'));
|
||||
|
||||
return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]);
|
||||
return $this->redirectToRoute('chill_docstore_generic-doc_by-person_index', ['id' => $person->getId()]);
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && !$form->isValid()) {
|
||||
|
@@ -0,0 +1,98 @@
|
||||
<?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\DocStoreBundle\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Symfony\Bundle\TwigBundle\TwigEngine;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
final readonly class GenericDocForAccompanyingPeriodController
|
||||
{
|
||||
public function __construct(
|
||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||
private Manager $manager,
|
||||
private PaginatorFactory $paginator,
|
||||
private Security $security,
|
||||
private EngineInterface $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccompanyingPeriod $accompanyingPeriod
|
||||
* @return Response
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*
|
||||
* @Route("/{_locale}/doc-store/generic-doc/by-period/{id}/index", name="chill_docstore_generic-doc_by-period_index")
|
||||
*/
|
||||
public function list(AccompanyingPeriod $accompanyingPeriod): Response
|
||||
{
|
||||
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) {
|
||||
throw new AccessDeniedHttpException("not allowed to see the documents for accompanying period");
|
||||
}
|
||||
|
||||
$filterBuilder = $this->filterOrderHelperFactory
|
||||
->create(self::class)
|
||||
->addSearchBox()
|
||||
->addDateRange('dateRange', 'generic_doc.filter.date-range');
|
||||
|
||||
if ([] !== $places = $this->manager->placesForAccompanyingPeriod($accompanyingPeriod)) {
|
||||
$filterBuilder->addCheckbox('places', $places, [], array_map(
|
||||
static fn (string $k) => 'generic_doc.filter.keys.' . $k,
|
||||
$places
|
||||
));
|
||||
}
|
||||
|
||||
$filter = $filterBuilder
|
||||
->build();
|
||||
|
||||
['to' => $endDate, 'from' => $startDate ] = $filter->getDateRangeData('dateRange');
|
||||
$content = $filter->getQueryString();
|
||||
|
||||
$nb = $this->manager->countDocForAccompanyingPeriod(
|
||||
$accompanyingPeriod,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$content,
|
||||
$filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
|
||||
);
|
||||
$paginator = $this->paginator->create($nb);
|
||||
|
||||
$documents = $this->manager->findDocForAccompanyingPeriod(
|
||||
$accompanyingPeriod,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage(),
|
||||
$startDate,
|
||||
$endDate,
|
||||
$content,
|
||||
$filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
|
||||
);
|
||||
|
||||
return new Response($this->twig->render(
|
||||
'@ChillDocStore/GenericDoc/accompanying_period_list.html.twig',
|
||||
[
|
||||
'accompanyingCourse' => $accompanyingPeriod,
|
||||
'pagination' => $paginator,
|
||||
'documents' => iterator_to_array($documents),
|
||||
'filter' => $filter,
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
<?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\DocStoreBundle\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
|
||||
final readonly class GenericDocForPerson
|
||||
{
|
||||
public function __construct(
|
||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||
private Manager $manager,
|
||||
private PaginatorFactory $paginator,
|
||||
private Security $security,
|
||||
private EngineInterface $twig,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*
|
||||
* @Route("/{_locale}/doc-store/generic-doc/by-person/{id}/index", name="chill_docstore_generic-doc_by-person_index")
|
||||
*/
|
||||
public function list(Person $person): Response
|
||||
{
|
||||
if (!$this->security->isGranted(PersonDocumentVoter::SEE, $person)) {
|
||||
throw new AccessDeniedHttpException("not allowed to see the documents for person");
|
||||
}
|
||||
|
||||
$filterBuilder = $this->filterOrderHelperFactory
|
||||
->create(self::class)
|
||||
->addSearchBox()
|
||||
->addDateRange('dateRange', 'generic_doc.filter.date-range');
|
||||
|
||||
if ([] !== $places = $this->manager->placesForPerson($person)) {
|
||||
$filterBuilder->addCheckbox('places', $places, [], array_map(
|
||||
static fn (string $k) => 'generic_doc.filter.keys.' . $k,
|
||||
$places
|
||||
));
|
||||
}
|
||||
|
||||
$filter = $filterBuilder
|
||||
->build();
|
||||
|
||||
['to' => $endDate, 'from' => $startDate ] = $filter->getDateRangeData('dateRange');
|
||||
$content = $filter->getQueryString();
|
||||
|
||||
$nb = $this->manager->countDocForPerson(
|
||||
$person,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$content,
|
||||
$filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
|
||||
);
|
||||
$paginator = $this->paginator->create($nb);
|
||||
|
||||
$documents = $this->manager->findDocForPerson(
|
||||
$person,
|
||||
$paginator->getCurrentPageFirstItemNumber(),
|
||||
$paginator->getItemsPerPage(),
|
||||
$startDate,
|
||||
$endDate,
|
||||
$content,
|
||||
$filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
|
||||
);
|
||||
|
||||
return new Response($this->twig->render(
|
||||
'@ChillDocStore/GenericDoc/person_list.html.twig',
|
||||
[
|
||||
'person' => $person,
|
||||
'pagination' => $paginator,
|
||||
'documents' => iterator_to_array($documents),
|
||||
'filter' => $filter,
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
}
|
233
src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php
Normal file
233
src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
class FetchQuery implements FetchQueryInterface
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $joins = [];
|
||||
|
||||
/**
|
||||
* @var list<list<mixed>>
|
||||
*/
|
||||
private array $joinParams = [];
|
||||
|
||||
/**
|
||||
* @var array<list<Types::*>>
|
||||
*/
|
||||
private array $joinTypes = [];
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
private array $wheres = [];
|
||||
|
||||
/**
|
||||
* @var array<list<mixed>>
|
||||
*/
|
||||
private array $whereParams = [];
|
||||
|
||||
/**
|
||||
* @var array<list<Types::*>>
|
||||
*/
|
||||
private array $whereTypes = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly string $selectKeyString,
|
||||
private readonly string $selectIdentifierJsonB,
|
||||
private readonly string $selectDate,
|
||||
private string $from = '',
|
||||
private array $selectIdentifierParams = [],
|
||||
private array $selectIdentifierTypes = [],
|
||||
private array $selectDateParams = [],
|
||||
private array $selectDateTypes = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function addJoinClause(string $sql, array $params = [], array $types = []): int
|
||||
{
|
||||
$this->joins[] = $sql;
|
||||
$this->joinParams[] = $params;
|
||||
$this->joinTypes[] = $types;
|
||||
|
||||
return count($this->joins) - 1;
|
||||
}
|
||||
|
||||
public function addWhereClause(string $sql, array $params = [], array $types = []): int
|
||||
{
|
||||
$this->wheres[] = $sql;
|
||||
$this->whereParams[] = $params;
|
||||
$this->whereTypes[] = $types;
|
||||
|
||||
return count($this->wheres) - 1;
|
||||
}
|
||||
|
||||
public function removeWhereClause(int $index): void
|
||||
{
|
||||
if (!array_key_exists($index, $this->wheres)) {
|
||||
throw new \UnexpectedValueException("this index does not exists");
|
||||
}
|
||||
|
||||
unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]);
|
||||
|
||||
}
|
||||
|
||||
public function removeJoinClause(int $index): void
|
||||
{
|
||||
if (!array_key_exists($index, $this->joins)) {
|
||||
throw new \UnexpectedValueException("this index does not exists");
|
||||
}
|
||||
|
||||
unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]);
|
||||
|
||||
}
|
||||
|
||||
public function getSelectKeyString(): string
|
||||
{
|
||||
return $this->selectKeyString;
|
||||
}
|
||||
|
||||
public function getSelectIdentifierJsonB(): string
|
||||
{
|
||||
return $this->selectIdentifierJsonB;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSelectIdentifierParams(): array
|
||||
{
|
||||
return $this->selectIdentifierParams;
|
||||
}
|
||||
|
||||
public function getSelectIdentifiersTypes(): array
|
||||
{
|
||||
return $this->selectIdentifierTypes;
|
||||
}
|
||||
|
||||
public function getSelectDate(): string
|
||||
{
|
||||
return $this->selectDate;
|
||||
}
|
||||
|
||||
public function getSelectDateTypes(): array
|
||||
{
|
||||
return $this->selectDateTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getSelectDateParams(): array
|
||||
{
|
||||
return $this->selectDateParams;
|
||||
}
|
||||
|
||||
public function getFromQuery(): string
|
||||
{
|
||||
return $this->from . " " . implode(' ', $this->joins);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getFromQueryParams(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->joinParams as $params) {
|
||||
$result = [...$result, ...$params];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getFromQueryTypes(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->joinTypes as $types) {
|
||||
$result = [...$result, ...$types];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getWhereQuery(): string
|
||||
{
|
||||
return implode(' AND ', $this->wheres);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWhereQueryParams(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->whereParams as $params) {
|
||||
$result = [...$result, ...$params];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getWhereQueryTypes(): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->whereTypes as $types) {
|
||||
$result = [...$result, ...$types];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function setSelectIdentifierParams(array $selectIdentifierParams): self
|
||||
{
|
||||
$this->selectIdentifierParams = $selectIdentifierParams;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSelectDateParams(array $selectDateParams): self
|
||||
{
|
||||
$this->selectDateParams = $selectDateParams;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setFrom(string $from): self
|
||||
{
|
||||
$this->from = $from;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSelectIdentifierTypes(array $selectIdentifierTypes): self
|
||||
{
|
||||
$this->selectIdentifierTypes = $selectIdentifierTypes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSelectDateTypes(array $selectDateTypes): self
|
||||
{
|
||||
$this->selectDateTypes = $selectDateTypes;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
interface FetchQueryInterface
|
||||
{
|
||||
public function getSelectKeyString(): string;
|
||||
|
||||
public function getSelectIdentifierJsonB(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getSelectIdentifierParams(): array;
|
||||
|
||||
/**
|
||||
* @return list<Types::*>
|
||||
*/
|
||||
public function getSelectIdentifiersTypes(): array;
|
||||
|
||||
public function getSelectDate(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getSelectDateParams(): array;
|
||||
|
||||
/**
|
||||
* @return list<Types::*>
|
||||
*/
|
||||
public function getSelectDateTypes(): array;
|
||||
|
||||
public function getFromQuery(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getFromQueryParams(): array;
|
||||
|
||||
/**
|
||||
* @return list<Types::*>
|
||||
*/
|
||||
public function getFromQueryTypes(): array;
|
||||
|
||||
public function getWhereQuery(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getWhereQueryParams(): array;
|
||||
|
||||
/**
|
||||
* @return list<Types::*>
|
||||
*/
|
||||
public function getWhereQueryTypes(): array;
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
final readonly class FetchQueryToSqlBuilder
|
||||
{
|
||||
private const SQL = <<<'SQL'
|
||||
SELECT
|
||||
'{{ key }}' AS key,
|
||||
{{ identifiers }} AS identifiers,
|
||||
{{ date }}::date AS doc_date
|
||||
FROM {{ from }}
|
||||
{{ where }}
|
||||
SQL;
|
||||
|
||||
/**
|
||||
* @param FetchQueryInterface $query
|
||||
* @return array{sql: string, params: list<mixed>, types: list<Types::*>}
|
||||
*/
|
||||
public function toSql(FetchQueryInterface $query): array
|
||||
{
|
||||
$sql = strtr(self::SQL, [
|
||||
'{{ key }}' => $query->getSelectKeyString(),
|
||||
'{{ identifiers }}' => $query->getSelectIdentifierJsonB(),
|
||||
'{{ date }}' => $query->getSelectDate(),
|
||||
'{{ from }}' => $query->getFromQuery(),
|
||||
'{{ where }}' => '' === ($w = $query->getWhereQuery()) ? '' : 'WHERE ' . $w,
|
||||
]);
|
||||
|
||||
$params = [
|
||||
...$query->getSelectIdentifierParams(),
|
||||
...$query->getSelectDateParams(),
|
||||
...$query->getFromQueryParams(),
|
||||
...$query->getWhereQueryParams()
|
||||
];
|
||||
|
||||
$types = [
|
||||
...$query->getSelectIdentifiersTypes(),
|
||||
...$query->getSelectDateTypes(),
|
||||
...$query->getFromQueryTypes(),
|
||||
...$query->getWhereQueryTypes(),
|
||||
];
|
||||
|
||||
return ['sql' => $sql, 'params' => $params, 'types' => $types];
|
||||
}
|
||||
}
|
31
src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php
Normal file
31
src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
final readonly class GenericDocDTO
|
||||
{
|
||||
public function __construct(
|
||||
public string $key,
|
||||
public array $identifiers,
|
||||
public \DateTimeImmutable $docDate,
|
||||
public AccompanyingPeriod|Person $linked,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getContext(): string
|
||||
{
|
||||
return $this->linked instanceof AccompanyingPeriod ? 'accompanying-period' : 'person';
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
|
||||
interface GenericDocForAccompanyingPeriodProviderInterface
|
||||
{
|
||||
public function buildFetchQueryForAccompanyingPeriod(
|
||||
AccompanyingPeriod $accompanyingPeriod,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
?string $origin = null
|
||||
): FetchQueryInterface;
|
||||
|
||||
/**
|
||||
* Return true if the user is allowed to see some documents for this provider.
|
||||
*/
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool;
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
|
||||
interface GenericDocForPersonProviderInterface
|
||||
{
|
||||
public function buildFetchQueryForPerson(
|
||||
Person $person,
|
||||
?DateTimeImmutable $startDate = null,
|
||||
?DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
?string $origin = null
|
||||
): FetchQueryInterface;
|
||||
|
||||
/**
|
||||
* Return true if the user is allowed to see some documents for this provider.
|
||||
*/
|
||||
public function isAllowedForPerson(Person $person): bool;
|
||||
}
|
222
src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php
Normal file
222
src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php
Normal file
@@ -0,0 +1,222 @@
|
||||
<?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\DocStoreBundle\GenericDoc;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
|
||||
final readonly class Manager
|
||||
{
|
||||
private FetchQueryToSqlBuilder $builder;
|
||||
|
||||
public function __construct(
|
||||
/**
|
||||
* @var iterable<GenericDocForAccompanyingPeriodProviderInterface>
|
||||
*/
|
||||
private iterable $providersForAccompanyingPeriod,
|
||||
|
||||
/**
|
||||
* @var iterable<GenericDocForPersonProviderInterface>
|
||||
*/
|
||||
private iterable $providersForPerson,
|
||||
private Connection $connection,
|
||||
) {
|
||||
$this->builder = new FetchQueryToSqlBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $places
|
||||
* @throws Exception
|
||||
*/
|
||||
public function countDocForAccompanyingPeriod(
|
||||
AccompanyingPeriod $accompanyingPeriod,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
array $places = []
|
||||
): int {
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places);
|
||||
|
||||
return $this->countDoc($sql, $params, $types);
|
||||
}
|
||||
|
||||
private function countDoc(string $sql, array $params, array $types): int
|
||||
{
|
||||
if ($sql === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$countSql = "SELECT count(*) AS c FROM ({$sql}) AS sq";
|
||||
$result = $this->connection->executeQuery($countSql, $params, $types);
|
||||
|
||||
$number = $result->fetchOne();
|
||||
|
||||
if (false === $number) {
|
||||
throw new \UnexpectedValueException("number of documents failed to load");
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
|
||||
public function countDocForPerson(
|
||||
Person $person,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
array $places = []
|
||||
): int {
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places);
|
||||
|
||||
return $this->countDoc($sql, $params, $types);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $places places to search. When empty, search in all places
|
||||
* @return iterable<GenericDocDTO>
|
||||
* @throws Exception
|
||||
*/
|
||||
public function findDocForAccompanyingPeriod(
|
||||
AccompanyingPeriod $accompanyingPeriod,
|
||||
int $offset = 0,
|
||||
int $limit = 20,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
array $places = []
|
||||
): iterable {
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places);
|
||||
|
||||
return $this->findDocs($accompanyingPeriod, $sql, $params, $types, $offset, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function findDocs(AccompanyingPeriod|Person $linked, string $sql, array $params, array $types, int $offset, int $limit): iterable
|
||||
{
|
||||
if ($sql === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$runSql = "{$sql} ORDER BY doc_date DESC LIMIT ? OFFSET ?";
|
||||
$runParams = [...$params, ...[$limit, $offset]];
|
||||
$runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]];
|
||||
|
||||
foreach ($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) {
|
||||
yield new GenericDocDTO(
|
||||
$row['key'],
|
||||
json_decode($row['identifiers'], true, 512, JSON_THROW_ON_ERROR),
|
||||
new \DateTimeImmutable($row['doc_date']),
|
||||
$linked,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $places places to search. When empty, search in all places
|
||||
* @return iterable<GenericDocDTO>
|
||||
*/
|
||||
public function findDocForPerson(
|
||||
Person $person,
|
||||
int $offset = 0,
|
||||
int $limit = 20,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
array $places = []
|
||||
): iterable {
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places);
|
||||
|
||||
return $this->findDocs($person, $sql, $params, $types, $offset, $limit);
|
||||
}
|
||||
|
||||
public function placesForPerson(Person $person): array
|
||||
{
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person);
|
||||
|
||||
return $this->places($sql, $params, $types);
|
||||
}
|
||||
|
||||
public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array
|
||||
{
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod);
|
||||
|
||||
return $this->places($sql, $params, $types);
|
||||
}
|
||||
|
||||
private function places(string $sql, array $params, array $types): array
|
||||
{
|
||||
if ($sql === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$runSql = "SELECT DISTINCT key FROM ({$sql}) AS sq ORDER BY key";
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($this->connection->iterateAssociative($runSql, $params, $types) as $k) {
|
||||
$keys[] = $k['key'];
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $places places to search. When empty, search in all places
|
||||
*/
|
||||
private function buildUnionQuery(
|
||||
AccompanyingPeriod|Person $linked,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
array $places = [],
|
||||
): array {
|
||||
$queries = [];
|
||||
|
||||
if ($linked instanceof AccompanyingPeriod) {
|
||||
foreach ($this->providersForAccompanyingPeriod as $provider) {
|
||||
if (!$provider->isAllowedForAccompanyingPeriod($linked)) {
|
||||
continue;
|
||||
}
|
||||
$queries[] = $provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content);
|
||||
}
|
||||
} else {
|
||||
foreach ($this->providersForPerson as $provider) {
|
||||
if (!$provider->isAllowedForPerson($linked)) {
|
||||
continue;
|
||||
}
|
||||
$queries[] = $provider->buildFetchQueryForPerson($linked, $startDate, $endDate, $content);
|
||||
}
|
||||
}
|
||||
$sql = [];
|
||||
$params = [];
|
||||
$types = [];
|
||||
|
||||
foreach ($queries as $query) {
|
||||
if ([] !== $places and !in_array($query->getSelectKeyString(), $places, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder->toSql($query);
|
||||
|
||||
$sql[] = $q;
|
||||
$params = [...$params, ...$p];
|
||||
$types = [...$types, ...$t];
|
||||
}
|
||||
|
||||
return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types];
|
||||
}
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
<?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\DocStoreBundle\GenericDoc\Providers;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class AccompanyingCourseDocumentGenericDocProvider implements GenericDocForAccompanyingPeriodProviderInterface, GenericDocForPersonProviderInterface
|
||||
{
|
||||
public const KEY = 'accompanying_course_document';
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
self::KEY,
|
||||
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
|
||||
$classMetadata->getColumnName('date'),
|
||||
$classMetadata->getSchemaName() . '.' . $classMetadata->getTableName()
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')),
|
||||
[$accompanyingPeriod->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
return $this->addWhereClause($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||
{
|
||||
return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod);
|
||||
}
|
||||
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
self::KEY,
|
||||
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
|
||||
$classMetadata->getColumnName('date'),
|
||||
$classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() . ' AS acc_course_document'
|
||||
);
|
||||
|
||||
$atLeastOne = false;
|
||||
$or = [];
|
||||
$orParams = [];
|
||||
$orTypes = [];
|
||||
|
||||
foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
|
||||
if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $participation->getAccompanyingPeriod())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$atLeastOne = true;
|
||||
|
||||
$or[] = sprintf(
|
||||
"(acc_course_document.%s = ? AND acc_course_document.%s BETWEEN ? AND COALESCE(?, 'infinity'::date))",
|
||||
$classMetadata->getSingleAssociationJoinColumnName('course'),
|
||||
$classMetadata->getColumnName('date')
|
||||
);
|
||||
$orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), $participation->getStartDate(), $participation->getEndDate()];
|
||||
$orTypes = [...$orTypes, Types::INTEGER, Types::DATE_MUTABLE, Types::DATE_MUTABLE];
|
||||
}
|
||||
|
||||
if (!$atLeastOne) {
|
||||
// there aren't any period allowed to be seen. Add an unreachable condition
|
||||
$query->addWhereClause('TRUE = FALSE');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->addWhereClause('(' . implode(' OR ', $or) . ')', $orParams, $orTypes);
|
||||
|
||||
return $this->addWhereClause($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person);
|
||||
}
|
||||
|
||||
private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
|
||||
|
||||
if (null !== $startDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('? <= %s', $classMetadata->getColumnName('date')),
|
||||
[$startDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $endDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('? >= %s', $classMetadata->getColumnName('date')),
|
||||
[$endDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $content and '' !== $content) {
|
||||
$query->addWhereClause(
|
||||
sprintf(
|
||||
'(%s ilike ? OR %s ilike ?)',
|
||||
$classMetadata->getColumnName('title'),
|
||||
$classMetadata->getColumnName('description')
|
||||
),
|
||||
['%' . $content . '%', '%' . $content . '%'],
|
||||
[Types::STRING, Types::STRING]
|
||||
);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?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\DocStoreBundle\GenericDoc\Providers;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class PersonDocumentGenericDocProvider implements GenericDocForPersonProviderInterface, GenericDocForAccompanyingPeriodProviderInterface
|
||||
{
|
||||
public const KEY = 'person_document';
|
||||
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildFetchQueryForPerson(
|
||||
Person $person,
|
||||
?DateTimeImmutable $startDate = null,
|
||||
?DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
?string $origin = null
|
||||
): FetchQueryInterface {
|
||||
return $this->personDocumentACLAwareRepository->buildFetchQueryForPerson(
|
||||
$person,
|
||||
$startDate,
|
||||
$endDate,
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
return $this->security->isGranted(PersonDocumentVoter::SEE, $person);
|
||||
}
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
return $this->personDocumentACLAwareRepository->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||
{
|
||||
// we assume that the user is allowed to see at least one person of the course
|
||||
// this will be double checked when running the query
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
<?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\DocStoreBundle\GenericDoc\Renderer;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||
use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
||||
use Chill\DocStoreBundle\Repository\AccompanyingCourseDocumentRepository;
|
||||
use Chill\DocStoreBundle\Repository\PersonDocumentRepository;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
|
||||
final readonly class AccompanyingCourseDocumentGenericDocRenderer implements GenericDocRendererInterface
|
||||
{
|
||||
public function __construct(
|
||||
private AccompanyingCourseDocumentRepository $accompanyingCourseDocumentRepository,
|
||||
private PersonDocumentRepository $personDocumentRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool
|
||||
{
|
||||
return $genericDocDTO->key === AccompanyingCourseDocumentGenericDocProvider::KEY
|
||||
|| $genericDocDTO->key === PersonDocumentGenericDocProvider::KEY;
|
||||
}
|
||||
|
||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
|
||||
{
|
||||
return '@ChillDocStore/List/list_item.html.twig';
|
||||
}
|
||||
|
||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
|
||||
{
|
||||
if (AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key) {
|
||||
return [
|
||||
'document' => $doc = $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']),
|
||||
'accompanyingCourse' => $doc->getCourse(),
|
||||
'options' => $options,
|
||||
'context' => $genericDocDTO->getContext(),
|
||||
];
|
||||
}
|
||||
// this is a person
|
||||
return [
|
||||
'document' => $doc = $this->personDocumentRepository->find($genericDocDTO->identifiers['id']),
|
||||
'person' => $doc->getPerson(),
|
||||
'options' => $options,
|
||||
'context' => $genericDocDTO->getContext(),
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
<?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\DocStoreBundle\GenericDoc\Twig;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFilter;
|
||||
|
||||
final class GenericDocExtension extends AbstractExtension
|
||||
{
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new TwigFilter('chill_generic_doc_render', [GenericDocExtensionRuntime::class, 'renderGenericDoc'], [
|
||||
'needs_environment' => true,
|
||||
'is_safe' => ['html'],
|
||||
])
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?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\DocStoreBundle\GenericDoc\Twig;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
|
||||
final readonly class GenericDocExtensionRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
* @var list<GenericDocRendererInterface>
|
||||
*/
|
||||
private iterable $renderers,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
* @throws LoaderError
|
||||
*/
|
||||
public function renderGenericDoc(Environment $twig, GenericDocDTO $genericDocDTO, array $options = []): string
|
||||
{
|
||||
foreach ($this->renderers as $renderer) {
|
||||
if ($renderer->supports($genericDocDTO)) {
|
||||
return $twig->render(
|
||||
$renderer->getTemplate($genericDocDTO, $options),
|
||||
$renderer->getTemplateData($genericDocDTO, $options),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new \LogicException("no renderer found");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
<?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\DocStoreBundle\GenericDoc\Twig;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
|
||||
interface GenericDocRendererInterface
|
||||
{
|
||||
public function supports(GenericDocDTO $genericDocDTO, $options = []): bool;
|
||||
|
||||
public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string;
|
||||
|
||||
public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array;
|
||||
|
||||
}
|
@@ -45,9 +45,9 @@ final class MenuBuilder implements LocalMenuBuilderInterface
|
||||
|
||||
if ($this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $course)) {
|
||||
$menu->addChild($this->translator->trans('Documents'), [
|
||||
'route' => 'accompanying_course_document_index',
|
||||
'route' => 'chill_docstore_generic-doc_by-period_index',
|
||||
'routeParameters' => [
|
||||
'course' => $course->getId(),
|
||||
'id' => $course->getId(),
|
||||
],
|
||||
])
|
||||
->setExtras([
|
||||
@@ -63,9 +63,9 @@ final class MenuBuilder implements LocalMenuBuilderInterface
|
||||
|
||||
if ($this->security->isGranted(PersonDocumentVoter::SEE, $person)) {
|
||||
$menu->addChild($this->translator->trans('Documents'), [
|
||||
'route' => 'person_document_index',
|
||||
'route' => 'chill_docstore_generic-doc_by-person_index',
|
||||
'routeParameters' => [
|
||||
'person' => $person->getId(),
|
||||
'id' => $person->getId(),
|
||||
],
|
||||
])
|
||||
->setExtras([
|
||||
|
@@ -12,18 +12,33 @@ declare(strict_types=1);
|
||||
namespace Chill\DocStoreBundle\Repository;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||
use Chill\MainBundle\Entity\Scope;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface
|
||||
final readonly class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface
|
||||
{
|
||||
public function __construct(private EntityManagerInterface $em, private AuthorizationHelperInterface $authorizationHelper, private CenterResolverDispatcher $centerResolverDispatcher, private Security $security)
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private CenterResolverManagerInterface $centerResolverManager,
|
||||
private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
|
||||
private Security $security,
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildQueryByPerson(Person $person): QueryBuilder
|
||||
@@ -37,6 +52,128 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
|
||||
return $qb;
|
||||
}
|
||||
|
||||
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
|
||||
{
|
||||
$query = $this->buildBaseFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||
|
||||
return $this->addFetchQueryByPersonACL($query, $person);
|
||||
}
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
|
||||
{
|
||||
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
||||
$participationMetadata = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
PersonDocumentGenericDocProvider::KEY,
|
||||
sprintf('jsonb_build_object(\'id\', person_document.%s)', $personDocMetadata->getSingleIdentifierColumnName()),
|
||||
sprintf('person_document.%s', $personDocMetadata->getColumnName('date')),
|
||||
sprintf('%s AS person_document', $personDocMetadata->getSchemaName().'.'.$personDocMetadata->getTableName())
|
||||
);
|
||||
|
||||
$query->addJoinClause(
|
||||
sprintf(
|
||||
'JOIN %s AS participation ON participation.%s = person_document.%s '.
|
||||
'AND person_document.%s BETWEEN participation.%s AND COALESCE(participation.%s, \'infinity\'::date)',
|
||||
$participationMetadata->getTableName(),
|
||||
$participationMetadata->getSingleAssociationJoinColumnName('person'),
|
||||
$personDocMetadata->getSingleAssociationJoinColumnName('person'),
|
||||
$personDocMetadata->getColumnName('date'),
|
||||
$participationMetadata->getColumnName('startDate'),
|
||||
$participationMetadata->getColumnName('endDate')
|
||||
)
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf('participation.%s = ?', $participationMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod')),
|
||||
[$period->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
// can we see the document for this person ?
|
||||
$orPersonId = [];
|
||||
foreach ($period->getParticipations() as $participation) {
|
||||
if (!$this->security->isGranted(PersonDocumentVoter::SEE, $participation->getPerson())) {
|
||||
continue;
|
||||
}
|
||||
$orPersonId[] = $participation->getPerson()->getId();
|
||||
|
||||
}
|
||||
|
||||
if ([] === $orPersonId) {
|
||||
$query->addWhereClause('FALSE = TRUE');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf(
|
||||
'participation.%s IN (%s)',
|
||||
$participationMetadata->getSingleAssociationJoinColumnName('person'),
|
||||
implode(', ', array_fill(0, count($orPersonId), '?'))
|
||||
),
|
||||
$orPersonId,
|
||||
array_fill(0, count($orPersonId), Types::INTEGER)
|
||||
);
|
||||
|
||||
return $this->addFilterClauses($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
||||
|
||||
$query = new FetchQuery(
|
||||
PersonDocumentGenericDocProvider::KEY,
|
||||
sprintf('jsonb_build_object(\'id\', person_document.%s)', $personDocMetadata->getSingleIdentifierColumnName()),
|
||||
sprintf('person_document.%s', $personDocMetadata->getColumnName('date')),
|
||||
sprintf('%s AS person_document', $personDocMetadata->getSchemaName().'.'.$personDocMetadata->getTableName())
|
||||
);
|
||||
|
||||
$query->addWhereClause(
|
||||
sprintf('person_document.%s = ?', $personDocMetadata->getSingleAssociationJoinColumnName('person')),
|
||||
[$person->getId()],
|
||||
[Types::INTEGER]
|
||||
);
|
||||
|
||||
return $this->addFilterClauses($query, $startDate, $endDate, $content);
|
||||
}
|
||||
|
||||
private function addFilterClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
|
||||
{
|
||||
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
||||
|
||||
if (null !== $startDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('? <= %s', $personDocMetadata->getColumnName('date')),
|
||||
[$startDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $endDate) {
|
||||
$query->addWhereClause(
|
||||
sprintf('? >= %s', $personDocMetadata->getColumnName('date')),
|
||||
[$endDate],
|
||||
[Types::DATE_IMMUTABLE]
|
||||
);
|
||||
}
|
||||
|
||||
if (null !== $content and '' !== $content) {
|
||||
$query->addWhereClause(
|
||||
sprintf(
|
||||
'(%s ilike ? OR %s ilike ?)',
|
||||
$personDocMetadata->getColumnName('title'),
|
||||
$personDocMetadata->getColumnName('description')
|
||||
),
|
||||
['%' . $content . '%', '%' . $content . '%'],
|
||||
[Types::STRING, Types::STRING]
|
||||
);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function countByPerson(Person $person): int
|
||||
{
|
||||
$qb = $this->buildQueryByPerson($person)->select('COUNT(d)');
|
||||
@@ -63,16 +200,58 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
|
||||
|
||||
private function addACL(QueryBuilder $qb, Person $person): void
|
||||
{
|
||||
$center = $this->centerResolverDispatcher->resolveCenter($person);
|
||||
$reachableScopes = [];
|
||||
|
||||
$reachableScopes = $this->authorizationHelper
|
||||
->getReachableScopes(
|
||||
$this->security->getUser(),
|
||||
PersonDocumentVoter::SEE,
|
||||
$center
|
||||
);
|
||||
foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
|
||||
$reachableScopes = [
|
||||
...$reachableScopes,
|
||||
...$this->authorizationHelperForCurrentUser
|
||||
->getReachableScopes(
|
||||
PersonDocumentVoter::SEE,
|
||||
$center
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
if ([] === $reachableScopes) {
|
||||
$qb->andWhere("'FALSE' = 'TRUE'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()->in('d.scope', ':scopes'))
|
||||
->setParameter('scopes', $reachableScopes);
|
||||
}
|
||||
|
||||
private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery
|
||||
{
|
||||
$personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
|
||||
|
||||
$reachableScopes = [];
|
||||
|
||||
foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
|
||||
$reachableScopes = [
|
||||
...$reachableScopes,
|
||||
...$this->authorizationHelperForCurrentUser->getReachableScopes(PersonDocumentVoter::SEE, $center)
|
||||
];
|
||||
}
|
||||
|
||||
if ([] === $reachableScopes) {
|
||||
$fetchQuery->addWhereClause('FALSE = TRUE');
|
||||
|
||||
return $fetchQuery;
|
||||
}
|
||||
|
||||
$fetchQuery->addWhereClause(
|
||||
sprintf(
|
||||
'person_document.%s IN (%s)',
|
||||
$personDocMetadata->getSingleAssociationJoinColumnName('scope'),
|
||||
implode(', ', array_fill(0, count($reachableScopes), '?'))
|
||||
),
|
||||
array_map(static fn (Scope $s) => $s->getId(), $reachableScopes),
|
||||
array_fill(0, count($reachableScopes), Types::INTEGER)
|
||||
);
|
||||
|
||||
return $fetchQuery;
|
||||
}
|
||||
}
|
||||
|
@@ -11,11 +11,33 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\DocStoreBundle\Repository;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
interface PersonDocumentACLAwareRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @deprecated use fetch query for listing and counting person documents
|
||||
*/
|
||||
public function countByPerson(Person $person): int;
|
||||
|
||||
/**
|
||||
* @deprecated use fetch query for listing and counting person documents
|
||||
*/
|
||||
public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array;
|
||||
|
||||
public function buildFetchQueryForPerson(
|
||||
Person $person,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null
|
||||
): FetchQueryInterface;
|
||||
|
||||
public function buildFetchQueryForAccompanyingPeriod(
|
||||
AccompanyingPeriod $period,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null
|
||||
): FetchQueryInterface;
|
||||
}
|
||||
|
@@ -0,0 +1,57 @@
|
||||
<?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\DocStoreBundle\Repository;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use UnexpectedValueException;
|
||||
|
||||
/**
|
||||
* @template ObjectRepository<PersonDocument::class>
|
||||
*/
|
||||
readonly class PersonDocumentRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager
|
||||
) {
|
||||
$this->repository = $this->entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?PersonDocument
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
public function findAll()
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?PersonDocument
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return PersonDocument::class;
|
||||
}
|
||||
}
|
@@ -44,3 +44,9 @@ async function download_and_open(event: Event): Promise<void> {
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,51 +1,96 @@
|
||||
<template>
|
||||
<a :class="props.classes" @click="download_and_open($event)">
|
||||
<i class="fa fa-download"></i>
|
||||
Télécharger
|
||||
</a>
|
||||
<a v-if="!state.is_ready" :class="props.classes" @click="download_and_open($event)">
|
||||
<i class="fa fa-download"></i>
|
||||
Télécharger
|
||||
</a>
|
||||
<a v-else :class="props.classes" target="_blank" :type="props.storedObject.type" :download="buildDocumentName()" :href="state.href_url" ref="open_button">
|
||||
<i class="fa fa-external-link"></i>
|
||||
Ouvrir
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {reactive} from "vue";
|
||||
import {reactive, ref, nextTick, onMounted} from "vue";
|
||||
import {build_download_info_link, download_and_decrypt_doc} from "./helpers";
|
||||
import mime from "mime";
|
||||
import {StoredObject} from "../../types";
|
||||
|
||||
interface DownloadButtonConfig {
|
||||
storedObject: StoredObject,
|
||||
classes: {[k: string]: boolean},
|
||||
filename?: string,
|
||||
storedObject: StoredObject,
|
||||
classes: { [k: string]: boolean },
|
||||
filename?: string,
|
||||
}
|
||||
|
||||
interface DownloadButtonState {
|
||||
content: null|string
|
||||
is_ready: boolean,
|
||||
is_running: boolean,
|
||||
href_url: string,
|
||||
}
|
||||
|
||||
const props = defineProps<DownloadButtonConfig>();
|
||||
const state: DownloadButtonState = reactive({content: null});
|
||||
const state: DownloadButtonState = reactive({is_ready: false, is_running: false, href_url: "#"});
|
||||
|
||||
const open_button = ref<HTMLAnchorElement | null>(null);
|
||||
|
||||
function buildDocumentName(): string {
|
||||
const document_name = props.filename || 'document';
|
||||
const ext = mime.getExtension(props.storedObject.type);
|
||||
|
||||
if (null !== ext) {
|
||||
return document_name + '.' + ext;
|
||||
}
|
||||
|
||||
return document_name;
|
||||
}
|
||||
|
||||
async function download_and_open(event: Event): Promise<void> {
|
||||
const button = event.target as HTMLAnchorElement;
|
||||
const button = event.target as HTMLAnchorElement;
|
||||
|
||||
if (null === state.content) {
|
||||
event.preventDefault();
|
||||
if (state.is_running) {
|
||||
console.log('state is running, aborting');
|
||||
return;
|
||||
}
|
||||
|
||||
state.is_running = true;
|
||||
|
||||
if (state.is_ready) {
|
||||
console.log('state is ready. This should not happens');
|
||||
return;
|
||||
}
|
||||
|
||||
const urlInfo = build_download_info_link(props.storedObject.filename);
|
||||
let raw;
|
||||
|
||||
const raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
|
||||
state.content = window.URL.createObjectURL(raw);
|
||||
|
||||
button.href = window.URL.createObjectURL(raw);
|
||||
button.type = props.storedObject.type;
|
||||
|
||||
button.download = props.filename || 'document';
|
||||
|
||||
const ext = mime.getExtension(props.storedObject.type);
|
||||
if (null !== ext) {
|
||||
button.download = button.download + '.' + ext;
|
||||
try {
|
||||
raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
|
||||
} catch (e) {
|
||||
console.error("error while downloading and decrypting document");
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
button.click();
|
||||
console.log('document downloading (and decrypting) successfully');
|
||||
|
||||
console.log('creating the url')
|
||||
state.href_url = window.URL.createObjectURL(raw);
|
||||
console.log('url created', state.href_url);
|
||||
state.is_running = false;
|
||||
state.is_ready = true;
|
||||
console.log('new button marked as ready');
|
||||
console.log('will click on button');
|
||||
|
||||
console.log('openbutton is now', open_button.value);
|
||||
|
||||
await nextTick();
|
||||
console.log('next tick actions');
|
||||
console.log('openbutton after next tick', open_button.value);
|
||||
open_button.value?.click();
|
||||
console.log('open button should have been clicked');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
@@ -40,5 +40,8 @@ async function beforeLeave(event: Event): Promise<true> {
|
||||
</script>
|
||||
|
||||
<style scoped lang="sass">
|
||||
i.fa::before {
|
||||
color: var(--bs-dropdown-link-hover-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@@ -149,16 +149,21 @@ async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKe
|
||||
}
|
||||
|
||||
if (iv.length === 0) {
|
||||
console.log('returning document immediatly');
|
||||
return rawResponse.blob();
|
||||
}
|
||||
|
||||
console.log('start decrypting doc');
|
||||
|
||||
const rawBuffer = await rawResponse.arrayBuffer();
|
||||
|
||||
try {
|
||||
const key = await window.crypto.subtle
|
||||
.importKey('jwk', keyData, { name: algo }, false, ['decrypt']);
|
||||
console.log('key created');
|
||||
const decrypted = await window.crypto.subtle
|
||||
.decrypt({ name: algo, iv: iv }, key, rawBuffer);
|
||||
console.log('doc decrypted');
|
||||
|
||||
return Promise.resolve(new Blob([decrypted]));
|
||||
} catch (e) {
|
||||
|
@@ -31,8 +31,8 @@
|
||||
'title' : 'Delete document ?'|trans,
|
||||
'display_content' : block('docdescription'),
|
||||
'confirm_question' : 'Are you sure you want to remove this document ?'|trans,
|
||||
'cancel_route' : 'accompanying_course_document_index',
|
||||
'cancel_parameters' : {'course' : accompanyingCourse.id, 'id': document.id},
|
||||
'cancel_route' : 'chill_docstore_generic-doc_by-period_index',
|
||||
'cancel_parameters' : {'id' : accompanyingCourse.id},
|
||||
'form' : delete_form
|
||||
} ) }}
|
||||
{% endblock %}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||
<a href="{{ chill_return_path_or('chill_docstore_generic-doc_by-period_index', {'id': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -31,7 +31,7 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="edit">
|
||||
<button class="btn btn-edit">{{ 'Edit'|trans }}</button>
|
||||
<button class="btn btn-save">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@@ -1,52 +0,0 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = '' %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Documents' }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="document-list">
|
||||
<h1>{{ 'Documents' }}</h1>
|
||||
|
||||
{% if documents|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No documents'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table chill-task-list">
|
||||
{% for document in documents %}
|
||||
{% include '@ChillDocStore/List/list_item.html.twig' %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ chill_pagination(pagination) }}
|
||||
|
||||
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\AccompanyingPeriod" data-entity-id="{{ accompanyingCourse.id }}"></div>
|
||||
|
||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="create">
|
||||
<a href="{{ path('accompanying_course_document_new', {'course': accompanyingCourse.id}) }}" class="btn btn-create">
|
||||
{{ 'Create'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@@ -25,7 +25,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||
<a href="{{ chill_return_path_or('chill_docstore_generic-doc_by-period_index', {'id': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -46,7 +46,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||
<a href="{{ chill_return_path_or('chill_docstore_generic-doc_by-period_index', {'id': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
|
||||
<div
|
||||
<div class="d-inline-flex"
|
||||
data-download-buttons
|
||||
data-stored-object="{{ document_json|json_encode|escape('html_attr') }}"
|
||||
data-can-edit="{{ can_edit ? '1' : '0' }}"
|
||||
|
@@ -0,0 +1,54 @@
|
||||
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = '' %}
|
||||
|
||||
{% block title %}
|
||||
{{ 'Documents' }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="document-list">
|
||||
<h1>{{ 'Documents' }}</h1>
|
||||
|
||||
{{ filter|chill_render_filter_order_helper }}
|
||||
|
||||
{% if documents|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No documents'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table chill-task-list">
|
||||
{% for document in documents %}
|
||||
{{ document|chill_generic_doc_render }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ chill_pagination(pagination) }}
|
||||
|
||||
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\AccompanyingPeriod" data-entity-id="{{ accompanyingCourse.id }}"></div>
|
||||
|
||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="create">
|
||||
<a href="{{ path('accompanying_course_document_new', {'course': accompanyingCourse.id}) }}" class="btn btn-create">
|
||||
{{ 'Create'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@@ -44,12 +44,14 @@
|
||||
<div class="col-md-10 col-xxl">
|
||||
<h1>{{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}</h1>
|
||||
|
||||
{{ filter|chill_render_filter_order_helper }}
|
||||
|
||||
{% if documents|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'No documents'|trans }}</p>
|
||||
{% else %}
|
||||
<div class="flex-table chill-task-list">
|
||||
{% for document in documents %}
|
||||
{% include 'ChillDocStoreBundle:List:list_item.html.twig' %}
|
||||
{{ document|chill_generic_doc_render }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
@@ -10,8 +10,23 @@
|
||||
{% elseif document.object.isFailure %}
|
||||
<div class="badge text-bg-warning">{{ 'docgen.Doc generation failed'|trans }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if context == 'person' and accompanyingCourse is defined %}
|
||||
<div>
|
||||
<span class="badge bg-primary">
|
||||
<i class="fa fa-random"></i> {{ accompanyingCourse.id }}
|
||||
</span>
|
||||
</div>
|
||||
{% elseif context == 'accompanying-period' and person is defined %}
|
||||
<div>
|
||||
<span class="badge bg-primary">
|
||||
{{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
<div class="denomination h2">
|
||||
{{ document.title }}
|
||||
{{ document.title|chill_print_or_message("No title") }}
|
||||
</div>
|
||||
{% if document.object.type is not empty %}
|
||||
<div>
|
||||
|
@@ -36,8 +36,8 @@
|
||||
'title' : 'Delete document ?'|trans,
|
||||
'display_content' : block('docdescription'),
|
||||
'confirm_question' : 'Are you sure you want to remove this document ?'|trans,
|
||||
'cancel_route' : 'person_document_index',
|
||||
'cancel_parameters' : {'person' : person.id, 'id': document.id},
|
||||
'cancel_route' : 'chill_docstore_generic-doc_by-person_index',
|
||||
'cancel_parameters' : {'id' : person.id},
|
||||
'form' : delete_form
|
||||
} ) }}
|
||||
{% endblock %}
|
||||
|
@@ -38,7 +38,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('person_document_index', {'person': person.id}) }}" class="btn btn-cancel">
|
||||
<a href="{{ chill_return_path_or('chill_docstore_generic-doc_by-person_index', {'id': person.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -42,7 +42,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('person_document_index', {'person': person.id}) }}" class="btn btn-cancel">
|
||||
<a href="{{ chill_return_path_or('chill_docstore_generic-doc_by-person_index', {'id': person.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -63,7 +63,7 @@
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('person_document_index', {'person': person.id}) }}" class="btn btn-cancel">
|
||||
<a href="{{ chill_return_path_or('chill_docstore_generic-doc_by-person_index', {'id': person.id}) }}" class="btn btn-cancel">
|
||||
{{ 'Back to the list' | trans }}
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -0,0 +1,89 @@
|
||||
<?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\DocStoreBundle\Tests\GenericDoc;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class FetchQueryToSqlBuilderTest extends KernelTestCase
|
||||
{
|
||||
public function testToSql(): void
|
||||
{
|
||||
$query = new FetchQuery(
|
||||
'test',
|
||||
'jsonb_build_object(\'id\', a.column)',
|
||||
'a.datecolumn',
|
||||
'my_table a'
|
||||
);
|
||||
$query->addJoinClause('LEFT JOIN other b ON a.id = b.foreign_id', ['foo'], [Types::STRING]);
|
||||
$index = $query->addJoinClause('LEFT JOIN other c ON a.id = c.foreign_id', ['bar'], [Types::STRING]);
|
||||
$query->addJoinClause('LEFT JOIN other d ON a.id = d.foreign_id', ['bar_baz'], [Types::STRING]);
|
||||
$query->removeJoinClause($index);
|
||||
$query->addWhereClause('b.item = ?', ['baz'], [Types::STRING]);
|
||||
$index = $query->addWhereClause('b.cancel', [ 'foz'], [Types::STRING]);
|
||||
$query->removeWhereClause($index);
|
||||
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$filteredSql =
|
||||
implode(" ", array_filter(
|
||||
explode(" ", str_replace("\n", "", $sql)),
|
||||
fn (string $tok) => $tok !== ""
|
||||
))
|
||||
;
|
||||
|
||||
self::assertEquals(
|
||||
"SELECT 'test' AS key, jsonb_build_object('id', a.column) AS identifiers, ".
|
||||
"a.datecolumn::date AS doc_date FROM my_table a LEFT JOIN other b ON a.id = b.foreign_id LEFT JOIN other d ON a.id = d.foreign_id WHERE b.item = ?",
|
||||
$filteredSql
|
||||
);
|
||||
self::assertEquals(['foo', 'bar_baz', 'baz'], $params);
|
||||
self::assertEquals([Types::STRING, Types::STRING, Types::STRING], $types);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function testToSqlWithoutWhere(): void
|
||||
{
|
||||
$query = new FetchQuery(
|
||||
'test',
|
||||
'jsonb_build_object(\'id\', a.column)',
|
||||
'a.datecolumn',
|
||||
'my_table a'
|
||||
);
|
||||
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$filteredSql =
|
||||
implode(" ", array_filter(
|
||||
explode(" ", str_replace("\n", "", $sql)),
|
||||
fn (string $tok) => $tok !== ""
|
||||
))
|
||||
;
|
||||
|
||||
self::assertEquals(
|
||||
"SELECT 'test' AS key, jsonb_build_object('id', a.column) AS identifiers, ".
|
||||
"a.datecolumn::date AS doc_date FROM my_table a",
|
||||
$filteredSql
|
||||
);
|
||||
self::assertEquals([], $params);
|
||||
self::assertEquals([], $types);
|
||||
}
|
||||
|
||||
}
|
217
src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php
Normal file
217
src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?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\DocStoreBundle\Tests\GenericDoc;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocDTO;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
|
||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||
use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ManagerTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private Connection $connection;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
|
||||
$this->em = self::$container->get(EntityManagerInterface::class);
|
||||
$this->connection = self::$container->get(Connection::class);
|
||||
}
|
||||
|
||||
public function testCountByAccompanyingPeriod(): void
|
||||
{
|
||||
$period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $period) {
|
||||
throw new \UnexpectedValueException("period not found");
|
||||
}
|
||||
|
||||
$manager = new Manager(
|
||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||
[new SimpleGenericDocPersonProvider()],
|
||||
$this->connection,
|
||||
);
|
||||
|
||||
$nb = $manager->countDocForAccompanyingPeriod($period);
|
||||
|
||||
self::assertIsInt($nb);
|
||||
}
|
||||
|
||||
public function testCountByPerson(): void
|
||||
{
|
||||
$person = $this->em->createQuery('SELECT a FROM '.Person::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $person) {
|
||||
throw new \UnexpectedValueException("person found");
|
||||
}
|
||||
|
||||
$manager = new Manager(
|
||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||
[new SimpleGenericDocPersonProvider()],
|
||||
$this->connection,
|
||||
);
|
||||
|
||||
$nb = $manager->countDocForPerson($person);
|
||||
|
||||
self::assertIsInt($nb);
|
||||
}
|
||||
|
||||
public function testFindDocByAccompanyingPeriod(): void
|
||||
{
|
||||
$period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $period) {
|
||||
throw new \UnexpectedValueException("period not found");
|
||||
}
|
||||
|
||||
$manager = new Manager(
|
||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||
[new SimpleGenericDocPersonProvider()],
|
||||
$this->connection,
|
||||
);
|
||||
|
||||
foreach ($manager->findDocForAccompanyingPeriod($period) as $doc) {
|
||||
self::assertInstanceOf(GenericDocDTO::class, $doc);
|
||||
}
|
||||
}
|
||||
|
||||
public function testFindDocByPerson(): void
|
||||
{
|
||||
$person = $this->em->createQuery('SELECT a FROM '.Person::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $person) {
|
||||
throw new \UnexpectedValueException("person not found");
|
||||
}
|
||||
|
||||
$manager = new Manager(
|
||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||
[new SimpleGenericDocPersonProvider()],
|
||||
$this->connection,
|
||||
);
|
||||
|
||||
foreach ($manager->findDocForPerson($person) as $doc) {
|
||||
self::assertInstanceOf(GenericDocDTO::class, $doc);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPlacesForPerson(): void
|
||||
{
|
||||
$person = $this->em->createQuery('SELECT a FROM '.Person::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $person) {
|
||||
throw new \UnexpectedValueException("person not found");
|
||||
}
|
||||
|
||||
$manager = new Manager(
|
||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||
[new SimpleGenericDocPersonProvider()],
|
||||
$this->connection,
|
||||
);
|
||||
|
||||
$places = $manager->placesForPerson($person);
|
||||
|
||||
self::assertEquals(['dummy_person_doc'], $places);
|
||||
}
|
||||
|
||||
public function testPlacesForAccompanyingPeriod(): void
|
||||
{
|
||||
$period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $period) {
|
||||
throw new \UnexpectedValueException("period not found");
|
||||
}
|
||||
|
||||
$manager = new Manager(
|
||||
[new SimpleGenericDocAccompanyingPeriodProvider()],
|
||||
[new SimpleGenericDocPersonProvider()],
|
||||
$this->connection,
|
||||
);
|
||||
|
||||
$places = $manager->placesForAccompanyingPeriod($period);
|
||||
|
||||
self::assertEquals(['accompanying_course_document_dummy'], $places);
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class SimpleGenericDocAccompanyingPeriodProvider implements GenericDocForAccompanyingPeriodProviderInterface
|
||||
{
|
||||
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$query = new FetchQuery(
|
||||
'accompanying_course_document_dummy',
|
||||
sprintf('jsonb_build_object(\'id\', %s)', 'id'),
|
||||
'd',
|
||||
'(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (id, d)',
|
||||
);
|
||||
|
||||
$query->addWhereClause('d > ?', [new \DateTimeImmutable('2023-01-01')], [Types::DATE_IMMUTABLE]);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final readonly class SimpleGenericDocPersonProvider implements GenericDocForPersonProviderInterface
|
||||
{
|
||||
public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
|
||||
{
|
||||
$query = new FetchQuery(
|
||||
'dummy_person_doc',
|
||||
sprintf('jsonb_build_object(\'id\', %s)', 'id'),
|
||||
'd',
|
||||
'(VALUES (1, \'2023-05-01\'::date), (2, \'2023-05-01\'::date)) AS sq (id, d)',
|
||||
);
|
||||
|
||||
$query->addWhereClause('d > ?', [new \DateTimeImmutable('2023-01-01')], [Types::DATE_IMMUTABLE]);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function isAllowedForPerson(Person $person): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?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\DocStoreBundle\Tests\GenericDoc\Providers;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||
use Chill\DocStoreBundle\GenericDoc\Providers\AccompanyingCourseDocumentGenericDocProvider;
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class AccompanyingCourseDocumentGenericDocProviderTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideSearchArguments
|
||||
*/
|
||||
public function testWithoutAnyArgument(?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content = null): void
|
||||
{
|
||||
$period = $this->entityManager->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $period) {
|
||||
throw new \UnexpectedValueException("period not found");
|
||||
}
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted(AccompanyingCourseDocumentVoter::SEE, $period)
|
||||
->willReturn(true);
|
||||
|
||||
$provider = new AccompanyingCourseDocumentGenericDocProvider(
|
||||
$security->reveal(),
|
||||
$this->entityManager
|
||||
);
|
||||
|
||||
$query = $provider->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
||||
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$nb = $this->entityManager->getConnection()->executeQuery('SELECT COUNT(*) FROM ('.$sql.') AS sq', $params, $types)
|
||||
->fetchOne();
|
||||
|
||||
self::assertIsInt($nb);
|
||||
}
|
||||
|
||||
public function provideSearchArguments(): iterable
|
||||
{
|
||||
yield [null, null, null];
|
||||
yield [new \DateTimeImmutable('1 month ago'), null, null];
|
||||
yield [new \DateTimeImmutable('1 month ago'), new \DateTimeImmutable('now'), null];
|
||||
yield [null, null, 'test'];
|
||||
}
|
||||
}
|
@@ -0,0 +1,84 @@
|
||||
<?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\DocStoreBundle\Tests\GenericDoc\Providers;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
|
||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class PersonDocumentGenericDocProviderTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
$this->personDocumentACLAwareRepository = self::$container->get(PersonDocumentACLAwareRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataBuildFetchQueryForPerson
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
* @throws \Doctrine\ORM\NoResultException
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function testBuildFetchQueryForPerson(?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): void
|
||||
{
|
||||
$security = $this->prophesize(Security::class);
|
||||
$person = $this->entityManager->createQuery('SELECT a FROM '.Person::class.' a')
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $person) {
|
||||
throw new \UnexpectedValueException("person found");
|
||||
}
|
||||
|
||||
$provider = new PersonDocumentGenericDocProvider(
|
||||
$security->reveal(),
|
||||
$this->personDocumentACLAwareRepository
|
||||
);
|
||||
|
||||
$query = $provider->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$nb = $this->entityManager->getConnection()
|
||||
->fetchOne("SELECT COUNT(*) AS nb FROM (${sql}) AS sq", $params, $types);
|
||||
|
||||
self::assertIsInt($nb, "Test that the query is syntactically correct");
|
||||
}
|
||||
|
||||
public function provideDataBuildFetchQueryForPerson(): iterable
|
||||
{
|
||||
yield [null, null, null];
|
||||
yield [new DateTimeImmutable('1 year ago'), null, null];
|
||||
yield [null, new DateTimeImmutable('1 year ago'), null];
|
||||
yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null];
|
||||
yield [null, null, 'test'];
|
||||
yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
<?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\DocStoreBundle\Tests\Repository;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepository;
|
||||
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
|
||||
use Chill\MainBundle\Entity\Center;
|
||||
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class PersonDocumentACLAwareRepositoryTest extends KernelTestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private ScopeRepositoryInterface $scopeRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
$this->scopeRepository = self::$container->get(ScopeRepositoryInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDataBuildFetchQueryForPerson
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
* @throws \Doctrine\ORM\NoResultException
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function testBuildFetchQueryForPerson(?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): void
|
||||
{
|
||||
$centerManager = $this->prophesize(CenterResolverManagerInterface::class);
|
||||
$centerManager->resolveCenters(Argument::type(Person::class))
|
||||
->willReturn([new Center()]);
|
||||
|
||||
$scopes = $this->scopeRepository->findAll();
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
|
||||
$authorizationHelper->getReachableScopes(PersonDocumentVoter::SEE, Argument::any())->willReturn($scopes);
|
||||
|
||||
$repository = new PersonDocumentACLAwareRepository(
|
||||
$this->entityManager,
|
||||
$centerManager->reveal(),
|
||||
$authorizationHelper->reveal(),
|
||||
$this->prophesize(Security::class)->reveal()
|
||||
);
|
||||
|
||||
$person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p")
|
||||
->setMaxResults(1)
|
||||
->getSingleResult();
|
||||
|
||||
if (null === $person) {
|
||||
throw new \RuntimeException("person not exists in database");
|
||||
}
|
||||
|
||||
$query = $repository->buildFetchQueryForPerson($person, $startDate, $endDate, $content);
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$nb = $this->entityManager->getConnection()
|
||||
->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types);
|
||||
|
||||
self::assertIsInt($nb, "test that the query could be executed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideDateForFetchQueryForAccompanyingPeriod
|
||||
*/
|
||||
public function testBuildFetchQueryForAccompanyingPeriod(
|
||||
AccompanyingPeriod $period,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null
|
||||
): void {
|
||||
$centerManager = $this->prophesize(CenterResolverManagerInterface::class);
|
||||
$centerManager->resolveCenters(Argument::type(Person::class))
|
||||
->willReturn([new Center()]);
|
||||
|
||||
$scopes = $this->scopeRepository->findAll();
|
||||
$authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
|
||||
$authorizationHelper->getReachableScopes(PersonDocumentVoter::SEE, Argument::any())->willReturn($scopes);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted(PersonDocumentVoter::SEE, Argument::type(Person::class))->willReturn(true);
|
||||
|
||||
$repository = new PersonDocumentACLAwareRepository(
|
||||
$this->entityManager,
|
||||
$centerManager->reveal(),
|
||||
$authorizationHelper->reveal(),
|
||||
$security->reveal()
|
||||
);
|
||||
|
||||
$query = $repository->buildFetchQueryForAccompanyingPeriod($period, $startDate, $endDate, $content);
|
||||
['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
|
||||
|
||||
$nb = $this->entityManager->getConnection()
|
||||
->fetchOne("SELECT COUNT(*) FROM ({$sql}) AS sq", $params, $types);
|
||||
|
||||
self::assertIsInt($nb, "test that the query could be executed");
|
||||
}
|
||||
|
||||
public function provideDateForFetchQueryForAccompanyingPeriod(): iterable
|
||||
{
|
||||
$this->setUp();
|
||||
|
||||
if (null === $period = $this->entityManager->createQuery(
|
||||
"SELECT p FROM " . AccompanyingPeriod::class . " p WHERE SIZE(p.participations) > 0"
|
||||
)
|
||||
->setMaxResults(1)->getSingleResult()) {
|
||||
throw new \RuntimeException("no course found");
|
||||
}
|
||||
|
||||
yield [$period, null, null, null];
|
||||
yield [$period, new DateTimeImmutable('1 year ago'), null, null];
|
||||
yield [$period, null, new DateTimeImmutable('1 year ago'), null];
|
||||
yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null];
|
||||
yield [$period, null, null, 'test'];
|
||||
yield [$period, new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
|
||||
|
||||
}
|
||||
|
||||
public function provideDataBuildFetchQueryForPerson(): iterable
|
||||
{
|
||||
yield [null, null, null];
|
||||
yield [new DateTimeImmutable('1 year ago'), null, null];
|
||||
yield [null, new DateTimeImmutable('1 year ago'), null];
|
||||
yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), null];
|
||||
yield [null, null, 'test'];
|
||||
yield [new DateTimeImmutable('2 years ago'), new DateTimeImmutable('1 year ago'), 'test'];
|
||||
}
|
||||
|
||||
}
|
@@ -43,8 +43,7 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
|
||||
|
||||
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
|
||||
{
|
||||
$course = $this->getRelatedEntity($entityWorkflow)
|
||||
->getCourse();
|
||||
$course = $this->getRelatedEntity($entityWorkflow)?->getCourse();
|
||||
$persons = [];
|
||||
|
||||
if (null !== $course) {
|
||||
|
@@ -2,46 +2,72 @@ parameters:
|
||||
# cl_chill_person.example.class: Chill\PersonBundle\Example
|
||||
|
||||
services:
|
||||
Chill\DocStoreBundle\Repository\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: "../Repository/"
|
||||
Chill\DocStoreBundle\Repository\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: "../Repository/"
|
||||
|
||||
Chill\DocStoreBundle\Form\DocumentCategoryType:
|
||||
class: Chill\DocStoreBundle\Form\DocumentCategoryType
|
||||
arguments: ["%kernel.bundles%"]
|
||||
tags:
|
||||
- { name: form.type }
|
||||
Chill\DocStoreBundle\Form\DocumentCategoryType:
|
||||
class: Chill\DocStoreBundle\Form\DocumentCategoryType
|
||||
arguments: [ "%kernel.bundles%" ]
|
||||
tags:
|
||||
- { name: form.type }
|
||||
|
||||
Chill\DocStoreBundle\Form\PersonDocumentType:
|
||||
class: Chill\DocStoreBundle\Form\PersonDocumentType
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
# arguments:
|
||||
# - "@chill.main.helper.translatable_string"
|
||||
tags:
|
||||
- { name: form.type, alias: chill_docstorebundle_form_document }
|
||||
Chill\DocStoreBundle\Form\PersonDocumentType:
|
||||
class: Chill\DocStoreBundle\Form\PersonDocumentType
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
# arguments:
|
||||
# - "@chill.main.helper.translatable_string"
|
||||
tags:
|
||||
- { name: form.type, alias: chill_docstorebundle_form_document }
|
||||
|
||||
Chill\DocStoreBundle\Security\Authorization\:
|
||||
resource: "./../Security/Authorization"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags:
|
||||
- { name: chill.role }
|
||||
Chill\DocStoreBundle\Security\Authorization\:
|
||||
resource: "./../Security/Authorization"
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
tags:
|
||||
- { name: chill.role }
|
||||
|
||||
Chill\DocStoreBundle\Workflow\:
|
||||
resource: './../Workflow/'
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
Chill\DocStoreBundle\Workflow\:
|
||||
resource: './../Workflow/'
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
||||
Chill\DocStoreBundle\Serializer\Normalizer\:
|
||||
autowire: true
|
||||
resource: '../Serializer/Normalizer/'
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 16 }
|
||||
Chill\DocStoreBundle\Serializer\Normalizer\:
|
||||
autowire: true
|
||||
resource: '../Serializer/Normalizer/'
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 16 }
|
||||
|
||||
Chill\DocStoreBundle\Service\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Service/'
|
||||
Chill\DocStoreBundle\Service\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../Service/'
|
||||
|
||||
Chill\DocStoreBundle\GenericDoc\Manager:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
arguments:
|
||||
$providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider
|
||||
$providersForPerson: !tagged_iterator chill_doc_store.generic_doc_person_provider
|
||||
|
||||
Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtension:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
||||
Chill\DocStoreBundle\GenericDoc\Twig\GenericDocExtensionRuntime:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
arguments:
|
||||
$renderers: !tagged_iterator chill_doc_store.generic_doc_renderer
|
||||
|
||||
Chill\DocStoreBundle\GenericDoc\Providers\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../GenericDoc/Providers/'
|
||||
|
||||
Chill\DocStoreBundle\GenericDoc\Renderer\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../GenericDoc/Renderer/'
|
||||
|
@@ -18,10 +18,19 @@ No document found: Aucun document trouvé
|
||||
The document is successfully registered: Le document est enregistré
|
||||
The document is successfully updated: Le document est mis à jour
|
||||
Any description: Aucune description
|
||||
Document from person %name%: Document de l'usager %name%
|
||||
See the document: Voir le document
|
||||
|
||||
document:
|
||||
Any title: Aucun titre
|
||||
|
||||
generic_doc:
|
||||
filter:
|
||||
keys:
|
||||
accompanying_course_document: Document du parcours
|
||||
person_document: Documents de l'usager
|
||||
date-range: Date du document
|
||||
|
||||
# delete
|
||||
Delete document ?: Supprimer le document ?
|
||||
Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ?
|
||||
|
Reference in New Issue
Block a user