mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 14:43:49 +00:00
Merge remote-tracking branch 'origin/111_exports_suite' into calendar/finalization
This commit is contained in:
@@ -11,23 +11,32 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Form\SavedExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\FormatterType;
|
||||
use Chill\MainBundle\Form\Type\Export\PickCenterType;
|
||||
use Chill\MainBundle\Redis\ChillRedis;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RedisException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function count;
|
||||
use function serialize;
|
||||
use function unserialize;
|
||||
@@ -38,35 +47,37 @@ use function unserialize;
|
||||
*/
|
||||
class ExportController extends AbstractController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
/**
|
||||
* @var ExportManager
|
||||
*/
|
||||
protected $exportManager;
|
||||
private $exportManager;
|
||||
|
||||
/**
|
||||
* @var FormFactoryInterface
|
||||
*/
|
||||
protected $formFactory;
|
||||
private $formFactory;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @var ChillRedis
|
||||
*/
|
||||
protected $redis;
|
||||
private $redis;
|
||||
|
||||
/**
|
||||
* @var SessionInterface
|
||||
*/
|
||||
protected $session;
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
private $translator;
|
||||
|
||||
public function __construct(
|
||||
ChillRedis $chillRedis,
|
||||
@@ -74,8 +85,10 @@ class ExportController extends AbstractController
|
||||
FormFactoryInterface $formFactory,
|
||||
LoggerInterface $logger,
|
||||
SessionInterface $session,
|
||||
TranslatorInterface $translator
|
||||
TranslatorInterface $translator,
|
||||
EntityManagerInterface $entityManager
|
||||
) {
|
||||
$this->entityManager = $entityManager;
|
||||
$this->redis = $chillRedis;
|
||||
$this->exportManager = $exportManager;
|
||||
$this->formFactory = $formFactory;
|
||||
@@ -142,6 +155,29 @@ class ExportController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/generate-from-saved/{id}", name="chill_main_export_generate_from_saved")
|
||||
*
|
||||
* @throws RedisException
|
||||
*/
|
||||
public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport);
|
||||
|
||||
$key = md5(uniqid((string) mt_rand(), false));
|
||||
|
||||
$this->redis->setEx($key, 3600, serialize($savedExport->getOptions()));
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_main_export_download',
|
||||
[
|
||||
'alias' => $savedExport->getExportAlias(),
|
||||
'key' => $key, 'prevent_save' => true,
|
||||
'returnPath' => $this->generateUrl('chill_main_export_saved_list_my'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the list of available exports.
|
||||
*/
|
||||
@@ -203,6 +239,46 @@ class ExportController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/export/save-from-key/{alias}/{key}", name="chill_main_export_save_from_key")
|
||||
*/
|
||||
public function saveFromKey(string $alias, string $key, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$data = $this->rebuildRawData($key);
|
||||
|
||||
$savedExport = new SavedExport();
|
||||
$savedExport
|
||||
->setOptions($data)
|
||||
->setExportAlias($alias)
|
||||
->setUser($user);
|
||||
|
||||
$form = $this->createForm(SavedExportType::class, $savedExport);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($savedExport);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_export_index');
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/SavedExport/new.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
'saved_export' => $savedExport,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a form to show on different steps.
|
||||
*
|
||||
@@ -418,28 +494,7 @@ class ExportController extends AbstractController
|
||||
|
||||
protected function rebuildData($key)
|
||||
{
|
||||
if (null === $key) {
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
if ($this->redis->exists($key) !== 1) {
|
||||
$this->addFlash('error', $this->translator->trans('This report is not available any more'));
|
||||
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
$serialized = $this->redis->get($key);
|
||||
|
||||
if (false === $serialized) {
|
||||
throw new LogicException('the key could not be reached from redis');
|
||||
}
|
||||
|
||||
$rawData = unserialize($serialized);
|
||||
|
||||
$this->logger->notice('[export] choices for an export unserialized', [
|
||||
'key' => $key,
|
||||
'rawData' => json_encode($rawData),
|
||||
]);
|
||||
$rawData = $this->rebuildRawData($key);
|
||||
|
||||
$alias = $rawData['alias'];
|
||||
|
||||
@@ -585,4 +640,32 @@ class ExportController extends AbstractController
|
||||
throw new LogicException("the step {$step} is not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
private function rebuildRawData(string $key): array
|
||||
{
|
||||
if (null === $key) {
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
if ($this->redis->exists($key) !== 1) {
|
||||
$this->addFlash('error', $this->translator->trans('This report is not available any more'));
|
||||
|
||||
throw $this->createNotFoundException('key does not exists');
|
||||
}
|
||||
|
||||
$serialized = $this->redis->get($key);
|
||||
|
||||
if (false === $serialized) {
|
||||
throw new LogicException('the key could not be reached from redis');
|
||||
}
|
||||
|
||||
$rawData = unserialize($serialized);
|
||||
|
||||
$this->logger->notice('[export] choices for an export unserialized', [
|
||||
'key' => $key,
|
||||
'rawData' => json_encode($rawData),
|
||||
]);
|
||||
|
||||
return $rawData;
|
||||
}
|
||||
}
|
||||
|
185
src/Bundle/ChillMainBundle/Controller/SavedExportController.php
Normal file
185
src/Bundle/ChillMainBundle/Controller/SavedExportController.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||
use Chill\MainBundle\Form\SavedExportType;
|
||||
use Chill\MainBundle\Repository\SavedExportRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function count;
|
||||
|
||||
class SavedExportController
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private ExportManager $exportManager;
|
||||
|
||||
private FormFactoryInterface $formFactory;
|
||||
|
||||
private SavedExportRepositoryInterface $savedExportRepository;
|
||||
|
||||
private Security $security;
|
||||
|
||||
private SessionInterface $session;
|
||||
|
||||
private EngineInterface $templating;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
public function __construct(
|
||||
EngineInterface $templating,
|
||||
EntityManagerInterface $entityManager,
|
||||
ExportManager $exportManager,
|
||||
FormFactoryInterface $formBuilder,
|
||||
SavedExportRepositoryInterface $savedExportRepository,
|
||||
Security $security,
|
||||
SessionInterface $session,
|
||||
TranslatorInterface $translator,
|
||||
UrlGeneratorInterface $urlGenerator
|
||||
) {
|
||||
$this->exportManager = $exportManager;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->formFactory = $formBuilder;
|
||||
$this->savedExportRepository = $savedExportRepository;
|
||||
$this->security = $security;
|
||||
$this->session = $session;
|
||||
$this->templating = $templating;
|
||||
$this->translator = $translator;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/saved/{id}/delete", name="chill_main_export_saved_delete")
|
||||
*/
|
||||
public function delete(SavedExport $savedExport, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted(SavedExportVoter::DELETE, $savedExport)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create();
|
||||
$form->add('submit', SubmitType::class, ['label' => 'Delete']);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->remove($savedExport);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Export is deleted'));
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate('chill_main_export_saved_list_my')
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillMain/SavedExport/delete.html.twig',
|
||||
[
|
||||
'saved_export' => $savedExport,
|
||||
'delete_form' => $form->createView(),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/saved/{id}/edit", name="chill_main_export_saved_edit")
|
||||
*/
|
||||
public function edit(SavedExport $savedExport, Request $request): Response
|
||||
{
|
||||
if (!$this->security->isGranted(SavedExportVoter::EDIT, $savedExport)) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$form = $this->formFactory->create(SavedExportType::class, $savedExport);
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Saved export is saved!'));
|
||||
|
||||
return new RedirectResponse(
|
||||
$this->urlGenerator->generate('chill_main_export_saved_list_my')
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillMain/SavedExport/edit.html.twig',
|
||||
[
|
||||
'form' => $form->createView(),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/exports/saved/my", name="chill_main_export_saved_list_my")
|
||||
*/
|
||||
public function list(): Response
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$this->security->isGranted('ROLE_USER') || !$user instanceof User) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
$exports = $this->savedExportRepository->findByUser($user, ['title' => 'ASC']);
|
||||
|
||||
// group by center
|
||||
/** @var array<string, array{saved: SavedExport, export: ExportInterface}> $exportsGrouped */
|
||||
$exportsGrouped = [];
|
||||
|
||||
foreach ($exports as $savedExport) {
|
||||
$export = $this->exportManager->getExport($savedExport->getExportAlias());
|
||||
|
||||
$exportsGrouped[
|
||||
$export instanceof GroupedExportInterface
|
||||
? $this->translator->trans($export->getGroup()) : '_'
|
||||
][] = ['saved' => $savedExport, 'export' => $export];
|
||||
}
|
||||
|
||||
ksort($exportsGrouped);
|
||||
|
||||
return new Response(
|
||||
$this->templating->render(
|
||||
'@ChillMain/SavedExport/index.html.twig',
|
||||
[
|
||||
'grouped_exports' => $exportsGrouped,
|
||||
'total' => count($exports),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -25,15 +25,19 @@ use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
|
||||
use Chill\MainBundle\Doctrine\DQL\Age;
|
||||
use Chill\MainBundle\Doctrine\DQL\Extract;
|
||||
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
|
||||
use Chill\MainBundle\Doctrine\DQL\Greatest;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
|
||||
use Chill\MainBundle\Doctrine\DQL\JsonExtract;
|
||||
use Chill\MainBundle\Doctrine\DQL\Least;
|
||||
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
|
||||
use Chill\MainBundle\Doctrine\DQL\Replace;
|
||||
use Chill\MainBundle\Doctrine\DQL\Similarity;
|
||||
use Chill\MainBundle\Doctrine\DQL\STContains;
|
||||
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
|
||||
use Chill\MainBundle\Doctrine\DQL\STX;
|
||||
use Chill\MainBundle\Doctrine\DQL\STY;
|
||||
use Chill\MainBundle\Doctrine\DQL\ToChar;
|
||||
use Chill\MainBundle\Doctrine\DQL\Unaccent;
|
||||
use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator;
|
||||
@@ -245,6 +249,10 @@ class ChillMainExtension extends Extension implements
|
||||
'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class,
|
||||
'ST_CONTAINS' => STContains::class,
|
||||
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
|
||||
'ST_X' => STX::class,
|
||||
'ST_Y' => STY::class,
|
||||
'GREATEST' => Greatest::class,
|
||||
'LEAST' => LEAST::class,
|
||||
],
|
||||
'datetime_functions' => [
|
||||
'EXTRACT' => Extract::class,
|
||||
|
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Greatest.php
Normal file
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Greatest.php
Normal file
@@ -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\MainBundle\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\Node;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Postgresql GREATEST function.
|
||||
*
|
||||
* Borrowed from https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Postgresql/Greatest.php
|
||||
* (https://github.com/beberlei/DoctrineExtensions/blob/master/LICENSE) and
|
||||
* https://gist.github.com/olimsaidov/4bbd530b1b645ce75e1bbb781b5dd91f
|
||||
*/
|
||||
class Greatest extends FunctionNode
|
||||
{
|
||||
/**
|
||||
* @var array|Node[]
|
||||
*/
|
||||
private array $exprs = [];
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'GREATEST(' . implode(', ', array_map(static function (Node $expr) use ($sqlWalker) {
|
||||
return $expr->dispatch($sqlWalker);
|
||||
}, $this->exprs)) . ')';
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$this->exprs = [];
|
||||
|
||||
$lexer = $parser->getLexer();
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
|
||||
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
}
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php
Normal file
57
src/Bundle/ChillMainBundle/Doctrine/DQL/Least.php
Normal file
@@ -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\MainBundle\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\AST\Node;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
/**
|
||||
* Postgresql LEAST function.
|
||||
*
|
||||
* Borrowed from https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Postgresql/Least.php
|
||||
* (https://github.com/beberlei/DoctrineExtensions/blob/master/LICENSE) and
|
||||
* https://gist.github.com/olimsaidov/4bbd530b1b645ce75e1bbb781b5dd91f
|
||||
*/
|
||||
class Least extends FunctionNode
|
||||
{
|
||||
/**
|
||||
* @var array|Node[]
|
||||
*/
|
||||
private array $exprs = [];
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return 'LEAST(' . implode(', ', array_map(static function (Node $expr) use ($sqlWalker) {
|
||||
return $expr->dispatch($sqlWalker);
|
||||
}, $this->exprs)) . ')';
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$this->exprs = [];
|
||||
|
||||
$lexer = $parser->getLexer();
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
|
||||
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
|
||||
$parser->match(Lexer::T_COMMA);
|
||||
$this->exprs[] = $parser->ArithmeticPrimary();
|
||||
}
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STX.php
Normal file
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STX.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
class STX extends FunctionNode
|
||||
{
|
||||
private $field;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return sprintf('ST_X(%s)', $this->field->dispatch($sqlWalker));
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->field = $parser->ArithmeticExpression();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php
Normal file
37
src/Bundle/ChillMainBundle/Doctrine/DQL/STY.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Doctrine\DQL;
|
||||
|
||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||
use Doctrine\ORM\Query\Lexer;
|
||||
use Doctrine\ORM\Query\Parser;
|
||||
use Doctrine\ORM\Query\SqlWalker;
|
||||
|
||||
class STY extends FunctionNode
|
||||
{
|
||||
private $field;
|
||||
|
||||
public function getSql(SqlWalker $sqlWalker)
|
||||
{
|
||||
return sprintf('ST_Y(%s)', $this->field->dispatch($sqlWalker));
|
||||
}
|
||||
|
||||
public function parse(Parser $parser)
|
||||
{
|
||||
$parser->match(Lexer::T_IDENTIFIER);
|
||||
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||
|
||||
$this->field = $parser->ArithmeticExpression();
|
||||
|
||||
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||
}
|
||||
}
|
@@ -15,6 +15,8 @@ use Chill\MainBundle\Doctrine\Model\Point;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation\Groups;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
@@ -97,6 +99,23 @@ class Address
|
||||
*/
|
||||
private $floor;
|
||||
|
||||
/**
|
||||
* List of geographical units and addresses.
|
||||
*
|
||||
* This list is computed by a materialized view. It won't be populated until a refresh is done
|
||||
* on the materialized view.
|
||||
*
|
||||
* @var Collection<int, GeographicalUnit>|GeographicalUnit[]
|
||||
* @readonly
|
||||
* @ORM\ManyToMany(targetEntity=GeographicalUnit::class)
|
||||
* @ORM\JoinTable(
|
||||
* name="view_chill_main_address_geographical_unit",
|
||||
* joinColumns={@ORM\JoinColumn(name="address_id")},
|
||||
* inverseJoinColumns={@ORM\JoinColumn(name="geographical_unit_id")}
|
||||
* )
|
||||
*/
|
||||
private Collection $geographicalUnits;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*
|
||||
@@ -104,8 +123,9 @@ class Address
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @Groups({"write"})
|
||||
* @readonly
|
||||
*/
|
||||
private $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* True if the address is a "no address", aka homeless person, ...
|
||||
@@ -190,6 +210,7 @@ class Address
|
||||
public function __construct()
|
||||
{
|
||||
$this->validFrom = new DateTime();
|
||||
$this->geographicalUnits = new ArrayCollection();
|
||||
}
|
||||
|
||||
public static function createFromAddress(Address $original): Address
|
||||
@@ -273,6 +294,14 @@ class Address
|
||||
return $this->floor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, GeographicalUnit>|GeographicalUnit[]
|
||||
*/
|
||||
public function getGeographicalUnits(): Collection
|
||||
{
|
||||
return $this->geographicalUnits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
|
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Entity\GeographicalUnit;
|
||||
|
||||
/**
|
||||
* Simple GeographialUnit Data Transfer Object.
|
||||
*
|
||||
* This allow to get access to id, unitName, unitRefId, and layer's id
|
||||
*/
|
||||
class SimpleGeographicalUnitDTO
|
||||
{
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public int $id;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public int $layerId;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public string $unitName;
|
||||
|
||||
/**
|
||||
* @readonly
|
||||
* @psalm-readonly
|
||||
*/
|
||||
public string $unitRefId;
|
||||
|
||||
public function __construct(int $id, string $unitName, string $unitRefId, int $layerId)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->unitName = $unitName;
|
||||
$this->unitRefId = $unitRefId;
|
||||
$this->layerId = $layerId;
|
||||
}
|
||||
}
|
136
src/Bundle/ChillMainBundle/Entity/SavedExport.php
Normal file
136
src/Bundle/ChillMainBundle/Entity/SavedExport.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="chill_main_saved_export")
|
||||
*/
|
||||
class SavedExport implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
private string $description = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
*/
|
||||
private string $exportAlias;
|
||||
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="uuid", unique="true")
|
||||
* @ORM\GeneratedValue(strategy="NONE")
|
||||
*/
|
||||
private UuidInterface $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json", nullable=false, options={"default": "[]"})
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
* @Assert\NotBlank
|
||||
*/
|
||||
private string $title = '';
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
*/
|
||||
private User $user;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = Uuid::uuid4();
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getExportAlias(): string
|
||||
{
|
||||
return $this->exportAlias;
|
||||
}
|
||||
|
||||
public function getId(): UuidInterface
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description): SavedExport
|
||||
{
|
||||
$this->description = (string) $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExportAlias(string $exportAlias): SavedExport
|
||||
{
|
||||
$this->exportAlias = $exportAlias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOptions(array $options): SavedExport
|
||||
{
|
||||
$this->options = $options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle(?string $title): SavedExport
|
||||
{
|
||||
$this->title = (string) $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(User $user): SavedExport
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@@ -445,6 +445,8 @@ class SpreadSheetFormatter implements FormatterInterface
|
||||
$this->initializeCache($key);
|
||||
}
|
||||
|
||||
$value = null === $value ? '' : $value;
|
||||
|
||||
return call_user_func($this->cacheDisplayableResult[$key], $value);
|
||||
}
|
||||
|
||||
|
@@ -20,11 +20,12 @@ use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
@@ -80,7 +81,7 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
*
|
||||
* @uses appendAggregatorForm
|
||||
*
|
||||
* @param type $exportAlias
|
||||
* @param string $exportAlias
|
||||
*/
|
||||
public function buildForm(
|
||||
FormBuilderInterface $builder,
|
||||
@@ -144,8 +145,6 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
$i = 1;
|
||||
|
||||
foreach ($result as $row) {
|
||||
$line = [];
|
||||
|
||||
if (true === $this->formatterData['numerotation']) {
|
||||
$worksheet->setCellValue('A' . ($i + 1), (string) $i);
|
||||
}
|
||||
@@ -155,13 +154,22 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
foreach ($row as $key => $value) {
|
||||
$row = $a . ($i + 1);
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
$worksheet->setCellValue($row, Date::PHPToExcel($value));
|
||||
$worksheet->getStyle($row)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
|
||||
$formattedValue = $this->getLabel($key, $value);
|
||||
|
||||
if ($formattedValue instanceof DateTimeInterface) {
|
||||
$worksheet->setCellValue($row, Date::PHPToExcel($formattedValue));
|
||||
|
||||
if ($formattedValue->format('His') === '000000') {
|
||||
$worksheet->getStyle($row)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
|
||||
} else {
|
||||
$worksheet->getStyle($row)
|
||||
->getNumberFormat()
|
||||
->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME);
|
||||
}
|
||||
} else {
|
||||
$worksheet->setCellValue($row, $this->getLabel($key, $value));
|
||||
$worksheet->setCellValue($row, $formattedValue);
|
||||
}
|
||||
++$a;
|
||||
}
|
||||
@@ -259,6 +267,10 @@ class SpreadsheetListFormatter implements FormatterInterface
|
||||
foreach ($keys as $key) {
|
||||
// get an array with all values for this key if possible
|
||||
$values = array_map(static function ($v) use ($key) {
|
||||
if (!array_key_exists($key, $v)) {
|
||||
throw new RuntimeException(sprintf('This key does not exists: %s. Available keys are %s', $key, implode(', ', array_keys($v))));
|
||||
}
|
||||
|
||||
return $v[$key];
|
||||
}, $this->result);
|
||||
// store the label in the labelsCache property
|
||||
|
60
src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php
Normal file
60
src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Helper;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class DateTimeHelper
|
||||
{
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
public function getLabel($header): callable
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $this->translator->trans($header);
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// warning: won't work with DateTimeImmutable as we reset time a few lines later
|
||||
$date = DateTime::createFromFormat('Y-m-d', $value);
|
||||
$hasTime = false;
|
||||
|
||||
if (false === $date) {
|
||||
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
|
||||
$hasTime = true;
|
||||
}
|
||||
|
||||
// check that the creation could occurs.
|
||||
if (false === $date) {
|
||||
throw new Exception(sprintf('The value %s could '
|
||||
. 'not be converted to %s', $value, DateTime::class));
|
||||
}
|
||||
|
||||
if (!$hasTime) {
|
||||
$date->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
return $date;
|
||||
};
|
||||
}
|
||||
}
|
426
src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php
Normal file
426
src/Bundle/ChillMainBundle/Export/Helper/ExportAddressHelper.php
Normal file
@@ -0,0 +1,426 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Helper;
|
||||
|
||||
use Chill\MainBundle\Entity\GeographicalUnit;
|
||||
use Chill\MainBundle\Entity\GeographicalUnitLayer;
|
||||
use Chill\MainBundle\Repository\AddressRepository;
|
||||
use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface;
|
||||
use Chill\MainBundle\Templating\Entity\AddressRender;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use LogicException;
|
||||
use function array_key_exists;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Helps to load addresses and format them in list.
|
||||
*/
|
||||
class ExportAddressHelper
|
||||
{
|
||||
/**
|
||||
* Compute all the F_* constants.
|
||||
*/
|
||||
public const F_ALL =
|
||||
self::F_ATTRIBUTES | self::F_BUILDING | self::F_COUNTRY |
|
||||
self::F_GEOM | self::F_POSTAL_CODE | self::F_STREET | self::F_GEOGRAPHICAL_UNITS;
|
||||
|
||||
public const F_AS_STRING = 0b00010000;
|
||||
|
||||
public const F_ATTRIBUTES = 0b01000000;
|
||||
|
||||
public const F_BUILDING = 0b00001000;
|
||||
|
||||
public const F_COUNTRY = 0b00000001;
|
||||
|
||||
public const F_GEOGRAPHICAL_UNITS = 0b1000000000;
|
||||
|
||||
public const F_GEOM = 0b00100000;
|
||||
|
||||
public const F_POSTAL_CODE = 0b00000010;
|
||||
|
||||
public const F_STREET = 0b00000100;
|
||||
|
||||
private const ALL = [
|
||||
'country' => self::F_COUNTRY,
|
||||
'postal_code' => self::F_POSTAL_CODE,
|
||||
'street' => self::F_STREET,
|
||||
'building' => self::F_BUILDING,
|
||||
'string' => self::F_AS_STRING,
|
||||
'geom' => self::F_GEOM,
|
||||
'attributes' => self::F_ATTRIBUTES,
|
||||
'geographical_units' => self::F_GEOGRAPHICAL_UNITS,
|
||||
];
|
||||
|
||||
private const COLUMN_MAPPING = [
|
||||
'country' => ['country'],
|
||||
'postal_code' => ['postcode_code', 'postcode_name'],
|
||||
'street' => ['street', 'streetNumber'],
|
||||
'building' => ['buildingName', 'corridor', 'distribution', 'extra', 'flat', 'floor', 'steps'],
|
||||
'string' => ['_as_string'],
|
||||
'attributes' => ['isNoAddress', 'confidential', 'id'],
|
||||
'geom' => ['_lat', '_lon'],
|
||||
'geographical_units' => ['_unit_names', '_unit_refs'],
|
||||
];
|
||||
|
||||
private AddressRender $addressRender;
|
||||
|
||||
private AddressRepository $addressRepository;
|
||||
|
||||
private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository;
|
||||
|
||||
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||
|
||||
/**
|
||||
* @var array<string, string, GeographicalUnitLayer>>|null
|
||||
*/
|
||||
private ?array $unitNamesKeysCache = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, GeographicalUnitLayer>>|null
|
||||
*/
|
||||
private ?array $unitRefsKeysCache = [];
|
||||
|
||||
public function __construct(
|
||||
AddressRender $addressRender,
|
||||
AddressRepository $addressRepository,
|
||||
GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository,
|
||||
TranslatableStringHelperInterface $translatableStringHelper
|
||||
) {
|
||||
$this->addressRepository = $addressRepository;
|
||||
$this->geographicalUnitLayerRepository = $geographicalUnitLayerRepository;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->addressRender = $addressRender;
|
||||
}
|
||||
|
||||
public function addSelectClauses(int $params, QueryBuilder $queryBuilder, $entityName = 'address', $prefix = 'add')
|
||||
{
|
||||
foreach (self::ALL as $key => $bitmask) {
|
||||
if (($params & $bitmask) === $bitmask) {
|
||||
foreach (self::COLUMN_MAPPING[$key] as $field) {
|
||||
switch ($field) {
|
||||
case 'id':
|
||||
case '_as_string':
|
||||
$queryBuilder->addSelect(sprintf('%s.id AS %s%s', $entityName, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case 'street':
|
||||
case 'streetNumber':
|
||||
case 'floor':
|
||||
case 'corridor':
|
||||
case 'steps':
|
||||
case 'buildingName':
|
||||
case 'flat':
|
||||
case 'distribution':
|
||||
case 'extra':
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $entityName, $field, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case 'country':
|
||||
case 'postcode_name':
|
||||
case 'postcode_code':
|
||||
$postCodeAlias = sprintf('%spostcode_t', $prefix);
|
||||
|
||||
if (!in_array($postCodeAlias, $queryBuilder->getAllAliases(), true)) {
|
||||
$queryBuilder->leftJoin($entityName . '.postcode', $postCodeAlias);
|
||||
}
|
||||
|
||||
if ('postcode_name' === $field) {
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $postCodeAlias, 'name', $prefix, $field));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ('postcode_code' === $field) {
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $postCodeAlias, 'code', $prefix, $field));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$countryAlias = sprintf('%scountry_t', $prefix);
|
||||
|
||||
if (!in_array($countryAlias, $queryBuilder->getAllAliases(), true)) {
|
||||
$queryBuilder->leftJoin(sprintf('%s.country', $postCodeAlias), $countryAlias);
|
||||
}
|
||||
|
||||
$queryBuilder->addSelect(sprintf('%s.%s AS %s%s', $countryAlias, 'name', $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case 'isNoAddress':
|
||||
case 'confidential':
|
||||
$queryBuilder->addSelect(sprintf('CASE WHEN %s.%s = \'TRUE\' THEN 1 ELSE 0 END AS %s%s', $entityName, $field, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case '_lat':
|
||||
$queryBuilder->addSelect(sprintf('ST_Y(%s.point) AS %s%s', $entityName, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case '_lon':
|
||||
$queryBuilder->addSelect(sprintf('ST_X(%s.point) AS %s%s', $entityName, $prefix, $field));
|
||||
|
||||
break;
|
||||
|
||||
case '_unit_names':
|
||||
foreach ($this->generateKeysForUnitsNames($prefix) as $alias => $layer) {
|
||||
$queryBuilder
|
||||
->addSelect(
|
||||
sprintf(
|
||||
'(SELECT AGGREGATE(u_n_%s_%s.unitName) FROM %s u_n_%s_%s WHERE u_n_%s_%s MEMBER OF %s.geographicalUnits AND u_n_%s_%s.layer = :layer_%s_%s) AS %s',
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
GeographicalUnit::class,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$entityName,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$alias
|
||||
)
|
||||
)
|
||||
->setParameter(sprintf('layer_%s_%s', $prefix, $layer->getId()), $layer);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '_unit_refs':
|
||||
foreach ($this->generateKeysForUnitsRefs($prefix) as $alias => $layer) {
|
||||
$queryBuilder
|
||||
->addSelect(
|
||||
sprintf(
|
||||
'(SELECT AGGREGATE(u_r_%s_%s.unitRefId) FROM %s u_r_%s_%s WHERE u_r_%s_%s MEMBER OF %s.geographicalUnits AND u_r_%s_%s.layer = :layer_%s_%s) AS %s',
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
GeographicalUnit::class,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$entityName,
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$prefix,
|
||||
$layer->getId(),
|
||||
$alias
|
||||
)
|
||||
)
|
||||
->setParameter(sprintf('layer_%s_%s', $prefix, $layer->getId()), $layer);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new LogicException(sprintf('This key is not supported: %s, field %s', $key, $field));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self::F_* $params
|
||||
*
|
||||
* @return array|string[]
|
||||
*/
|
||||
public function getKeys(int $params, string $prefix = ''): array
|
||||
{
|
||||
$prefixes = [];
|
||||
|
||||
foreach (self::ALL as $key => $bitmask) {
|
||||
if (($params & $bitmask) === $bitmask) {
|
||||
if ('geographical_units' === $key) {
|
||||
// geographical unit generate keys dynamically, depending on layers
|
||||
$prefixes = array_merge($prefixes, array_keys($this->generateKeysForUnitsNames($prefix)), array_keys($this->generateKeysForUnitsRefs($prefix)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$prefixes = array_merge(
|
||||
$prefixes,
|
||||
array_map(
|
||||
static function ($item) use ($prefix) {
|
||||
return $prefix . $item;
|
||||
},
|
||||
self::COLUMN_MAPPING[$key]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $prefixes;
|
||||
}
|
||||
|
||||
public function getLabel($key, array $values, $data, string $prefix = '', string $translationPrefix = 'export.address_helper.'): callable
|
||||
{
|
||||
$sanitizedKey = substr($key, strlen($prefix));
|
||||
|
||||
switch ($sanitizedKey) {
|
||||
case 'id':
|
||||
case 'street':
|
||||
case 'streetNumber':
|
||||
case 'buildingName':
|
||||
case 'corridor':
|
||||
case 'distribution':
|
||||
case 'extra':
|
||||
case 'flat':
|
||||
case 'floor':
|
||||
case '_lat':
|
||||
case '_lon':
|
||||
case 'steps':
|
||||
case 'postcode_code':
|
||||
case 'postcode_name':
|
||||
return static function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
|
||||
case 'country':
|
||||
return function ($value) use ($key) {
|
||||
if ('_header' === $value) {
|
||||
return 'export.list.acp' . $key;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->translatableStringHelper->localize(json_decode($value, true));
|
||||
};
|
||||
|
||||
case 'isNoAddress':
|
||||
case 'confidential':
|
||||
return static function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
switch ($value) {
|
||||
case null:
|
||||
return '';
|
||||
|
||||
case true:
|
||||
return 1;
|
||||
|
||||
case false:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
throw new LogicException('this value is not supported for ' . $sanitizedKey . ': ' . $value);
|
||||
}
|
||||
};
|
||||
|
||||
case '_as_string':
|
||||
return function ($value) use ($sanitizedKey, $translationPrefix) {
|
||||
if ('_header' === $value) {
|
||||
return $translationPrefix . $sanitizedKey;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$address = $this->addressRepository->find($value);
|
||||
|
||||
return $this->addressRender->renderString($address, []);
|
||||
};
|
||||
|
||||
default:
|
||||
$layerNamesKeys = array_merge($this->generateKeysForUnitsNames($prefix), $this->generateKeysForUnitsRefs($prefix));
|
||||
|
||||
if (array_key_exists($key, $layerNamesKeys)) {
|
||||
return function ($value) use ($key, $layerNamesKeys) {
|
||||
if ('_header' === $value) {
|
||||
$header = $this->translatableStringHelper->localize($layerNamesKeys[$key]->getName());
|
||||
|
||||
if (str_contains($key, 'unit_ref')) {
|
||||
$header .= ' (id)';
|
||||
}
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$decodedValues = json_decode($value, true);
|
||||
|
||||
switch (count($decodedValues)) {
|
||||
case 0:
|
||||
return '';
|
||||
|
||||
case 1:
|
||||
return $decodedValues[0];
|
||||
|
||||
default:
|
||||
return implode('|', $decodedValues);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
throw new LogicException('this key is not supported: ' . $sanitizedKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, GeographicalUnitLayer>
|
||||
*/
|
||||
private function generateKeysForUnitsNames(string $prefix): array
|
||||
{
|
||||
if (array_key_exists($prefix, $this->unitNamesKeysCache)) {
|
||||
return $this->unitNamesKeysCache[$prefix];
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($this->geographicalUnitLayerRepository->findAllHavingUnits() as $layer) {
|
||||
$keys[$prefix . 'unit_names_' . $layer->getId()] = $layer;
|
||||
}
|
||||
|
||||
return $this->unitNamesKeysCache[$prefix] = $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, GeographicalUnitLayer>
|
||||
*/
|
||||
private function generateKeysForUnitsRefs(string $prefix): array
|
||||
{
|
||||
if (array_key_exists($prefix, $this->unitRefsKeysCache)) {
|
||||
return $this->unitRefsKeysCache[$prefix];
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($this->geographicalUnitLayerRepository->findAllHavingUnits() as $layer) {
|
||||
$keys[$prefix . 'unit_refs_' . $layer->getId()] = $layer;
|
||||
}
|
||||
|
||||
return $this->unitRefsKeysCache[$prefix] = $keys;
|
||||
}
|
||||
}
|
43
src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php
Normal file
43
src/Bundle/ChillMainBundle/Export/Helper/UserHelper.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Helper;
|
||||
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||
|
||||
class UserHelper
|
||||
{
|
||||
private UserRender $userRender;
|
||||
|
||||
private UserRepositoryInterface $userRepository;
|
||||
|
||||
public function __construct(UserRender $userRender, UserRepositoryInterface $userRepository)
|
||||
{
|
||||
$this->userRender = $userRender;
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
|
||||
public function getLabel($key, array $values, string $header): callable
|
||||
{
|
||||
return function ($value) use ($header) {
|
||||
if ('_header' === $value) {
|
||||
return $header;
|
||||
}
|
||||
|
||||
if (null === $value || null === $user = $this->userRepository->find($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->userRender->renderString($user, []);
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Form\DataMapper;
|
||||
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Symfony\Component\Form\DataMapperInterface;
|
||||
use Symfony\Component\Form\Exception;
|
||||
|
||||
class RollingDateDataMapper implements DataMapperInterface
|
||||
{
|
||||
public function mapDataToForms($viewData, $forms)
|
||||
{
|
||||
if (null === $viewData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$viewData instanceof RollingDate) {
|
||||
throw new Exception\UnexpectedTypeException($viewData, RollingDate::class);
|
||||
}
|
||||
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$forms['roll']->setData($viewData->getRoll());
|
||||
$forms['fixedDate']->setData($viewData->getFixedDate());
|
||||
}
|
||||
|
||||
public function mapFormsToData($forms, &$viewData): void
|
||||
{
|
||||
$forms = iterator_to_array($forms);
|
||||
|
||||
$viewData = new RollingDate(
|
||||
$forms['roll']->getData(),
|
||||
$forms['fixedDate']->getData()
|
||||
);
|
||||
}
|
||||
}
|
40
src/Bundle/ChillMainBundle/Form/SavedExportType.php
Normal file
40
src/Bundle/ChillMainBundle/Form/SavedExportType.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class SavedExportType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('title', TextType::class, [
|
||||
'required' => true,
|
||||
])
|
||||
->add('description', ChillTextareaType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => SavedExport::class,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -51,7 +51,9 @@ class EntityToJsonTransformer implements DataTransformerInterface
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ($item) { return $this->denormalizeOne($item); },
|
||||
function ($item) {
|
||||
return $this->denormalizeOne($item);
|
||||
},
|
||||
$denormalized
|
||||
);
|
||||
}
|
||||
|
73
src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php
Normal file
73
src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Form\Type;
|
||||
|
||||
use Chill\MainBundle\Form\DataMapper\RollingDateDataMapper;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
class PickRollingDateType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
$builder
|
||||
->add('roll', ChoiceType::class, [
|
||||
'choices' => array_combine(
|
||||
array_map(static fn (string $item) => 'rolling_date.' . $item, RollingDate::ALL_T),
|
||||
RollingDate::ALL_T
|
||||
),
|
||||
'multiple' => false,
|
||||
'expanded' => false,
|
||||
'label' => 'rolling_date.roll_movement',
|
||||
])
|
||||
->add('fixedDate', ChillDateType::class, [
|
||||
'input' => 'datetime_immutable',
|
||||
'label' => 'rolling_date.fixed_date_date',
|
||||
]);
|
||||
|
||||
$builder->setDataMapper(new RollingDateDataMapper());
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => RollingDate::class,
|
||||
'empty_data' => new RollingDate(RollingDate::T_TODAY),
|
||||
'constraints' => [
|
||||
new Callback([$this, 'validate']),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function validate($data, ExecutionContextInterface $context, $payload): void
|
||||
{
|
||||
/** @var RollingDate $data */
|
||||
if (RollingDate::T_FIXED_DATE === $data->getRoll() && null === $data->getFixedDate()) {
|
||||
$context
|
||||
->buildViolation('rolling_date.When fixed date is selected, you must provide a date')
|
||||
->atPath('fixedDate')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options)
|
||||
{
|
||||
$view->vars['uniqid'] = uniqid('rollingdate-');
|
||||
}
|
||||
}
|
@@ -66,7 +66,9 @@ class ScopePickerType extends AbstractType
|
||||
$options['role'] instanceof Role ? $options['role']->getRole() : $options['role'],
|
||||
$options['center']
|
||||
),
|
||||
static function (Scope $s) { return $s->isActive(); }
|
||||
static function (Scope $s) {
|
||||
return $s->isActive();
|
||||
}
|
||||
);
|
||||
|
||||
if (0 === count($items)) {
|
||||
|
@@ -12,19 +12,40 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
|
||||
/**
|
||||
* @method Civility|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method Civility|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method Civility[] findAll()
|
||||
* @method Civility[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class CivilityRepository extends ServiceEntityRepository
|
||||
class CivilityRepository implements CivilityRepositoryInterface
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct($registry, Civility::class);
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?Civility
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?Civility
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Civility::class;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Civility;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface CivilityRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id): ?Civility;
|
||||
|
||||
/**
|
||||
* @return array|Civility[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @return array|Civility[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria): ?Civility;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -41,7 +41,7 @@ class GeographicalUnitRepository implements GeographicalUnitRepositoryInterface
|
||||
{
|
||||
return $this->repository
|
||||
->createQueryBuilder('gu')
|
||||
->select('PARTIAL gu.{id,unitName,unitRefId,layer}')
|
||||
->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class))
|
||||
->addOrderBy('IDENTITY(gu.layer)')
|
||||
->addOrderBy(('gu.unitName'))
|
||||
->getQuery()
|
||||
|
@@ -14,15 +14,14 @@ namespace Chill\MainBundle\Repository;
|
||||
use Chill\MainBundle\Entity\Language;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
final class LanguageRepository implements ObjectRepository
|
||||
final class LanguageRepository implements LanguageRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(Language::class);
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?Language
|
||||
@@ -54,7 +53,7 @@ final class LanguageRepository implements ObjectRepository
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Language::class;
|
||||
}
|
||||
|
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Language;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
interface LanguageRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?Language;
|
||||
|
||||
/**
|
||||
* @return Language[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @param mixed|null $limit
|
||||
* @param mixed|null $offset
|
||||
*
|
||||
* @return Language[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?Language;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
* @implements ObjectRepository<SavedExport>
|
||||
*/
|
||||
class SavedExportRepository implements SavedExportRepositoryInterface
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository($this->getClassName());
|
||||
}
|
||||
|
||||
public function find($id): ?SavedExport
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|SavedExport[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('se');
|
||||
|
||||
$qb
|
||||
->where($qb->expr()->eq('se.user', ':user'))
|
||||
->setParameter('user', $user);
|
||||
|
||||
if (null !== $limit) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
if (null !== $offset) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
|
||||
foreach ($orderBy as $field => $order) {
|
||||
$qb->addOrderBy('se.' . $field, $order);
|
||||
}
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?SavedExport
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return SavedExport::class;
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
* @implements ObjectRepository<SavedExport>
|
||||
*/
|
||||
interface SavedExportRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function find($id): ?SavedExport;
|
||||
|
||||
/**
|
||||
* @return array|SavedExport[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
/**
|
||||
* @return array|SavedExport[]
|
||||
*/
|
||||
public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array;
|
||||
|
||||
public function findOneBy(array $criteria): ?SavedExport;
|
||||
|
||||
public function getClassName(): string;
|
||||
}
|
@@ -518,8 +518,15 @@ div.v-toast {
|
||||
z-index: 10000!important;
|
||||
}
|
||||
|
||||
div.grouped {
|
||||
padding: 1em;
|
||||
border: 1px solid black;
|
||||
margin-bottom: 2em;
|
||||
// export index page
|
||||
div.exports-list {
|
||||
div.flex-bloc .item-bloc {
|
||||
flex-basis: 33%;
|
||||
@include media-breakpoint-down(lg) { flex-basis: 50%; }
|
||||
@include media-breakpoint-down(sm) { flex-basis: 100%; }
|
||||
div:last-child,
|
||||
p:last-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
import {ShowHide} from 'ChillMainAssets/lib/show_hide/index';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function(_e) {
|
||||
console.log('pick-rolling-date');
|
||||
document.querySelectorAll('div[data-rolling-date]').forEach( (picker) => {
|
||||
const
|
||||
roll_wrapper = picker.querySelector('div.roll-wrapper'),
|
||||
fixed_wrapper = picker.querySelector('div.fixed-wrapper');
|
||||
|
||||
new ShowHide({
|
||||
froms: [roll_wrapper],
|
||||
container: [fixed_wrapper],
|
||||
test: function (elems) {
|
||||
console.log('testing');
|
||||
console.log('elems', elems);
|
||||
for (let el of elems) {
|
||||
for (let select_roll of el.querySelectorAll('select[data-roll-picker]')) {
|
||||
console.log('select_roll', select_roll);
|
||||
console.log('value', select_roll.value);
|
||||
return select_roll.value === 'fixed_date';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@@ -0,0 +1,12 @@
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_export_index') }}" class="nav-link {% if current == 'common' %}active{% endif %}">
|
||||
{{ 'Exports list'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ chill_path_forward_return_path('chill_main_export_saved_list_my') }}" class="nav-link {% if current == 'my' %}active{% endif %}">
|
||||
{{ 'saved_export.My saved exports'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
@@ -49,5 +49,14 @@ window.addEventListener("DOMContentLoaded", function(e) {
|
||||
data-download-text="{{ "Download your report"|trans|escape('html_attr') }}"
|
||||
><span id="waiting_text">{{ "Waiting for your report"|trans ~ '...' }}</span></div>
|
||||
|
||||
</div>
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel"><a href="{{ chill_return_path_or('chill_main_export_index') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a></li>
|
||||
|
||||
{% if not app.request.query.has('prevent_save') %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_export_save_from_key', { alias: alias, key: app.request.query.get('key')}) }}" class="btn btn-save">{{ 'Save'|trans }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock content %}
|
@@ -22,23 +22,27 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
<h1>{{ 'Exports list'|trans }}</h1>
|
||||
|
||||
|
||||
{{ include('@ChillMain/Export/_navbar.html.twig', {'current' : 'common'}) }}
|
||||
|
||||
<div class="col-md-10 exports-list">
|
||||
|
||||
<div class="container mt-4">
|
||||
|
||||
{% for group, exports in grouped_exports %}{% if group != '_' %}
|
||||
<h2 class="display-6">{{ group|trans }}</h2>
|
||||
<div class="row grouped">
|
||||
<div class="row flex-bloc">
|
||||
{% for export_alias, export in exports %}
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<h2>{{ export.title|trans }}</h2>
|
||||
<p>{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
<div class="item-bloc">
|
||||
<div class="item-row card-body">
|
||||
<h2 class="card-title">{{ export.title|trans }}</h2>
|
||||
<p class="card-text my-3">{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -48,17 +52,19 @@
|
||||
<h2 class="display-6">{{ 'Ungrouped exports'|trans }}</h2>
|
||||
{% endif %}
|
||||
|
||||
<div class="row ungrouped">
|
||||
<div class="row flex-bloc">
|
||||
{% for export_alias,export in grouped_exports['_'] %}
|
||||
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<h2>{{ export.title|trans }}</h2>
|
||||
<p>{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row card-body">
|
||||
<h2 class="card-title">{{ export.title|trans }}</h2>
|
||||
<p class="card-text my-3">{{ export.description|trans }}</p>
|
||||
<p>
|
||||
<a class="btn btn-action" href="{{ path('chill_main_export_new', { 'alias': export_alias } ) }}">
|
||||
{{ 'Create an export'|trans }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
|
@@ -22,6 +22,7 @@
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||
{{ encore_entry_link_tags('mod_pick_rolling_date') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
@@ -30,6 +31,7 @@
|
||||
{% if export_alias == 'count_social_work_actions' %}
|
||||
{{ encore_entry_script_tags('vue_export_action_goal_result') }}
|
||||
{% endif %}
|
||||
{{ encore_entry_script_tags('mod_pick_rolling_date') }}
|
||||
{% endblock js %}
|
||||
|
||||
{% block content %}
|
||||
|
@@ -257,3 +257,16 @@
|
||||
<input type="hidden" {{ block('widget_attributes') }} {% if value is not empty %}value="{{ value }}" {% endif %} data-input-uniqid="{{ form.vars['uniqid'] }}"/>
|
||||
<div data-module="pick-postal-code" data-uniqid="{{ form.vars['uniqid'] }}"></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block pick_rolling_date_widget %}
|
||||
<div data-rolling-date="{{ form.vars['uniqid'] }}" class="row">
|
||||
<div class="roll-wrapper col-sm-6">
|
||||
{{ form_widget(form.roll, { 'attr': { 'data-roll-picker': 'data-roll-picker'}}) }}
|
||||
{{ form_errors(form.roll) }}
|
||||
</div>
|
||||
<div class="fixed-wrapper col-sm-6">
|
||||
{{ form_widget(form.fixedDate) }}
|
||||
{{ form_errors(form.fixedDate) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,25 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block title 'saved_export.Delete saved ?'|trans %}
|
||||
|
||||
{% block display_content %}
|
||||
<div class="col-10">
|
||||
<h3>{{ saved_export.title }}</h3>
|
||||
<p>{{ saved_export.description|chill_markdown_to_html }}</p>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container chill-md-10">
|
||||
{{ include('@ChillMain/Util/confirmation_template.html.twig',
|
||||
{
|
||||
'title' : 'saved_export.Delete saved ?'|trans,
|
||||
'confirm_question' : 'saved_export.Are you sure you want to delete this saved ?'|trans,
|
||||
'display_content' : block('display_content'),
|
||||
'cancel_route' : 'chill_main_export_saved_list_my',
|
||||
'cancel_parameters' : {},
|
||||
'form' : delete_form
|
||||
} ) }}
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,23 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ 'saved_export.Edit'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10">
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.description) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('chill_main_homepage') }}" class="btn btn-cancel">{{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" class="btn btn-save">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,78 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ 'saved_export.My saved exports'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10 exports-list">
|
||||
|
||||
{{ include('@ChillMain/Export/_navbar.html.twig', {'current' : 'my'}) }}
|
||||
|
||||
<div class="container mt-4">
|
||||
|
||||
{% if total == 0 %}
|
||||
<p class="chill-no-data-statement" >{{ 'saved_export.Any saved export'|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% for group, saveds in grouped_exports %}
|
||||
{% if group != '_' %}
|
||||
<h2 class="display-6">{{ group }}</h2>
|
||||
<div class="row flex-bloc">
|
||||
{% for s in saveds %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row card-body">
|
||||
<p class="card-subtitle"><strong>{{ s.export.title|trans }}</strong></p>
|
||||
<h2 class="card-title">{{ s.saved.title }}</h2>
|
||||
|
||||
<div class="createdBy">{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}</div>
|
||||
|
||||
<div class="card-text my-3">
|
||||
{{ s.saved.description|chill_markdown_to_html }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li><a href="{{ chill_path_add_return_path('chill_main_export_saved_delete', {'id': s.saved.id }) }}" class="btn btn-delete"></a></li>
|
||||
<li><a href="{{ chill_path_add_return_path('chill_main_export_saved_edit', {'id': s.saved.id }) }}" class="btn btn-edit"></a></li>
|
||||
<li><a href="{{ path('chill_main_export_generate_from_saved', { id: s.saved.id }) }}" class="btn btn-action"><i class="fa fa-cog"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if grouped_exports|keys|length > 1 and grouped_exports['_']|default([])|length > 0 %}
|
||||
<h2 class="display-6">{{ 'Ungrouped exports'|trans }}</h2>
|
||||
{% endif %}
|
||||
|
||||
<div class="row flex-bloc">
|
||||
{% for saveds in grouped_exports['_']|default([]) %}
|
||||
{% for s in saveds %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row card-body">
|
||||
<p class="card-subtitle"><strong>{{ s.export.title|trans }}</strong></p>
|
||||
<h2 class="card-title">{{ s.saved.title }}</h2>
|
||||
|
||||
<div class="createdBy">{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}</div>
|
||||
|
||||
<div class="card-text my-3">
|
||||
{{ s.saved.description|chill_markdown_to_html }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li><a href="{{ chill_path_add_return_path('chill_main_export_saved_delete', {'id': s.saved.id }) }}" class="btn btn-delete"></a></li>
|
||||
<li><a href="{{ chill_path_add_return_path('chill_main_export_saved_edit', {'id': s.saved.id }) }}" class="btn btn-edit"></a></li>
|
||||
<li><a href="{{ path('chill_main_export_generate_from_saved', { id: s.saved.id }) }}" class="btn btn-action"><i class="fa fa-cog"></i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,23 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ 'saved_export.New'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10">
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{{ form_start(form) }}
|
||||
{{ form_row(form.title) }}
|
||||
{{ form_row(form.description) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ chill_return_path_or('chill_main_homepage') }}" class="btn btn-cancel">{{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" class="btn btn-save">{{ 'Save'|trans }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Security\Authorization;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use UnexpectedValueException;
|
||||
use function in_array;
|
||||
|
||||
class SavedExportVoter extends Voter
|
||||
{
|
||||
public const DELETE = 'CHLL_MAIN_EXPORT_SAVED_DELETE';
|
||||
|
||||
public const EDIT = 'CHLL_MAIN_EXPORT_SAVED_EDIT';
|
||||
|
||||
public const GENERATE = 'CHLL_MAIN_EXPORT_SAVED_GENERATE';
|
||||
|
||||
private const ALL = [
|
||||
self::DELETE,
|
||||
self::EDIT,
|
||||
self::GENERATE,
|
||||
];
|
||||
|
||||
protected function supports($attribute, $subject): bool
|
||||
{
|
||||
return $subject instanceof SavedExport && in_array($attribute, self::ALL, true);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
/** @var SavedExport $subject */
|
||||
switch ($attribute) {
|
||||
case self::DELETE:
|
||||
case self::EDIT:
|
||||
case self::GENERATE:
|
||||
return $subject->getUser() === $token->getUser();
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException('attribute not supported: ' . $attribute);
|
||||
}
|
||||
}
|
||||
}
|
101
src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php
Normal file
101
src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\RollingDate;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
class RollingDate
|
||||
{
|
||||
public const ALL_T = [
|
||||
self::T_YEAR_PREVIOUS_START,
|
||||
self::T_QUARTER_PREVIOUS_START,
|
||||
self::T_MONTH_PREVIOUS_START,
|
||||
self::T_WEEK_PREVIOUS_START,
|
||||
self::T_YEAR_CURRENT_START,
|
||||
self::T_QUARTER_CURRENT_START,
|
||||
self::T_MONTH_CURRENT_START,
|
||||
self::T_WEEK_CURRENT_START,
|
||||
self::T_TODAY,
|
||||
self::T_WEEK_NEXT_START,
|
||||
self::T_MONTH_NEXT_START,
|
||||
self::T_QUARTER_NEXT_START,
|
||||
self::T_YEAR_NEXT_START,
|
||||
self::T_FIXED_DATE,
|
||||
];
|
||||
|
||||
/**
|
||||
* A given fixed date.
|
||||
*/
|
||||
public const T_FIXED_DATE = 'fixed_date';
|
||||
|
||||
public const T_MONTH_CURRENT_START = 'month_current_start';
|
||||
|
||||
public const T_MONTH_NEXT_START = 'month_next_start';
|
||||
|
||||
public const T_MONTH_PREVIOUS_START = 'month_previous_start';
|
||||
|
||||
public const T_QUARTER_CURRENT_START = 'quarter_current_start';
|
||||
|
||||
public const T_QUARTER_NEXT_START = 'quarter_next_start';
|
||||
|
||||
public const T_QUARTER_PREVIOUS_START = 'quarter_previous_start';
|
||||
|
||||
public const T_TODAY = 'today';
|
||||
|
||||
public const T_WEEK_CURRENT_START = 'week_current_start';
|
||||
|
||||
public const T_WEEK_NEXT_START = 'week_next_start';
|
||||
|
||||
public const T_WEEK_PREVIOUS_START = 'week_previous_start';
|
||||
|
||||
public const T_YEAR_CURRENT_START = 'year_current_start';
|
||||
|
||||
public const T_YEAR_NEXT_START = 'year_next_start';
|
||||
|
||||
public const T_YEAR_PREVIOUS_START = 'year_previous_start';
|
||||
|
||||
private ?DateTimeImmutable $fixedDate;
|
||||
|
||||
/**
|
||||
* The pivot date is the date from the rolling is computed. By default, it is "now".
|
||||
*/
|
||||
private DateTimeImmutable $pivotDate;
|
||||
|
||||
private string $roll;
|
||||
|
||||
/**
|
||||
* @param string|self::T_* $roll
|
||||
* @param DateTimeImmutable|null $pivotDate Will be "now" if null is given
|
||||
* @param DateTimeImmutable|null $fixedDate Only to insert if $roll equals @see{self::T_FIXED_DATE}
|
||||
*/
|
||||
public function __construct(string $roll, ?DateTimeImmutable $fixedDate = null, ?DateTimeImmutable $pivotDate = null)
|
||||
{
|
||||
$this->roll = $roll;
|
||||
$this->pivotDate = $pivotDate ?? new DateTimeImmutable('now');
|
||||
$this->fixedDate = $fixedDate;
|
||||
}
|
||||
|
||||
public function getFixedDate(): ?DateTimeImmutable
|
||||
{
|
||||
return $this->fixedDate;
|
||||
}
|
||||
|
||||
public function getPivotDate(): DateTimeImmutable
|
||||
{
|
||||
return $this->pivotDate;
|
||||
}
|
||||
|
||||
public function getRoll(): string
|
||||
{
|
||||
return $this->roll;
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\RollingDate;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use LogicException;
|
||||
use UnexpectedValueException;
|
||||
|
||||
class RollingDateConverter implements RollingDateConverterInterface
|
||||
{
|
||||
public function convert(RollingDate $rollingDate): DateTimeImmutable
|
||||
{
|
||||
switch ($rollingDate->getRoll()) {
|
||||
case RollingDate::T_MONTH_CURRENT_START:
|
||||
return $this->toBeginOfMonth($rollingDate->getPivotDate());
|
||||
|
||||
case RollingDate::T_MONTH_NEXT_START:
|
||||
return $this->toBeginOfMonth($rollingDate->getPivotDate()->add(new DateInterval('P1M')));
|
||||
|
||||
case RollingDate::T_MONTH_PREVIOUS_START:
|
||||
return $this->toBeginOfMonth($rollingDate->getPivotDate()->sub(new DateInterval('P1M')));
|
||||
|
||||
case RollingDate::T_QUARTER_CURRENT_START:
|
||||
return $this->toBeginOfQuarter($rollingDate->getPivotDate());
|
||||
|
||||
case RollingDate::T_QUARTER_NEXT_START:
|
||||
return $this->toBeginOfQuarter($rollingDate->getPivotDate()->add(new DateInterval('P3M')));
|
||||
|
||||
case RollingDate::T_QUARTER_PREVIOUS_START:
|
||||
return $this->toBeginOfQuarter($rollingDate->getPivotDate()->sub(new DateInterval('P3M')));
|
||||
|
||||
case RollingDate::T_WEEK_CURRENT_START:
|
||||
return $this->toBeginOfWeek($rollingDate->getPivotDate());
|
||||
|
||||
case RollingDate::T_WEEK_NEXT_START:
|
||||
return $this->toBeginOfWeek($rollingDate->getPivotDate()->add(new DateInterval('P1W')));
|
||||
|
||||
case RollingDate::T_WEEK_PREVIOUS_START:
|
||||
return $this->toBeginOfWeek($rollingDate->getPivotDate()->sub(new DateInterval('P1W')));
|
||||
|
||||
case RollingDate::T_YEAR_CURRENT_START:
|
||||
return $this->toBeginOfYear($rollingDate->getPivotDate());
|
||||
|
||||
case RollingDate::T_YEAR_PREVIOUS_START:
|
||||
return $this->toBeginOfYear($rollingDate->getPivotDate()->sub(new DateInterval('P1Y')));
|
||||
|
||||
case RollingDate::T_YEAR_NEXT_START:
|
||||
return $this->toBeginOfYear($rollingDate->getPivotDate()->add(new DateInterval('P1Y')));
|
||||
|
||||
case RollingDate::T_TODAY:
|
||||
return $rollingDate->getPivotDate();
|
||||
|
||||
case RollingDate::T_FIXED_DATE:
|
||||
if (null === $rollingDate->getFixedDate()) {
|
||||
throw new LogicException('You must provide a fixed date when selecting a fixed date');
|
||||
}
|
||||
|
||||
return $rollingDate->getFixedDate();
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException(sprintf('%s rolling operation not supported', $rollingDate->getRoll()));
|
||||
}
|
||||
}
|
||||
|
||||
private function toBeginOfMonth(DateTimeImmutable $date): DateTimeImmutable
|
||||
{
|
||||
return DateTimeImmutable::createFromFormat(
|
||||
'Y-m-d His',
|
||||
sprintf('%s-%s-01 000000', $date->format('Y'), $date->format('m'))
|
||||
);
|
||||
}
|
||||
|
||||
private function toBeginOfQuarter(DateTimeImmutable $date): DateTimeImmutable
|
||||
{
|
||||
switch ((int) $date->format('n')) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
$month = '01';
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
$month = '04';
|
||||
|
||||
break;
|
||||
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
$month = '07';
|
||||
|
||||
break;
|
||||
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
$month = '10';
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new LogicException('this month is not valid: ' . $date->format('n'));
|
||||
}
|
||||
|
||||
return DateTimeImmutable::createFromFormat(
|
||||
'Y-m-d His',
|
||||
sprintf('%s-%s-01 000000', $date->format('Y'), $month)
|
||||
);
|
||||
}
|
||||
|
||||
private function toBeginOfWeek(DateTimeImmutable $date): DateTimeImmutable
|
||||
{
|
||||
if (1 === $dayOfWeek = (int) $date->format('N')) {
|
||||
return $date->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
return $date
|
||||
->sub(new DateInterval('P' . ($dayOfWeek - 1) . 'D'))
|
||||
->setTime(0, 0, 0);
|
||||
}
|
||||
|
||||
private function toBeginOfYear(DateTimeImmutable $date): DateTimeImmutable
|
||||
{
|
||||
return DateTimeImmutable::createFromFormat(
|
||||
'Y-m-d His',
|
||||
sprintf('%s-01-01 000000', $date->format('Y'))
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Service\RollingDate;
|
||||
|
||||
use DateTimeImmutable;
|
||||
|
||||
interface RollingDateConverterInterface
|
||||
{
|
||||
public function convert(RollingDate $rollingDate): DateTimeImmutable;
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Tests\Doctrine\DQL;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
final class GreatestTest extends KernelTestCase
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
public function testGreatestInDQL()
|
||||
{
|
||||
$dql = 'SELECT GREATEST(a.validFrom, a.validTo, :now) AS g FROM ' . Address::class . ' a WHERE a.validTo < :now and a.validFrom < :now';
|
||||
|
||||
$actual = $this->entityManager
|
||||
->createQuery($dql)
|
||||
->setParameter('now', $now = new DateTimeImmutable('now'), Types::DATE_IMMUTABLE)
|
||||
->setMaxResults(3)
|
||||
->getResult();
|
||||
|
||||
$this->assertIsArray($actual);
|
||||
$this->assertEquals($now->format('Y-m-d'), $actual[0]['g']);
|
||||
}
|
||||
}
|
48
src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/LeastTest.php
Normal file
48
src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/LeastTest.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Tests\Doctrine\DQL;
|
||||
|
||||
use Chill\MainBundle\Entity\Address;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
final class LeastTest extends KernelTestCase
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
|
||||
$this->entityManager = self::$container->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
public function testGreatestInDQL()
|
||||
{
|
||||
$dql = 'SELECT LEAST(a.validFrom, a.validTo, :now) AS g FROM ' . Address::class . ' a WHERE a.validTo < :now and a.validFrom < :now';
|
||||
|
||||
$actual = $this->entityManager
|
||||
->createQuery($dql)
|
||||
->setParameter('now', $now = new DateTimeImmutable('now'), Types::DATE_IMMUTABLE)
|
||||
->setMaxResults(3)
|
||||
->getResult();
|
||||
|
||||
$this->assertIsArray($actual);
|
||||
$this->assertNotEquals($now->format('Y-m-d'), $actual[0]['g']);
|
||||
}
|
||||
}
|
@@ -134,7 +134,9 @@ final class NotificationTest extends KernelTestCase
|
||||
$this->assertEquals($senderId, $notification->getSender()->getId());
|
||||
$this->assertCount(count($addressesIds), $notification->getUnreadBy());
|
||||
|
||||
$unreadIds = $notification->getUnreadBy()->map(static function (User $u) { return $u->getId(); });
|
||||
$unreadIds = $notification->getUnreadBy()->map(static function (User $u) {
|
||||
return $u->getId();
|
||||
});
|
||||
|
||||
foreach ($addressesIds as $addresseeId) {
|
||||
$this->assertContains($addresseeId, $unreadIds);
|
||||
|
@@ -680,10 +680,12 @@ final class ExportManagerTest extends KernelTestCase
|
||||
|
||||
return new ExportManager(
|
||||
$logger ?? self::$container->get('logger'),
|
||||
$em ?? self::$container->get('doctrine.orm.entity_manager'),
|
||||
$authorizationChecker ?? self::$container->get('security.authorization_checker'),
|
||||
$authorizationHelper ?? self::$container->get('chill.main.security.authorization.helper'),
|
||||
$tokenStorage
|
||||
$tokenStorage,
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,53 @@
|
||||
<?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 Form\Type;
|
||||
|
||||
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Symfony\Component\Form\PreloadedExtension;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
final class PickRollingDateTypeTest extends TypeTestCase
|
||||
{
|
||||
public function testSubmitValidData()
|
||||
{
|
||||
$formData = [
|
||||
'roll' => 'year_previous_start',
|
||||
'fixedDate' => null,
|
||||
];
|
||||
|
||||
$form = $this->factory->create(PickRollingDateType::class);
|
||||
|
||||
$form->submit($formData);
|
||||
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
/** @var RollingDate $rollingDate */
|
||||
$rollingDate = $form->getData();
|
||||
|
||||
$this->assertInstanceOf(RollingDate::class, $rollingDate);
|
||||
$this->assertEquals(RollingDate::T_YEAR_PREVIOUS_START, $rollingDate->getRoll());
|
||||
}
|
||||
|
||||
protected function getExtensions(): array
|
||||
{
|
||||
$type = new PickRollingDateType();
|
||||
|
||||
return [
|
||||
new PreloadedExtension([$type], []),
|
||||
];
|
||||
}
|
||||
}
|
@@ -96,7 +96,9 @@ final class ScopePickerTypeTest extends TypeTestCase
|
||||
|
||||
$translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class);
|
||||
$translatableStringHelper->localize(Argument::type('array'))->will(
|
||||
static function ($args) { return $args[0]['fr']; }
|
||||
static function ($args) {
|
||||
return $args[0]['fr'];
|
||||
}
|
||||
);
|
||||
|
||||
$type = new ScopePickerType(
|
||||
|
@@ -211,7 +211,9 @@ final class AuthorizationHelperTest extends KernelTestCase
|
||||
$centerA
|
||||
);
|
||||
|
||||
$usernames = array_map(static function (User $u) { return $u->getUsername(); }, $users);
|
||||
$usernames = array_map(static function (User $u) {
|
||||
return $u->getUsername();
|
||||
}, $users);
|
||||
|
||||
$this->assertContains('center a_social', $usernames);
|
||||
}
|
||||
|
@@ -0,0 +1,101 @@
|
||||
<?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 Services\RollingDate;
|
||||
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverter;
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
final class RollingDateConverterTest extends TestCase
|
||||
{
|
||||
private RollingDateConverter $converter;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->converter = new RollingDateConverter();
|
||||
}
|
||||
|
||||
public function generateDataConversionDate(): iterable
|
||||
{
|
||||
$format = 'Y-m-d His';
|
||||
|
||||
yield [RollingDate::T_MONTH_CURRENT_START, '2022-11-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_MONTH_NEXT_START, '2022-12-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_MONTH_PREVIOUS_START, '2022-10-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_QUARTER_CURRENT_START, '2022-10-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_QUARTER_NEXT_START, '2023-01-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_QUARTER_PREVIOUS_START, '2022-07-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_TODAY, '2022-11-07 000000', $format];
|
||||
|
||||
yield [RollingDate::T_WEEK_CURRENT_START, '2022-11-07 000000', $format];
|
||||
|
||||
yield [RollingDate::T_WEEK_NEXT_START, '2022-11-14 000000', $format];
|
||||
|
||||
yield [RollingDate::T_WEEK_PREVIOUS_START, '2022-10-31 000000', $format];
|
||||
|
||||
yield [RollingDate::T_YEAR_CURRENT_START, '2022-01-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_YEAR_NEXT_START, '2023-01-01 000000', $format];
|
||||
|
||||
yield [RollingDate::T_YEAR_PREVIOUS_START, '2021-01-01 000000', $format];
|
||||
}
|
||||
|
||||
public function testConversionFixedDate()
|
||||
{
|
||||
$rollingDate = new RollingDate(RollingDate::T_FIXED_DATE, new DateTimeImmutable('2022-01-01'));
|
||||
|
||||
$this->assertEquals(
|
||||
'2022-01-01',
|
||||
$this->converter->convert($rollingDate)->format('Y-m-d')
|
||||
);
|
||||
}
|
||||
|
||||
public function testConvertOnDateNow()
|
||||
{
|
||||
$rollingDate = new RollingDate(RollingDate::T_YEAR_PREVIOUS_START);
|
||||
|
||||
$actual = $this->converter->convert($rollingDate);
|
||||
|
||||
$this->assertEquals(
|
||||
(int) (new DateTimeImmutable('now'))->format('Y') - 1,
|
||||
(int) $actual->format('Y')
|
||||
);
|
||||
$this->assertEquals(1, (int) $actual->format('m'));
|
||||
$this->assertEquals(1, (int) $actual->format('d'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider generateDataConversionDate
|
||||
*/
|
||||
public function testConvertOnPivotDate(string $roll, string $expectedDateTime, string $format)
|
||||
{
|
||||
$pivot = DateTimeImmutable::createFromFormat('Y-m-d His', '2022-11-07 000000');
|
||||
$rollingDate = new RollingDate($roll, null, $pivot);
|
||||
|
||||
$this->assertEquals(
|
||||
DateTime::createFromFormat($format, $expectedDateTime),
|
||||
$this->converter->convert($rollingDate)
|
||||
);
|
||||
}
|
||||
}
|
@@ -107,7 +107,9 @@ class NotificationOnTransition implements EventSubscriberInterface
|
||||
'dest' => $subscriber,
|
||||
'place' => $place,
|
||||
'workflow' => $workflow,
|
||||
'is_dest' => in_array($subscriber->getId(), array_map(static function (User $u) { return $u->getId(); }, $entityWorkflow->futureDestUsers), true),
|
||||
'is_dest' => in_array($subscriber->getId(), array_map(static function (User $u) {
|
||||
return $u->getId();
|
||||
}, $entityWorkflow->futureDestUsers), true),
|
||||
];
|
||||
|
||||
$notification = new Notification();
|
||||
|
@@ -71,6 +71,7 @@ module.exports = function(encore, entries)
|
||||
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
||||
encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js');
|
||||
encore.addEntry('mod_pick_postal_code', __dirname + '/Resources/public/module/pick-postal-code/index.js');
|
||||
encore.addEntry('mod_pick_rolling_date', __dirname + '/Resources/public/module/pick-rolling-date/index.js');
|
||||
|
||||
// Vue entrypoints
|
||||
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
|
||||
|
@@ -106,3 +106,8 @@ services:
|
||||
resource: '../Service/Import/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Service\RollingDate\:
|
||||
resource: '../Service/RollingDate/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@@ -3,6 +3,9 @@ services:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Export\Helper\:
|
||||
resource: '../../Export/Helper'
|
||||
|
||||
chill.main.export_element_validator:
|
||||
class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator
|
||||
tags:
|
||||
|
@@ -50,6 +50,8 @@ services:
|
||||
tags:
|
||||
- { name: security.voter }
|
||||
|
||||
Chill\MainBundle\Security\Authorization\SavedExportVoter: ~
|
||||
|
||||
Chill\MainBundle\Security\PasswordRecover\TokenManager:
|
||||
arguments:
|
||||
$secret: '%kernel.secret%'
|
||||
|
@@ -0,0 +1,43 @@
|
||||
<?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\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20221107212201 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE chill_main_saved_export');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create table for saved exports';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE chill_main_saved_export (id UUID NOT NULL, user_id INT DEFAULT NULL, description TEXT DEFAULT \'\' NOT NULL, exportAlias TEXT DEFAULT \'\' NOT NULL, options JSONB DEFAULT \'[]\' NOT NULL, title TEXT DEFAULT \'\' NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('CREATE INDEX IDX_C2029B22A76ED395 ON chill_main_saved_export (user_id)');
|
||||
$this->addSql('CREATE INDEX IDX_C2029B223174800F ON chill_main_saved_export (createdBy_id)');
|
||||
$this->addSql('CREATE INDEX IDX_C2029B2265FF1AEC ON chill_main_saved_export (updatedBy_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_saved_export.id IS \'(DC2Type:uuid)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_saved_export.options IS \'(DC2Type:json)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_saved_export.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_saved_export.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||
$this->addSql('ALTER TABLE chill_main_saved_export ADD CONSTRAINT FK_C2029B22A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_saved_export ADD CONSTRAINT FK_C2029B223174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_saved_export ADD CONSTRAINT FK_C2029B2265FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20221114105345 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP MATERIALIZED VIEW view_chill_main_address_geographical_unit');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Create materialized view to store GeographicalUnitAddress';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE MATERIALIZED VIEW view_chill_main_address_geographical_unit (address_id, geographical_unit_id) AS
|
||||
SELECT
|
||||
address.id AS address_id,
|
||||
geographical_unit.id AS geographical_unit_id
|
||||
FROM chill_main_address AS address
|
||||
JOIN chill_main_geographical_unit AS geographical_unit ON ST_CONTAINS(geographical_unit.geom, address.point)
|
||||
');
|
||||
|
||||
$this->addSql('CREATE INDEX IDX_BD42692CF5B7AF75 ON view_chill_main_address_geographical_unit (address_id)');
|
||||
$this->addSql('CREATE INDEX IDX_BD42692CDAA4DAB8 ON view_chill_main_address_geographical_unit (geographical_unit_id)');
|
||||
}
|
||||
}
|
@@ -39,6 +39,8 @@ Last updated by: Dernière mise à jour par
|
||||
on: "le "
|
||||
Last updated on: Dernière mise à jour le
|
||||
by_user: "par "
|
||||
lifecycleUpdate: Evenements de création et mise à jour
|
||||
address_fields: Données liées à l'adresse
|
||||
|
||||
Edit: Modifier
|
||||
Update: Mettre à jour
|
||||
@@ -57,6 +59,7 @@ Until: Jusqu'au
|
||||
#elements used in software
|
||||
centers: centres
|
||||
Centers: Centres
|
||||
center: centre
|
||||
comment: commentaire
|
||||
Comment: Commentaire
|
||||
Pinned comment: Commentaire épinglé
|
||||
@@ -79,17 +82,17 @@ Postal code: Code postal
|
||||
Valid from: Valide à partir du
|
||||
Choose a postal code: Choisir un code postal
|
||||
address:
|
||||
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
|
||||
real address: Adresse d'un domicile
|
||||
consider homeless: Cette adresse est incomplète
|
||||
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
|
||||
real address: Adresse d'un domicile
|
||||
consider homeless: Cette adresse est incomplète
|
||||
address more:
|
||||
floor: ét
|
||||
corridor: coul
|
||||
steps: esc
|
||||
flat: appart
|
||||
buildingName: résidence
|
||||
extra: ""
|
||||
distribution: cedex
|
||||
floor: ét
|
||||
corridor: coul
|
||||
steps: esc
|
||||
flat: appart
|
||||
buildingName: résidence
|
||||
extra: ""
|
||||
distribution: cedex
|
||||
Create a new address: Créer une nouvelle adresse
|
||||
Create an address: Créer une adresse
|
||||
Update address: Modifier l'adresse
|
||||
@@ -125,7 +128,7 @@ Location and location type: Localisations et types de localisation
|
||||
Back to the admin: Menu d'administration
|
||||
"Administration interface": Interface d'administration
|
||||
Welcome to the admin section !: >
|
||||
Bienvenue dans l'interface d'administration !
|
||||
Bienvenue dans l'interface d'administration !
|
||||
|
||||
#permissions
|
||||
Permissions Menu: Gestion des droits
|
||||
@@ -236,6 +239,7 @@ Default for: Type de localisation par défaut pour
|
||||
none: aucun
|
||||
person: usager
|
||||
thirdparty: tiers
|
||||
civility: civilité
|
||||
|
||||
#admin section for civility
|
||||
abbreviation: abbréviation
|
||||
@@ -334,69 +338,69 @@ Impersonate: Incarner l'utilisateur
|
||||
Impersonate mode: Mode fantôme
|
||||
|
||||
crud:
|
||||
# general items
|
||||
new:
|
||||
button_action_form: Créer
|
||||
link_edit: Modifier
|
||||
save_and_close: Créer & fermer
|
||||
save_and_show: Créer & voir
|
||||
save_and_new: Créer & nouveau
|
||||
success: Les données ont été créées
|
||||
edit:
|
||||
button_action_form: Enregistrer
|
||||
back_to_view: Voir
|
||||
save_and_close: Enregistrer & fermer
|
||||
save_and_show: Enregistrer & voir
|
||||
success: Les données ont été modifiées
|
||||
delete:
|
||||
success: Les données ont été supprimées
|
||||
link_to_form: Supprimer
|
||||
default:
|
||||
success: Les données ont été enregistrées
|
||||
view:
|
||||
link_duplicate: Dupliquer
|
||||
admin_user:
|
||||
index:
|
||||
title: Utilisateurs
|
||||
add_new: Créer
|
||||
title_edit: Modifier un utilisateur
|
||||
title_new: Créer un utilisateur
|
||||
admin_user_job:
|
||||
index:
|
||||
title: Métiers
|
||||
add_new: Créer
|
||||
title_new: Nouveau métier
|
||||
title_edit: Modifier un métier
|
||||
main_location_type:
|
||||
index:
|
||||
title: Liste des types de localisations
|
||||
add_new: Ajouter un type de localisation
|
||||
title_new: Nouveau type de localisation
|
||||
title_edit: Modifier un type de localisation
|
||||
main_location:
|
||||
index:
|
||||
title: Liste des localisations
|
||||
add_new: Ajouter une localisation
|
||||
title_new: Nouvelle localisation
|
||||
title_edit: Modifier une localisation
|
||||
main_language:
|
||||
index:
|
||||
title: Liste des langues
|
||||
add_new: Ajouter une langue
|
||||
title_new: Nouvelle langue
|
||||
title_edit: Modifier une langue
|
||||
main_country:
|
||||
index:
|
||||
title: Liste des pays
|
||||
add_new: Ajouter un pays
|
||||
title_new: Nouveau pays
|
||||
title_edit: Modifier un pays
|
||||
main_civility:
|
||||
index:
|
||||
title: Liste des civilités
|
||||
add_new: Ajouter une civilité
|
||||
title_new: Nouvelle civilité
|
||||
title_edit: Modifier une civilité
|
||||
# general items
|
||||
new:
|
||||
button_action_form: Créer
|
||||
link_edit: Modifier
|
||||
save_and_close: Créer & fermer
|
||||
save_and_show: Créer & voir
|
||||
save_and_new: Créer & nouveau
|
||||
success: Les données ont été créées
|
||||
edit:
|
||||
button_action_form: Enregistrer
|
||||
back_to_view: Voir
|
||||
save_and_close: Enregistrer & fermer
|
||||
save_and_show: Enregistrer & voir
|
||||
success: Les données ont été modifiées
|
||||
delete:
|
||||
success: Les données ont été supprimées
|
||||
link_to_form: Supprimer
|
||||
default:
|
||||
success: Les données ont été enregistrées
|
||||
view:
|
||||
link_duplicate: Dupliquer
|
||||
admin_user:
|
||||
index:
|
||||
title: Utilisateurs
|
||||
add_new: Créer
|
||||
title_edit: Modifier un utilisateur
|
||||
title_new: Créer un utilisateur
|
||||
admin_user_job:
|
||||
index:
|
||||
title: Métiers
|
||||
add_new: Créer
|
||||
title_new: Nouveau métier
|
||||
title_edit: Modifier un métier
|
||||
main_location_type:
|
||||
index:
|
||||
title: Liste des types de localisations
|
||||
add_new: Ajouter un type de localisation
|
||||
title_new: Nouveau type de localisation
|
||||
title_edit: Modifier un type de localisation
|
||||
main_location:
|
||||
index:
|
||||
title: Liste des localisations
|
||||
add_new: Ajouter une localisation
|
||||
title_new: Nouvelle localisation
|
||||
title_edit: Modifier une localisation
|
||||
main_language:
|
||||
index:
|
||||
title: Liste des langues
|
||||
add_new: Ajouter une langue
|
||||
title_new: Nouvelle langue
|
||||
title_edit: Modifier une langue
|
||||
main_country:
|
||||
index:
|
||||
title: Liste des pays
|
||||
add_new: Ajouter un pays
|
||||
title_new: Nouveau pays
|
||||
title_edit: Modifier un pays
|
||||
main_civility:
|
||||
index:
|
||||
title: Liste des civilités
|
||||
add_new: Ajouter une civilité
|
||||
title_new: Nouvelle civilité
|
||||
title_edit: Modifier une civilité
|
||||
|
||||
No entities: Aucun élément
|
||||
|
||||
@@ -515,3 +519,51 @@ notification:
|
||||
Remove an email: Supprimer l'adresse email
|
||||
Email with access link: Adresse email ayant reçu un lien d'accès
|
||||
|
||||
export:
|
||||
address_helper:
|
||||
id: Identifiant de l'adresse
|
||||
street: Voie
|
||||
streetNumber: Numéro de voie
|
||||
buildingName: Résidence
|
||||
corridor: Couloir
|
||||
distribution: Distribution
|
||||
extra: Extra
|
||||
flat: Appartement
|
||||
floor: Étage
|
||||
postcode_code: Code postal
|
||||
postcode_name: Libellé du code postal
|
||||
country: Pays
|
||||
_as_string: Adresse formattée
|
||||
confidential: Adresse confidentielle ?
|
||||
isNoAddress: Adresse incomplète ?
|
||||
_lat: Latitude
|
||||
_lon: Longitude
|
||||
|
||||
rolling_date:
|
||||
year_previous_start: Début de l'année précédente
|
||||
quarter_previous_start: Début du trimestre précédent
|
||||
month_previous_start: Début du mois précédent
|
||||
week_previous_start: Début de la semaine précédente
|
||||
year_current_start: Début de l'année courante
|
||||
quarter_current_start: Début du trimestre courant
|
||||
month_current_start: Début du mois courant
|
||||
week_current_start: Début de la semaine courante
|
||||
today: Aujourd'hui (aucune modification de la date courante)
|
||||
year_next_start: Début de l'année suivante
|
||||
quarter_next_start: Début du trimestre suivante
|
||||
month_next_start: Début du mois suivant
|
||||
week_next_start: Début de la semaine suivante
|
||||
fixed_date: Date fixe
|
||||
roll_movement: Modification par rapport à aujourd'hui
|
||||
fixed_date_date: Date fixe
|
||||
|
||||
saved_export:
|
||||
Any saved export: Aucun export enregistré
|
||||
New: Nouvel export enregistré
|
||||
Edit: Modifier un export enregistré
|
||||
Delete saved ?: Supprimer un export enregistré ?
|
||||
Are you sure you want to delete this saved ?: Êtes-vous sûr·e de vouloir supprimer cet export ?
|
||||
My saved exports: Mes exports enregistrés
|
||||
Export is deleted: L'export est supprimé
|
||||
Saved export is saved!: L'export est enregistré
|
||||
Created on %date%: Créé le %date%
|
@@ -31,3 +31,6 @@ notification:
|
||||
|
||||
workflow:
|
||||
You must add at least one dest user or email: Indiquez au moins un destinataire ou une adresse email
|
||||
|
||||
rolling_date:
|
||||
When fixed date is selected, you must provide a date: Indiquez la date fixe choisie
|
Reference in New Issue
Block a user