mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge branch '21-save-exports' into '111_exports_suite'
Allow to save exports See merge request Chill-Projet/chill-bundles!465
This commit is contained in:
commit
9dba5965f6
@ -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),
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
133
src/Bundle/ChillMainBundle/Entity/SavedExport.php
Normal file
133
src/Bundle/ChillMainBundle/Entity/SavedExport.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @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": ""})
|
||||
*/
|
||||
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": ""})
|
||||
*/
|
||||
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 = $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 = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(User $user): SavedExport
|
||||
{
|
||||
$this->user = $user;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
66
src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php
Normal file
66
src/Bundle/ChillMainBundle/Form/Type/PickRollingDateType.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?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\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('roll')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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,9 +22,11 @@
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
{{ include('@ChillMain/Export/_navbar.html.twig', {'current' : 'common'}) }}
|
||||
|
||||
<div class="col-md-10">
|
||||
<h1>{{ 'Exports list'|trans }}</h1>
|
||||
|
||||
|
||||
<div class="container mt-4">
|
||||
|
||||
{% for group, exports in grouped_exports %}{% if group != '_' %}
|
||||
@ -32,13 +34,17 @@
|
||||
<div class="row grouped">
|
||||
{% 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="card">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ export.title|trans }}</h2>
|
||||
<p class="card-text">{{ 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>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -52,13 +58,17 @@
|
||||
{% 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="card">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ export.title|trans }}</h2>
|
||||
<p class="card-text">{{ 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>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
|
@ -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,21 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ 'saved_export.Edit'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<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) }}
|
||||
{% endblock %}
|
@ -0,0 +1,82 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ 'saved_export.My saved exports'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-10">
|
||||
|
||||
{{ 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 grouped">
|
||||
{% for s in saveds %}
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ s.saved.title }}</h2>
|
||||
<p class="card-subtitle"><strong>{{ s.export.title|trans }}</strong></p>
|
||||
|
||||
<div class="card-text">
|
||||
{{ s.saved.description|chill_markdown_to_html }}
|
||||
</div>
|
||||
|
||||
<div class="createdBy">{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}</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['_']|length > 0 %}
|
||||
<h2 class="display-6">{{ 'Ungrouped exports'|trans }}</h2>
|
||||
{% endif %}
|
||||
|
||||
<div class="row ungrouped">
|
||||
{% for saveds in grouped_exports['_']|default([]) %}
|
||||
{% for s in saveds %}
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<div class="col-6 col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{{ s.saved.title }}</h2>
|
||||
<p class="card-subtitle"><strong>{{ s.export.title|trans }}</strong></p>
|
||||
|
||||
<div class="card-text">
|
||||
{{ s.saved.description|chill_markdown_to_html }}
|
||||
</div>
|
||||
|
||||
<div class="createdBy">{{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}</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>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,21 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title %}{{ 'saved_export.New'|trans }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<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) }}
|
||||
{% 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,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], []),
|
||||
];
|
||||
}
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
@ -105,3 +105,8 @@ services:
|
||||
resource: '../Service/Import/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Service\RollingDate\:
|
||||
resource: '../Service/RollingDate/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
@ -538,3 +538,32 @@ export:
|
||||
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 rapport enregistré
|
||||
New: Nouveau rapport enregistré
|
||||
Edit: Modifier un rapport enregistré
|
||||
Delete saved ?: Supprimer un rapport enregistré ?
|
||||
Are you sure you want to delete this saved ?: Êtes-vous sûr·e de vouloir supprimer ce rapport ?
|
||||
My saved exports: Mes rapports enregistrés
|
||||
Export is deleted: Le rapport est supprimé
|
||||
Saved export is saved!: Le rapport est enregistré
|
||||
Created on %date%: Créé le %date%
|
@ -12,10 +12,11 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
|
||||
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Export\Declarations;
|
||||
use DateTime;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
@ -27,11 +28,15 @@ final class StepAggregator implements AggregatorInterface
|
||||
|
||||
private const P = 'acp_step_agg_date';
|
||||
|
||||
private RollingDateConverterInterface $rollingDateConverter;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
public function __construct(
|
||||
RollingDateConverterInterface $rollingDateConverter,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->rollingDateConverter = $rollingDateConverter;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
@ -60,7 +65,7 @@ final class StepAggregator implements AggregatorInterface
|
||||
)
|
||||
)
|
||||
)
|
||||
->setParameter(self::P, $data['on_date'])
|
||||
->setParameter(self::P, $this->rollingDateConverter->convert($data['on_date']))
|
||||
->addGroupBy('step_aggregator');
|
||||
}
|
||||
|
||||
@ -71,8 +76,8 @@ final class StepAggregator implements AggregatorInterface
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('on_date', ChillDateType::class, [
|
||||
'data' => new DateTime(),
|
||||
$builder->add('on_date', PickRollingDateType::class, [
|
||||
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -12,15 +12,23 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
|
||||
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDate;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||
use Chill\PersonBundle\Export\Declarations;
|
||||
use DateTime;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class OpenBetweenDatesFilter implements FilterInterface
|
||||
{
|
||||
private RollingDateConverterInterface $rollingDateConverter;
|
||||
|
||||
public function __construct(RollingDateConverterInterface $rollingDateConverter)
|
||||
{
|
||||
$this->rollingDateConverter = $rollingDateConverter;
|
||||
}
|
||||
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
@ -34,8 +42,8 @@ class OpenBetweenDatesFilter implements FilterInterface
|
||||
);
|
||||
|
||||
$qb->andWhere($clause);
|
||||
$qb->setParameter('datefrom', $data['date_from'], Types::DATE_MUTABLE);
|
||||
$qb->setParameter('dateto', $data['date_to'], Types::DATE_MUTABLE);
|
||||
$qb->setParameter('datefrom', $this->rollingDateConverter->convert($data['date_from']), Types::DATE_IMMUTABLE);
|
||||
$qb->setParameter('dateto', $this->rollingDateConverter->convert($data['date_to']), Types::DATE_IMMUTABLE);
|
||||
}
|
||||
|
||||
public function applyOn(): string
|
||||
@ -46,19 +54,19 @@ class OpenBetweenDatesFilter implements FilterInterface
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder
|
||||
->add('date_from', ChillDateType::class, [
|
||||
'data' => new DateTime(),
|
||||
->add('date_from', PickRollingDateType::class, [
|
||||
'data' => new RollingDate(RollingDate::T_MONTH_PREVIOUS_START),
|
||||
])
|
||||
->add('date_to', ChillDateType::class, [
|
||||
'data' => new DateTime(),
|
||||
->add('date_to', PickRollingDateType::class, [
|
||||
'data' => new RollingDate(RollingDate::T_TODAY),
|
||||
]);
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string'): array
|
||||
{
|
||||
return ['Filtered by opening dates: between %datefrom% and %dateto%', [
|
||||
'%datefrom%' => $data['date_from']->format('d-m-Y'),
|
||||
'%dateto%' => $data['date_to']->format('d-m-Y'),
|
||||
'%datefrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'),
|
||||
'%dateto%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'),
|
||||
]];
|
||||
}
|
||||
|
||||
|
@ -12,31 +12,40 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
|
||||
|
||||
use Chill\MainBundle\Export\FilterInterface;
|
||||
use Chill\MainBundle\Form\Type\PickRollingDateType;
|
||||
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Export\Declarations;
|
||||
use Doctrine\ORM\Query\Expr\Andx;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use function in_array;
|
||||
|
||||
class StepFilter implements FilterInterface
|
||||
{
|
||||
private const A = 'acp_filter_bystep_stephistories';
|
||||
|
||||
private const DEFAULT_CHOICE = AccompanyingPeriod::STEP_CONFIRMED;
|
||||
|
||||
private const P = 'acp_step_filter_date';
|
||||
|
||||
private const STEPS = [
|
||||
'Draft' => AccompanyingPeriod::STEP_DRAFT,
|
||||
'Confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
|
||||
'Closed' => AccompanyingPeriod::STEP_CLOSED,
|
||||
];
|
||||
|
||||
private RollingDateConverterInterface $rollingDateConverter;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
private $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
public function __construct(RollingDateConverterInterface $rollingDateConverter, TranslatorInterface $translator)
|
||||
{
|
||||
$this->rollingDateConverter = $rollingDateConverter;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
|
||||
@ -47,17 +56,25 @@ class StepFilter implements FilterInterface
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data)
|
||||
{
|
||||
$where = $qb->getDQLPart('where');
|
||||
$clause = $qb->expr()->eq('acp.step', ':step');
|
||||
|
||||
if ($where instanceof Andx) {
|
||||
$where->add($clause);
|
||||
} else {
|
||||
$where = $qb->expr()->andX($clause);
|
||||
if (!in_array(self::A, $qb->getAllAliases(), true)) {
|
||||
$qb->leftJoin('acp.stepHistories', self::A);
|
||||
}
|
||||
|
||||
$qb->add('where', $where);
|
||||
$qb->setParameter('step', $data['accepted_steps']);
|
||||
$qb
|
||||
->andWhere(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->lte(self::A . '.startDate', ':' . self::P),
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->isNull(self::A . '.endDate'),
|
||||
$qb->expr()->lt(self::A . '.endDate', ':' . self::P)
|
||||
)
|
||||
)
|
||||
)
|
||||
->andWhere(
|
||||
$qb->expr()->in(self::A . '.step', ':acp_filter_by_step_steps')
|
||||
)
|
||||
->setParameter(self::P, $this->rollingDateConverter->convert($data['calc_date']))
|
||||
->setParameter('acp_filter_by_step_steps', $data['accepted_steps']);
|
||||
}
|
||||
|
||||
public function applyOn()
|
||||
@ -67,13 +84,17 @@ class StepFilter implements FilterInterface
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder)
|
||||
{
|
||||
$builder->add('accepted_steps', ChoiceType::class, [
|
||||
'choices' => self::STEPS,
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'empty_data' => self::DEFAULT_CHOICE,
|
||||
'data' => self::DEFAULT_CHOICE,
|
||||
]);
|
||||
$builder
|
||||
->add('accepted_steps', ChoiceType::class, [
|
||||
'choices' => self::STEPS,
|
||||
'multiple' => false,
|
||||
'expanded' => true,
|
||||
'empty_data' => self::DEFAULT_CHOICE,
|
||||
'data' => self::DEFAULT_CHOICE,
|
||||
])
|
||||
->add('calc_date', PickRollingDateType::class, [
|
||||
'label' => 'export.acp.filter.by_step.date_calc',
|
||||
]);
|
||||
}
|
||||
|
||||
public function describeAction($data, $format = 'string')
|
||||
|
Loading…
x
Reference in New Issue
Block a user