mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-11-01 17:58:24 +00:00
Compare commits
5 Commits
ticket-app
...
create-adm
| Author | SHA1 | Date | |
|---|---|---|---|
| 19785795ad | |||
| e8450a519d | |||
| c66f0122bf | |||
| 37291b2756 | |||
| a88575463b |
6
.changes/unreleased/Feature-20251007-155945.yaml
Normal file
6
.changes/unreleased/Feature-20251007-155945.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Admin interface for Motive entity
|
||||
time: 2025-10-07T15:59:45.597029709+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: No schema change
|
||||
6
.changes/unreleased/Feature-20251022-111552.yaml
Normal file
6
.changes/unreleased/Feature-20251022-111552.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: Feature
|
||||
body: Add an admin interface for Motive entity
|
||||
time: 2025-10-22T11:15:52.13937955+02:00
|
||||
custom:
|
||||
Issue: ""
|
||||
SchemaChange: Add columns or tables
|
||||
@@ -66,7 +66,6 @@ framework:
|
||||
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||
'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async
|
||||
'Chill\MainBundle\Notification\Email\NotificationEmailMessages\ScheduleDailyNotificationDigestMessage': async
|
||||
'Chill\TicketBundle\Messenger\PostTicketUpdateMessage': async
|
||||
# end of routes added by chill-bundles recipes
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
||||
@@ -14,20 +14,14 @@ namespace Chill\TicketBundle\Action\Ticket\Handler;
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\EmergencyStatusUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Handler for changing the emergency status of a ticket.
|
||||
*/
|
||||
class ChangeEmergencyStateCommandHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClockInterface $clock,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
) {}
|
||||
public function __construct(private readonly ClockInterface $clock) {}
|
||||
|
||||
public function __invoke(Ticket $ticket, ChangeEmergencyStateCommand $command): Ticket
|
||||
{
|
||||
@@ -36,8 +30,6 @@ class ChangeEmergencyStateCommandHandler
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
$previous = $ticket->getEmergencyStatus();
|
||||
|
||||
// End the current emergency status history (if any)
|
||||
foreach ($ticket->getEmergencyStatusHistories() as $emergencyStatusHistory) {
|
||||
if (null === $emergencyStatusHistory->getEndDate()) {
|
||||
@@ -52,12 +44,6 @@ class ChangeEmergencyStateCommandHandler
|
||||
$this->clock->now(),
|
||||
);
|
||||
|
||||
// Dispatch event about the toggle
|
||||
if (null !== $previous) {
|
||||
$event = new EmergencyStatusUpdateEvent($ticket, $previous, $command->newEmergencyStatus);
|
||||
$this->eventDispatcher->dispatch($event, TicketUpdateEvent::class);
|
||||
}
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,8 @@ use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\MotiveUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class ReplaceMotiveCommandHandler
|
||||
{
|
||||
@@ -27,7 +24,6 @@ class ReplaceMotiveCommandHandler
|
||||
private readonly ClockInterface $clock,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly ChangeEmergencyStateCommandHandler $changeEmergencyStateCommandHandler,
|
||||
private readonly EventDispatcherInterface $eventDispatcher,
|
||||
) {}
|
||||
|
||||
public function handle(Ticket $ticket, ReplaceMotiveCommand $command): void
|
||||
@@ -36,8 +32,6 @@ class ReplaceMotiveCommandHandler
|
||||
throw new \InvalidArgumentException('The new motive cannot be null');
|
||||
}
|
||||
|
||||
$event = new MotiveUpdateEvent($ticket);
|
||||
|
||||
// will add if there are no existing motive
|
||||
$readyToAdd = 0 === count($ticket->getMotiveHistories());
|
||||
|
||||
@@ -51,8 +45,6 @@ class ReplaceMotiveCommandHandler
|
||||
continue;
|
||||
}
|
||||
|
||||
// collect previous active motives before closing
|
||||
$event->previousMotive = $history->getMotive();
|
||||
$history->setEndDate($this->clock->now());
|
||||
$readyToAdd = true;
|
||||
}
|
||||
@@ -60,7 +52,6 @@ class ReplaceMotiveCommandHandler
|
||||
if ($readyToAdd) {
|
||||
$history = new MotiveHistory($command->motive, $ticket, $this->clock->now());
|
||||
$this->entityManager->persist($history);
|
||||
$event->newMotive = $command->motive;
|
||||
|
||||
// Check if the motive has makeTicketEmergency set and update the ticket's emergency status if needed
|
||||
if ($command->motive->isMakeTicketEmergency()) {
|
||||
@@ -68,9 +59,5 @@ class ReplaceMotiveCommandHandler
|
||||
($this->changeEmergencyStateCommandHandler)($ticket, $changeEmergencyCommand);
|
||||
}
|
||||
}
|
||||
|
||||
if ($event->hasChanges()) {
|
||||
$this->eventDispatcher->dispatch($event, TicketUpdateEvent::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,9 @@ use Chill\MainBundle\Entity\User;
|
||||
use Chill\TicketBundle\Action\Ticket\SetPersonsCommand;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\PersonsUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
final readonly class SetPersonsCommandHandler
|
||||
{
|
||||
@@ -28,13 +25,10 @@ final readonly class SetPersonsCommandHandler
|
||||
private ClockInterface $clock,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Security $security,
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
) {}
|
||||
|
||||
public function handle(Ticket $ticket, SetPersonsCommand $command): void
|
||||
{
|
||||
$event = new PersonsUpdateEvent($ticket);
|
||||
|
||||
// remove existing addresses which are not in the new addresses
|
||||
foreach ($ticket->getPersonHistories() as $personHistory) {
|
||||
if (null !== $personHistory->getEndDate()) {
|
||||
@@ -46,7 +40,6 @@ final readonly class SetPersonsCommandHandler
|
||||
if (($user = $this->security->getUser()) instanceof User) {
|
||||
$personHistory->setRemovedBy($user);
|
||||
}
|
||||
$event->personsRemoved[] = $personHistory->getPerson();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +51,6 @@ final readonly class SetPersonsCommandHandler
|
||||
|
||||
$history = new PersonHistory($person, $ticket, $this->clock->now());
|
||||
$this->entityManager->persist($history);
|
||||
$event->personsAdded[] = $person;
|
||||
}
|
||||
|
||||
if ($event->hasChanges()) {
|
||||
$this->eventDispatcher->dispatch($event, TicketUpdateEvent::class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?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\TicketBundle\Controller\Admin;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\MotiveDTO;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class MotiveController extends CRUDController
|
||||
{
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
|
||||
{
|
||||
/* @var QueryBuilder $query */
|
||||
$query->addOrderBy('e.id', 'ASC');
|
||||
|
||||
return parent::orderQuery($action, $query, $request, $paginator);
|
||||
}
|
||||
|
||||
protected function createFormFor(string $action, mixed $entity, ?string $formClass = null, array $formOptions = []): FormInterface
|
||||
{
|
||||
if (in_array($action, ['new', 'edit'], true) && $entity instanceof Motive) {
|
||||
$dto = MotiveDTO::fromMotive($entity);
|
||||
|
||||
return parent::createFormFor($action, $dto, $formClass, $formOptions);
|
||||
}
|
||||
|
||||
return parent::createFormFor($action, $entity, $formClass, $formOptions);
|
||||
}
|
||||
|
||||
protected function onFormValid(string $action, object $entity, FormInterface $form, Request $request): void
|
||||
{
|
||||
if (in_array($action, ['new', 'edit'], true) && $entity instanceof Motive) {
|
||||
$dto = $form->getData();
|
||||
if ($dto instanceof MotiveDTO) {
|
||||
$dto->applyToMotive($entity);
|
||||
}
|
||||
}
|
||||
|
||||
parent::onFormValid($action, $entity, $form, $request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
* Class AdminController
|
||||
* Controller for the ticket configuration section (in admin section).
|
||||
*/
|
||||
class AdminController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* Ticket admin.
|
||||
*/
|
||||
#[Route(path: '/{_locale}/admin/ticket', name: 'chill_ticket_admin_index')]
|
||||
public function indexAdminAction()
|
||||
{
|
||||
return $this->render('@ChillTicket/Admin/index.html.twig');
|
||||
}
|
||||
}
|
||||
@@ -11,22 +11,38 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
|
||||
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Repository\MotiveRepository;
|
||||
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
|
||||
final readonly class TicketListController
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private TicketRepositoryInterface $ticketRepository,
|
||||
private Environment $twig,
|
||||
private FilterOrderHelperFactory $filterOrderHelperFactory,
|
||||
// private MotiveRepository $motiveRepository,
|
||||
// private TranslatableStringHelperInterface $translatableStringHelper,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws RuntimeError
|
||||
* @throws SyntaxError
|
||||
* @throws LoaderError
|
||||
*/
|
||||
#[Route('/{_locale}/ticket/ticket/list', name: 'chill_ticket_ticket_list')]
|
||||
public function __invoke(Request $request): Response
|
||||
{
|
||||
@@ -34,22 +50,35 @@ final readonly class TicketListController
|
||||
throw new AccessDeniedHttpException('only user can access this page');
|
||||
}
|
||||
|
||||
return new Response(
|
||||
$this->twig->render('@ChillTicket/Ticket/list.html.twig')
|
||||
);
|
||||
}
|
||||
$filter = $this->buildFilter();
|
||||
|
||||
#[Route('/{_locale}/ticket/by-person/{id}/list', name: 'chill_person_ticket_list')]
|
||||
public function listByPerson(Request $request, Person $person): Response
|
||||
{
|
||||
if (!$this->security->isGranted(PersonVoter::SEE, $person)) {
|
||||
throw new AccessDeniedHttpException('you are not allowed to see this person');
|
||||
}
|
||||
$tickets = $this->ticketRepository->findAllOrdered();
|
||||
|
||||
return new Response(
|
||||
$this->twig->render('@ChillTicket/Person/list.html.twig', [
|
||||
'person' => $person,
|
||||
$this->twig->render('@ChillTicket/Ticket/list.html.twig', [
|
||||
'tickets' => $tickets,
|
||||
'filter' => $filter,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
private function buildFilter(): FilterOrderHelper
|
||||
{
|
||||
// $motives = $this->motiveRepository->findAll();
|
||||
|
||||
return $this->filterOrderHelperFactory
|
||||
->create(self::class)
|
||||
->addSingleCheckbox('to_me', 'chill_ticket.list.filter.to_me')
|
||||
->addSingleCheckbox('in_alert', 'chill_ticket.list.filter.in_alert')
|
||||
->addDateRange('created_between', 'chill_ticket.list.filter.created_between')
|
||||
/*
|
||||
->addEntityChoice('by_motive', 'chill_ticket.list.filter.by_motive', Motive::class, $motives, [
|
||||
'choice_label' => fn (Motive $motive) => $this->translatableStringHelper->localize($motive->getLabel()),
|
||||
'expanded' => true,
|
||||
'multiple' => true,
|
||||
'attr' => ['class' => 'select2'],
|
||||
])
|
||||
*/
|
||||
->build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ final class LoadMotives extends Fixture implements FixtureGroupInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$labels = explode(' > ', (string) $data[0]);
|
||||
$labels = explode(' > ', $data[0]);
|
||||
$parent = null;
|
||||
|
||||
while (count($labels) > 0) {
|
||||
|
||||
@@ -11,8 +11,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\DependencyInjection;
|
||||
|
||||
use Chill\TicketBundle\Controller\Admin\MotiveController;
|
||||
use Chill\TicketBundle\Controller\MotiveApiController;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Form\MotiveType;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||
@@ -36,6 +38,7 @@ class ChillTicketExtension extends Extension implements PrependExtensionInterfac
|
||||
public function prepend(ContainerBuilder $container)
|
||||
{
|
||||
$this->prependApi($container);
|
||||
$this->prependCruds($container);
|
||||
}
|
||||
|
||||
private function prependApi(ContainerBuilder $container): void
|
||||
@@ -66,4 +69,37 @@ class ChillTicketExtension extends Extension implements PrependExtensionInterfac
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function prependCruds(ContainerBuilder $container): void
|
||||
{
|
||||
$container->prependExtensionConfig('chill_main', [
|
||||
'cruds' => [
|
||||
[
|
||||
'class' => Motive::class,
|
||||
'name' => 'motive',
|
||||
'base_path' => '/admin/ticket/motive',
|
||||
'form_class' => MotiveType::class,
|
||||
'controller' => MotiveController::class,
|
||||
'actions' => [
|
||||
'index' => [
|
||||
'template' => '@ChillTicket/Admin/Motive/index.html.twig',
|
||||
'role' => 'ROLE_ADMIN',
|
||||
],
|
||||
'new' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillTicket/Admin/Motive/new.html.twig',
|
||||
],
|
||||
'view' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillTicket/Admin/Motive/view.html.twig',
|
||||
],
|
||||
'edit' => [
|
||||
'role' => 'ROLE_ADMIN',
|
||||
'template' => '@ChillTicket/Admin/Motive/edit.html.twig',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,13 +51,15 @@ class Motive
|
||||
#[ORM\ManyToOne(targetEntity: Motive::class, inversedBy: 'children')]
|
||||
private ?Motive $parent = null;
|
||||
|
||||
|
||||
/**
|
||||
* @var Collection<int, Motive>&Selectable<int, Motive>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Motive::class, mappedBy: 'parent')]
|
||||
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: Motive::class)]
|
||||
private Collection&Selectable $children;
|
||||
|
||||
#[ORM\Column(name: 'ordering', type: \Doctrine\DBAL\Types\Types::FLOAT, nullable: true, options: ['default' => '0.0'])]
|
||||
private float $ordering = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->storedObjects = new ArrayCollection();
|
||||
@@ -218,4 +220,14 @@ class Motive
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function getOrdering(): float
|
||||
{
|
||||
return $this->ordering;
|
||||
}
|
||||
|
||||
public function setOrdering(float $ordering): void
|
||||
{
|
||||
$this->ordering = $ordering;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?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\TicketBundle\Event;
|
||||
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
final class EmergencyStatusUpdateEvent extends TicketUpdateEvent
|
||||
{
|
||||
public function __construct(
|
||||
Ticket $ticket,
|
||||
public EmergencyStatusEnum $previousEmergencyStatus,
|
||||
public EmergencyStatusEnum $newEmergencyStatus,
|
||||
) {
|
||||
parent::__construct(TicketUpdateKindEnum::TOGGLE_EMERGENCY, $ticket);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
<?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\TicketBundle\Event\EventSubscriber;
|
||||
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Chill\TicketBundle\Messenger\PostTicketUpdateMessage;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpKernel\Event\TerminateEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/**
|
||||
* Subscribe to TicketUpdateEvents and dispatch a message for each one when the kernel terminates.
|
||||
*/
|
||||
final class GeneratePostUpdateTicketEventSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @var list<PostTicketUpdateMessage>
|
||||
*/
|
||||
private array $toDispatch = [];
|
||||
|
||||
public function __construct(private readonly MessageBusInterface $messageBus) {}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
TicketUpdateEvent::class => ['onTicketUpdate', 0],
|
||||
KernelEvents::TERMINATE => ['onKernelTerminate', 8096],
|
||||
];
|
||||
}
|
||||
|
||||
public function onTicketUpdate(TicketUpdateEvent $event): void
|
||||
{
|
||||
$this->toDispatch[] = new PostTicketUpdateMessage($event->ticket, $event->updateKind);
|
||||
}
|
||||
|
||||
public function onKernelTerminate(TerminateEvent $event): void
|
||||
{
|
||||
foreach ($this->toDispatch as $message) {
|
||||
$this->messageBus->dispatch($message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?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\TicketBundle\Event;
|
||||
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
final class MotiveUpdateEvent extends TicketUpdateEvent
|
||||
{
|
||||
public function __construct(
|
||||
Ticket $ticket,
|
||||
public ?Motive $previousMotive = null,
|
||||
public ?Motive $newMotive = null,
|
||||
) {
|
||||
parent::__construct(TicketUpdateKindEnum::UPDATE_MOTIVE, $ticket);
|
||||
}
|
||||
|
||||
public function hasChanges(): bool
|
||||
{
|
||||
return null !== $this->newMotive || null !== $this->previousMotive;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?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\TicketBundle\Event;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
class PersonsUpdateEvent extends TicketUpdateEvent
|
||||
{
|
||||
public function __construct(Ticket $ticket)
|
||||
{
|
||||
parent::__construct(TicketUpdateKindEnum::UPDATE_PERSONS, $ticket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var list<Person>
|
||||
*/
|
||||
public $personsAdded = [];
|
||||
|
||||
/**
|
||||
* @var list<Person>
|
||||
*/
|
||||
public $personsRemoved = [];
|
||||
|
||||
public function hasChanges(): bool
|
||||
{
|
||||
return count($this->personsAdded) > 0 || count($this->personsRemoved) > 0;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?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\TicketBundle\Event;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
/**
|
||||
* Event triggered asynchronously after a ticket has been updated.
|
||||
*
|
||||
* This event is trigged by PostTicketUpdateMessageHandler, using Messenger component.
|
||||
*
|
||||
* To use a synchronous event, see @see{TicketUpdateEvent}
|
||||
*/
|
||||
class PostTicketUpdateEvent
|
||||
{
|
||||
public function __construct(
|
||||
public readonly TicketUpdateKindEnum $updateKind,
|
||||
public readonly Ticket $ticket,
|
||||
) {}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?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\TicketBundle\Event;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
|
||||
abstract class TicketUpdateEvent
|
||||
{
|
||||
public function __construct(
|
||||
public readonly TicketUpdateKindEnum $updateKind,
|
||||
public readonly Ticket $ticket,
|
||||
) {}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?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\TicketBundle\Event;
|
||||
|
||||
enum TicketUpdateKindEnum: string
|
||||
{
|
||||
case UPDATE_ADDRESSEE = 'UPDATE_ADDRESSEE';
|
||||
case ADD_COMMENT = 'ADD_COMMENT';
|
||||
case TOGGLE_EMERGENCY = 'TOGGLE_EMERGENCY';
|
||||
case TOGGLE_STATE = 'TOGGLE_STATE';
|
||||
case UPDATE_MOTIVE = 'UPDATE_MOTIVE';
|
||||
case UPDATE_CALLER = 'UPDATE_CALLER';
|
||||
case UPDATE_PERSONS = 'UPDATE_PERSONS';
|
||||
}
|
||||
65
src/Bundle/ChillTicketBundle/src/Form/MotiveType.php
Normal file
65
src/Bundle/ChillTicketBundle/src/Form/MotiveType.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\MotiveDTO;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EnumType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class MotiveType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('label', TranslatableStringFormType::class, [
|
||||
'label' => 'Label',
|
||||
'required' => true,
|
||||
])
|
||||
->add('active', CheckboxType::class, [
|
||||
'label' => 'Active',
|
||||
'required' => false,
|
||||
])
|
||||
->add('makeTicketEmergency', EnumType::class, [
|
||||
'class' => EmergencyStatusEnum::class,
|
||||
'label' => 'emergency?',
|
||||
'required' => false,
|
||||
'placeholder' => 'Choose an option...',
|
||||
])
|
||||
->add('supplementaryComments', ChillCollectionType::class, [
|
||||
'entry_type' => TextareaType::class,
|
||||
'allow_add' => true,
|
||||
'allow_delete' => true,
|
||||
'by_reference' => true,
|
||||
'label' => 'Supplementary comments',
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => MotiveDTO::class,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getBlockPrefix(): string
|
||||
{
|
||||
return 'chill_ticketbundle_motive';
|
||||
}
|
||||
}
|
||||
45
src/Bundle/ChillTicketBundle/src/Menu/AdminMenuBuilder.php
Normal file
45
src/Bundle/ChillTicketBundle/src/Menu/AdminMenuBuilder.php
Normal file
@@ -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\TicketBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
class AdminMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
public function __construct(protected AuthorizationCheckerInterface $authorizationChecker) {}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
|
||||
{
|
||||
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$menu->addChild('Tickets', [
|
||||
'route' => 'chill_ticket_admin_index',
|
||||
])
|
||||
->setAttribute('class', 'list-group-item-header')
|
||||
->setExtras([
|
||||
'order' => 7500,
|
||||
]);
|
||||
|
||||
$menu->addChild('admin.ticket.motive.menu', [
|
||||
'route' => 'chill_crud_motive_index',
|
||||
])->setExtras(['order' => 7510]);
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return ['admin_section', 'admin_ticket'];
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
<?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\TicketBundle\Menu;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* Add menu entrie to person menu.
|
||||
*
|
||||
* Menu entries added :
|
||||
*
|
||||
* - person details ;
|
||||
* - accompanying period (if `visible`)
|
||||
*
|
||||
* @implements LocalMenuBuilderInterface<array{person: Person}>
|
||||
*/
|
||||
class PersonMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AuthorizationCheckerInterface $authorizationChecker,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly TicketRepositoryInterface $ticketRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array{person: Person} $parameters
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters)
|
||||
{
|
||||
/** @var Person $person */
|
||||
$person = $parameters['person'];
|
||||
|
||||
if ($this->authorizationChecker->isGranted(PersonVoter::SEE, $person)) {
|
||||
$menu->addChild($this->translator->trans('chill_ticket.list.title_menu'), [
|
||||
'route' => 'chill_person_ticket_list',
|
||||
'routeParameters' => [
|
||||
'id' => $person->getId(),
|
||||
],
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 150,
|
||||
'counter' => 0 < ($nbTickets = $this->ticketRepository->countOpenedByPerson($person))
|
||||
? $nbTickets : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMenuIds(): array
|
||||
{
|
||||
return ['person'];
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<?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\TicketBundle\Messenger\Handler;
|
||||
|
||||
use Chill\TicketBundle\Event\PostTicketUpdateEvent;
|
||||
use Chill\TicketBundle\Messenger\PostTicketUpdateMessage;
|
||||
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
final readonly class PostTicketUpdateMessageHandler
|
||||
{
|
||||
public function __construct(
|
||||
private EventDispatcherInterface $eventDispatcher,
|
||||
private TicketRepositoryInterface $ticketRepository,
|
||||
) {}
|
||||
|
||||
public function __invoke(PostTicketUpdateMessage $event): void
|
||||
{
|
||||
$ticket = $this->ticketRepository->find($event->ticketId);
|
||||
|
||||
if (null === $ticket) {
|
||||
throw new UnrecoverableMessageHandlingException('Ticket not found');
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatch(new PostTicketUpdateEvent($event->updateKind, $ticket));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<?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\TicketBundle\Messenger;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\TicketUpdateKindEnum;
|
||||
|
||||
final readonly class PostTicketUpdateMessage
|
||||
{
|
||||
public readonly int $ticketId;
|
||||
|
||||
public function __construct(
|
||||
Ticket $ticket,
|
||||
public TicketUpdateKindEnum $updateKind,
|
||||
) {
|
||||
$this->ticketId = $ticket->getId();
|
||||
}
|
||||
}
|
||||
64
src/Bundle/ChillTicketBundle/src/MotiveDTO.php
Normal file
64
src/Bundle/ChillTicketBundle/src/MotiveDTO.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?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\TicketBundle;
|
||||
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
class MotiveDTO
|
||||
{
|
||||
public function __construct(
|
||||
#[Assert\NotBlank()]
|
||||
public array $label = [],
|
||||
public bool $active = true,
|
||||
public Collection $supplementaryComments = new ArrayCollection(),
|
||||
public ?EmergencyStatusEnum $makeTicketEmergency = null,
|
||||
) {
|
||||
if ($this->supplementaryComments->isEmpty()) {
|
||||
$this->supplementaryComments = new ArrayCollection();
|
||||
}
|
||||
}
|
||||
|
||||
public static function fromMotive(Motive $motive): self
|
||||
{
|
||||
$supplementaryCommentsCollection = new ArrayCollection();
|
||||
|
||||
foreach ($motive->getSupplementaryComments() as $comment) {
|
||||
$supplementaryCommentsCollection->add($comment['label']);
|
||||
}
|
||||
|
||||
return new self(
|
||||
label: $motive->getLabel(),
|
||||
active: $motive->isActive(),
|
||||
supplementaryComments: $supplementaryCommentsCollection,
|
||||
makeTicketEmergency: $motive->getMakeTicketEmergency(),
|
||||
);
|
||||
}
|
||||
|
||||
public function applyToMotive(Motive $motive): void
|
||||
{
|
||||
$motive->setLabel($this->label);
|
||||
$motive->setActive($this->active);
|
||||
$motive->setMakeTicketEmergency($this->makeTicketEmergency);
|
||||
|
||||
$supplementaryCommentsArray = [];
|
||||
|
||||
foreach ($this->supplementaryComments as $supplementaryComment) {
|
||||
$supplementaryCommentsArray[] = ['label' => $supplementaryComment];
|
||||
}
|
||||
|
||||
$motive->setSupplementaryComment($supplementaryCommentsArray);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Repository;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
@@ -62,19 +61,4 @@ final readonly class TicketRepository implements TicketRepositoryInterface
|
||||
{
|
||||
return $this->repository->findOneBy(['externalRef' => $extId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count tickets associated with a person where endDate is null.
|
||||
*/
|
||||
public function countOpenedByPerson(Person $person): int
|
||||
{
|
||||
return (int) $this->objectManager->createQuery(
|
||||
'SELECT COUNT(DISTINCT t.id) FROM '.$this->getClassName().' t
|
||||
JOIN t.personHistories ph
|
||||
WHERE ph.person = :person
|
||||
AND ph.endDate IS NULL'
|
||||
)
|
||||
->setParameter('person', $person)
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ declare(strict_types=1);
|
||||
namespace Chill\TicketBundle\Repository;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
/**
|
||||
@@ -26,6 +25,4 @@ interface TicketRepositoryInterface extends ObjectRepository
|
||||
* @return list<Ticket>
|
||||
*/
|
||||
public function findAllOrdered(): array;
|
||||
|
||||
public function countOpenedByPerson(Person $person): int;
|
||||
}
|
||||
|
||||
@@ -177,12 +177,8 @@ export interface Ticket extends BaseTicket<"ticket_ticket:extended"> {
|
||||
}
|
||||
|
||||
export interface TicketFilters {
|
||||
byPerson: Person[];
|
||||
byCreator: User[];
|
||||
byAddressee: UserGroupOrUser[];
|
||||
byCurrentState: TicketState[];
|
||||
byCurrentStateEmergency: TicketEmergencyState[];
|
||||
byMotives: Motive[];
|
||||
byCreatedAfter: string;
|
||||
byCreatedBefore: string;
|
||||
byResponseTimeExceeded: boolean;
|
||||
@@ -202,7 +198,7 @@ export interface TicketFilterParams {
|
||||
byCreatedBefore?: string;
|
||||
byResponseTimeExceeded?: string;
|
||||
byAddresseeToMe?: boolean;
|
||||
byTicketId?: number | null;
|
||||
byTicketId?: number;
|
||||
}
|
||||
|
||||
export interface TicketInitForm {
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'opacity-50': disabled,
|
||||
}"
|
||||
:style="disabled ? 'pointer-events: none;' : ''"
|
||||
>
|
||||
<pick-entity
|
||||
uniqid="ticket-addressee-selector"
|
||||
:types="['user', 'user_group']"
|
||||
:picked="selectedEntities"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="true"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="label"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</div>
|
||||
<pick-entity
|
||||
uniqid="ticket-addressee-selector"
|
||||
:types="['user', 'user_group']"
|
||||
:picked="selectedEntities"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="true"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="label"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -40,11 +33,9 @@ const props = withDefaults(
|
||||
modelValue: Entities[];
|
||||
suggested: Entities[];
|
||||
label?: string;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
label: trans(CHILL_TICKET_TICKET_ADD_ADDRESSEE_USER_LABEL),
|
||||
disabled: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
v-model="motive"
|
||||
class="form-control"
|
||||
@remove="(value: Motive) => $emit('remove', value)"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<template
|
||||
#option="{
|
||||
@@ -96,10 +95,6 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const store = useStore();
|
||||
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'opacity-50': disabled,
|
||||
}"
|
||||
:style="disabled ? 'pointer-events: none;' : ''"
|
||||
>
|
||||
<pick-entity
|
||||
uniqid="ticket-person-selector"
|
||||
:types="types"
|
||||
:picked="pickedEntities"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="multiple"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="label"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</div>
|
||||
<pick-entity
|
||||
uniqid="ticket-person-selector"
|
||||
:types="types"
|
||||
:picked="pickedEntities"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="multiple"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="label"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -29,19 +22,13 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
// Types
|
||||
import { Entities, EntitiesOrMe, EntityType } from "ChillPersonAssets/types";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: EntitiesOrMe[] | EntitiesOrMe | null;
|
||||
suggested: Entities[];
|
||||
multiple: boolean;
|
||||
types: EntityType[];
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
},
|
||||
);
|
||||
const props = defineProps<{
|
||||
modelValue: EntitiesOrMe[] | EntitiesOrMe | null;
|
||||
suggested: Entities[];
|
||||
multiple: boolean;
|
||||
types: EntityType[];
|
||||
label: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: EntitiesOrMe[] | EntitiesOrMe | null];
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
type="number"
|
||||
class="form-control"
|
||||
:placeholder="trans(CHILL_TICKET_LIST_FILTER_TICKET_ID)"
|
||||
:disabled="disabled"
|
||||
@input="
|
||||
ticketId = isNaN(Number(($event.target as HTMLInputElement).value))
|
||||
? null
|
||||
@@ -27,15 +26,9 @@
|
||||
import { ref, watch } from "vue";
|
||||
// Translation
|
||||
import { trans, CHILL_TICKET_LIST_FILTER_TICKET_ID } from "translator";
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: number | null;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
},
|
||||
);
|
||||
const props = defineProps<{
|
||||
modelValue: number | null;
|
||||
}>();
|
||||
|
||||
const ticketId = ref<number | null>(props.modelValue);
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<h1 class="text-primary">
|
||||
{{ title }}
|
||||
</h1>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<ticket-filter-list-component
|
||||
:resultCount="resultCount"
|
||||
:available-persons="availablePersons"
|
||||
:available-motives="availableMotives"
|
||||
:ticket-filter-params="ticketFilterParams"
|
||||
@filters-changed="handleFiltersChanged"
|
||||
/>
|
||||
</div>
|
||||
@@ -33,6 +35,7 @@
|
||||
<ticket-list-component
|
||||
v-else
|
||||
:tickets="ticketList"
|
||||
:title="title"
|
||||
:hasMoreTickets="pagination.next !== null"
|
||||
@fetchNextPage="fetchNextPage"
|
||||
/>
|
||||
@@ -58,10 +61,8 @@ import { Pagination } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { trans, CHILL_TICKET_LIST_LOADING_TICKET } from "translator";
|
||||
|
||||
const store = useStore();
|
||||
const ticketFilterParams = window.ticketFilterParams
|
||||
? window.ticketFilterParams
|
||||
: null;
|
||||
|
||||
const title = window.title;
|
||||
const isLoading = ref(false);
|
||||
const ticketList = computed(
|
||||
() => store.getters.getTicketList as TicketSimple[],
|
||||
@@ -89,27 +90,12 @@ const fetchNextPage = async () => {
|
||||
onMounted(async () => {
|
||||
isLoading.value = true;
|
||||
const filters: TicketFilterParams = {
|
||||
byPerson: ticketFilterParams?.byPerson
|
||||
? ticketFilterParams.byPerson.map((person) => person.id)
|
||||
: [],
|
||||
byCreator: ticketFilterParams?.byCreator
|
||||
? ticketFilterParams.byCreator.map((creator) => creator.id)
|
||||
: [],
|
||||
byAddressee: ticketFilterParams?.byAddressee
|
||||
? ticketFilterParams.byAddressee.map((addressee) => addressee.id)
|
||||
: [],
|
||||
byCurrentState: ticketFilterParams?.byCurrentState ?? ["open"],
|
||||
byCurrentStateEmergency: ticketFilterParams?.byCurrentStateEmergency ?? [],
|
||||
byMotives: ticketFilterParams?.byMotives
|
||||
? ticketFilterParams.byMotives.map((motive) => motive.id)
|
||||
: [],
|
||||
byCreatedAfter: ticketFilterParams?.byCreatedAfter ?? "",
|
||||
byCreatedBefore: ticketFilterParams?.byCreatedBefore ?? "",
|
||||
byResponseTimeExceeded: ticketFilterParams?.byResponseTimeExceeded
|
||||
? "true"
|
||||
: "",
|
||||
byAddresseeToMe: ticketFilterParams?.byAddresseeToMe ?? false,
|
||||
byTicketId: ticketFilterParams?.byTicketId ?? null,
|
||||
byCurrentState: ["open"],
|
||||
byCurrentStateEmergency: [],
|
||||
byCreatedAfter: "",
|
||||
byCreatedBefore: "",
|
||||
byResponseTimeExceeded: "",
|
||||
byAddresseeToMe: false,
|
||||
};
|
||||
try {
|
||||
await store.dispatch("fetchTicketList", filters);
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
trans(CHILL_TICKET_LIST_FILTER_PERSONS_CONCERNED)
|
||||
}}</label>
|
||||
<persons-selector
|
||||
v-model="filters.byPerson"
|
||||
v-model="selectedPersons"
|
||||
:suggested="availablePersons"
|
||||
:multiple="true"
|
||||
:types="['person']"
|
||||
id="personSelector"
|
||||
:label="trans(CHILL_TICKET_LIST_FILTER_BY_PERSON)"
|
||||
:disabled="ticketFilterParams?.byPerson ? true : false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -29,13 +28,12 @@
|
||||
trans(CHILL_TICKET_LIST_FILTER_CREATORS)
|
||||
}}</label>
|
||||
<persons-selector
|
||||
v-model="filters.byCreator"
|
||||
v-model="selectedCreator"
|
||||
:suggested="[]"
|
||||
:multiple="true"
|
||||
:types="['user']"
|
||||
id="userSelector"
|
||||
:label="trans(CHILL_TICKET_LIST_FILTER_BY_CREATOR)"
|
||||
:disabled="ticketFilterParams?.byCreator ? true : false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -44,11 +42,10 @@
|
||||
trans(CHILL_TICKET_LIST_FILTER_ADDRESSEES)
|
||||
}}</label>
|
||||
<addressee-selector-component
|
||||
v-model="filters.byAddressee"
|
||||
v-model="selectedAddressees"
|
||||
:suggested="[]"
|
||||
:label="trans(CHILL_TICKET_LIST_FILTER_BY_ADDRESSEES)"
|
||||
id="addresseeSelector"
|
||||
:disabled="ticketFilterParams?.byAddressee ? true : false"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
@@ -63,13 +60,12 @@
|
||||
:allow-parent-selection="true"
|
||||
@remove="(motive) => removeMotive(motive)"
|
||||
id="motiveSelector"
|
||||
:disabled="ticketFilterParams?.byMotives ? true : false"
|
||||
/>
|
||||
|
||||
<div class="mb-2" style="min-height: 2em">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="motive in filters.byMotives"
|
||||
v-for="motive in selectedMotives"
|
||||
:key="motive.id"
|
||||
class="badge bg-secondary d-flex align-items-center gap-1"
|
||||
>
|
||||
@@ -79,7 +75,6 @@
|
||||
class="btn-close btn-close-white"
|
||||
:aria-label="trans(CHILL_TICKET_LIST_FILTER_REMOVE)"
|
||||
@click="removeMotive(motive)"
|
||||
:disabled="ticketFilterParams?.byMotives ? true : false"
|
||||
></button>
|
||||
</span>
|
||||
</div>
|
||||
@@ -101,7 +96,6 @@
|
||||
}"
|
||||
@update:model-value="handleStateToggle"
|
||||
id="currentState"
|
||||
:disabled="ticketFilterParams?.byCurrentState ? true : false"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -120,9 +114,6 @@
|
||||
}"
|
||||
@update:model-value="handleEmergencyToggle"
|
||||
id="emergency"
|
||||
:disabled="
|
||||
ticketFilterParams?.byCurrentStateEmergency ? true : false
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,7 +129,6 @@
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="stateMe"
|
||||
:disabled="ticketFilterParams?.byAddresseeToMe ? true : false"
|
||||
/>
|
||||
<label class="form-check-label" for="stateMe">
|
||||
{{ trans(CHILL_TICKET_LIST_FILTER_TO_ME) }}
|
||||
@@ -152,9 +142,6 @@
|
||||
v-model="filters.byResponseTimeExceeded"
|
||||
@change="handleResponseTimeExceededChange"
|
||||
id="responseTimeExceeded"
|
||||
:disabled="
|
||||
ticketFilterParams?.byResponseTimeExceeded ? true : false
|
||||
"
|
||||
/>
|
||||
<label class="form-check-label" for="responseTimeExceeded">
|
||||
{{ trans(CHILL_TICKET_LIST_FILTER_RESPONSE_TIME_EXCEEDED) }}
|
||||
@@ -170,11 +157,7 @@
|
||||
<label class="form-label pe-2" for="ticketSelector">
|
||||
{{ trans(CHILL_TICKET_LIST_FILTER_BY_TICKET_ID) }}
|
||||
</label>
|
||||
<ticket-selector
|
||||
v-model="filters.byTicketId"
|
||||
id="ticketSelector"
|
||||
:disabled="ticketFilterParams?.byTicketId ? true : false"
|
||||
/>
|
||||
<ticket-selector v-model="filters.byTicketId" id="ticketSelector" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -187,12 +170,7 @@
|
||||
default-value-time="00:00"
|
||||
:model-value-date="filters.byCreatedAfter"
|
||||
:model-value-time="byCreatedAfterTime"
|
||||
:disabled="
|
||||
filters.byResponseTimeExceeded ||
|
||||
ticketFilterParams?.byCreatedAfter
|
||||
? true
|
||||
: false
|
||||
"
|
||||
:disabled="filters.byResponseTimeExceeded"
|
||||
@update:modelValueDate="filters.byCreatedAfter = $event"
|
||||
@update:modelValueTime="byCreatedAfterTime = $event"
|
||||
/>
|
||||
@@ -205,12 +183,7 @@
|
||||
default-value-time="23:59"
|
||||
:model-value-date="filters.byCreatedBefore"
|
||||
:model-value-time="byCreatedBeforeTime"
|
||||
:disabled="
|
||||
filters.byResponseTimeExceeded ||
|
||||
ticketFilterParams?.byCreatedBefore
|
||||
? true
|
||||
: false
|
||||
"
|
||||
:disabled="filters.byResponseTimeExceeded"
|
||||
@update:modelValueDate="filters.byCreatedBefore = $event"
|
||||
@update:modelValueTime="byCreatedBeforeTime = $event"
|
||||
/>
|
||||
@@ -254,6 +227,7 @@ import {
|
||||
type TicketFilterParams,
|
||||
type TicketFilters,
|
||||
} from "../../../types";
|
||||
import { User, UserGroupOrUser } from "ChillMainAssets/types";
|
||||
|
||||
// Translation
|
||||
import {
|
||||
@@ -295,7 +269,6 @@ const props = defineProps<{
|
||||
availablePersons?: Person[];
|
||||
availableMotives: Motive[];
|
||||
resultCount: number;
|
||||
ticketFilterParams: TicketFilters | null;
|
||||
}>();
|
||||
|
||||
// Emits
|
||||
@@ -303,66 +276,74 @@ const emit = defineEmits<{
|
||||
"filters-changed": [filters: TicketFilterParams];
|
||||
}>();
|
||||
|
||||
const filtersInitValues: TicketFilters = {
|
||||
byPerson: props.ticketFilterParams?.byPerson ?? [],
|
||||
byCreator: props.ticketFilterParams?.byCreator ?? [],
|
||||
byAddressee: props.ticketFilterParams?.byAddressee ?? [],
|
||||
byCurrentState: props.ticketFilterParams?.byCurrentState ?? ["open"],
|
||||
byCurrentStateEmergency:
|
||||
props.ticketFilterParams?.byCurrentStateEmergency ?? [],
|
||||
byMotives: props.ticketFilterParams?.byMotives ?? [],
|
||||
byCreatedAfter: props.ticketFilterParams?.byCreatedAfter ?? "",
|
||||
byCreatedBefore: props.ticketFilterParams?.byCreatedBefore ?? "",
|
||||
byResponseTimeExceeded:
|
||||
props.ticketFilterParams?.byResponseTimeExceeded ?? false,
|
||||
byAddresseeToMe: props.ticketFilterParams?.byAddresseeToMe ?? false,
|
||||
byTicketId: props.ticketFilterParams?.byTicketId ?? null,
|
||||
};
|
||||
|
||||
// État réactif
|
||||
const filters = ref<TicketFilters>({ ...filtersInitValues });
|
||||
const filters = ref<TicketFilters>({
|
||||
byCurrentState: ["open"],
|
||||
byCurrentStateEmergency: [],
|
||||
byCreatedAfter: "",
|
||||
byCreatedBefore: "",
|
||||
byResponseTimeExceeded: false,
|
||||
byAddresseeToMe: false,
|
||||
byTicketId: null,
|
||||
});
|
||||
|
||||
const byCreatedAfterTime = ref("00:00");
|
||||
const byCreatedBeforeTime = ref("23:59");
|
||||
const isClosedToggled = ref(false);
|
||||
const isEmergencyToggled = ref(false);
|
||||
|
||||
// Sélection des personnes
|
||||
const selectedPersons = ref<Person[]>([]);
|
||||
const availablePersons = ref<Person[]>(props.availablePersons || []);
|
||||
const selectedMotive = ref<Motive | null>();
|
||||
|
||||
// Sélection des utilisateur assigné
|
||||
const selectedAddressees = ref<UserGroupOrUser[]>([]);
|
||||
|
||||
// Séléction des créateurs
|
||||
const selectedCreator = ref<User[]>([]);
|
||||
|
||||
// Sélection des motifs
|
||||
const selectedMotive = ref<Motive | null>();
|
||||
const selectedMotives = ref<Motive[]>([]);
|
||||
|
||||
// Watchers pour les sélecteurs
|
||||
watch(selectedMotive, (newMotive) => {
|
||||
if (
|
||||
newMotive &&
|
||||
!filters.value.byMotives.find((m) => m.id === newMotive.id)
|
||||
) {
|
||||
filters.value.byMotives = [...filters.value.byMotives, newMotive];
|
||||
if (newMotive && !selectedMotives.value.find((m) => m.id === newMotive.id)) {
|
||||
selectedMotives.value.push(newMotive);
|
||||
}
|
||||
});
|
||||
|
||||
// Computed pour les IDs des personnes sélectionnées
|
||||
const selectedPersonIds = computed(() =>
|
||||
filters.value.byPerson.map((person) => person.id),
|
||||
selectedPersons.value.map((person) => person.id),
|
||||
);
|
||||
|
||||
// Computed pour les IDs des utilisateur ou groupes sélectionnées
|
||||
const selectedUserAddresseesIds = computed(() =>
|
||||
filters.value.byAddressee
|
||||
selectedAddressees.value
|
||||
.filter((addressee) => addressee.type === "user")
|
||||
.map((addressee) => addressee.id),
|
||||
);
|
||||
|
||||
const selectedGroupAddresseesIds = computed(() =>
|
||||
filters.value.byAddressee
|
||||
selectedAddressees.value
|
||||
.filter((addressee) => addressee.type === "user_group")
|
||||
.map((addressee) => addressee.id),
|
||||
);
|
||||
|
||||
// Computed pour les IDs des créateurs
|
||||
const selectedCreatorIds = computed(() =>
|
||||
filters.value.byCreator.map((creator) => creator.id),
|
||||
selectedCreator.value.map((creator) => creator.id),
|
||||
);
|
||||
|
||||
// Computed pour les IDs des motifs sélectionnés
|
||||
const selectedMotiveIds = computed(() =>
|
||||
filters.value.byMotives.map((motive) => motive.id),
|
||||
selectedMotives.value.map((motive) => motive.id),
|
||||
);
|
||||
|
||||
// Nouveaux états pour les toggles
|
||||
const isClosedToggled = ref(false);
|
||||
const isEmergencyToggled = ref(false);
|
||||
|
||||
// Méthodes pour gérer les toggles
|
||||
const handleStateToggle = (value: boolean) => {
|
||||
if (value) {
|
||||
filters.value.byCurrentState = ["closed"];
|
||||
@@ -398,9 +379,12 @@ const getMotiveDisplayName = (motive: Motive): string => {
|
||||
};
|
||||
|
||||
const removeMotive = (motiveToRemove: Motive): void => {
|
||||
filters.value.byMotives = filters.value.byMotives.filter(
|
||||
(m) => m.id !== motiveToRemove.id,
|
||||
const index = selectedMotives.value.findIndex(
|
||||
(m) => m.id === motiveToRemove.id,
|
||||
);
|
||||
if (index !== -1) {
|
||||
selectedMotives.value.splice(index, 1);
|
||||
}
|
||||
if (selectedMotive.value && motiveToRemove.id == selectedMotive.value.id) {
|
||||
selectedMotive.value = null;
|
||||
}
|
||||
@@ -463,12 +447,22 @@ const applyFilters = (): void => {
|
||||
};
|
||||
|
||||
const resetFilters = (): void => {
|
||||
filters.value = { ...filtersInitValues };
|
||||
filters.value = {
|
||||
byCurrentState: ["open"],
|
||||
byCurrentStateEmergency: [],
|
||||
byCreatedAfter: "",
|
||||
byCreatedBefore: "",
|
||||
byResponseTimeExceeded: false,
|
||||
byAddresseeToMe: false,
|
||||
byTicketId: null,
|
||||
};
|
||||
selectedPersons.value = [];
|
||||
selectedCreator.value = [];
|
||||
selectedAddressees.value = [];
|
||||
selectedMotives.value = [];
|
||||
selectedMotive.value = null;
|
||||
isClosedToggled.value = false;
|
||||
isEmergencyToggled.value = false;
|
||||
byCreatedAfterTime.value = "00:00";
|
||||
byCreatedBeforeTime.value = "23:59";
|
||||
applyFilters();
|
||||
};
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ import { useStore } from "vuex";
|
||||
defineProps<{
|
||||
tickets: TicketSimple[];
|
||||
hasMoreTickets: boolean;
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -3,12 +3,10 @@ import { createApp } from "vue";
|
||||
import { store } from "../TicketApp/store";
|
||||
import VueToast from "vue-toast-notification";
|
||||
import "vue-toast-notification/dist/theme-sugar.css";
|
||||
import { TicketFilters } from "../../types";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
title: string;
|
||||
ticketFilterParams: TicketFilters;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
|
||||
{% block content_form_actions_save_and_view %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,64 @@
|
||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
||||
|
||||
{% block title %}{{ 'admin.motive.list.title'|trans }}{% endblock title %}
|
||||
|
||||
{% block admin_content %}
|
||||
|
||||
<h1>{{ 'admin.motive.list.title'|trans }}</h1>
|
||||
|
||||
<table class="records_list table table-bordered border-dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ 'Label'|trans }}</th>
|
||||
<th>{{ 'Active'|trans }}</th>
|
||||
<th>{{ 'emergency?'|trans }}</th>
|
||||
<th>{{ 'Supplementary comments'|trans }}</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.label|localize_translatable_string }}</td>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.isActive -%}
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
{%- else -%}
|
||||
<i class="fa fa-square-o"></i>
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.makeTicketEmergency -%}
|
||||
{{ entity.makeTicketEmergency.value|trans }}
|
||||
{%- else -%}
|
||||
-
|
||||
{%- endif -%}
|
||||
</td>
|
||||
<td style="text-align:center;">
|
||||
{{ entity.supplementaryComments|length }}
|
||||
</td>
|
||||
<td>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_motive_view', { 'id': entity.id }) }}" class="btn btn-show" title="{{ 'show'|trans }}"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_motive_edit', { 'id': entity.id }) }}" class="btn btn-edit" title="{{ 'edit'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_motive_new') }}" class="btn btn-create">
|
||||
{{ 'admin.motive.new.title'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||
|
||||
{% block title %}
|
||||
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block admin_content %}
|
||||
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
|
||||
{% block content_form_actions_save_and_show %}{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,68 @@
|
||||
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
|
||||
|
||||
{% block title %}{{ 'admin.motive.view.title'|trans }}{% endblock title %}
|
||||
|
||||
{% block admin_content %}
|
||||
|
||||
<h1>{{ 'admin.motive.view.title'|trans }}</h1>
|
||||
|
||||
<table class="record_properties table table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{{ 'Id'|trans }}</th>
|
||||
<td>{{ entity.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'Label'|trans }}</th>
|
||||
<td>{{ entity.label|localize_translatable_string }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'Active'|trans }}</th>
|
||||
<td style="text-align:center;">
|
||||
{%- if entity.isActive -%}
|
||||
<i class="fa fa-check-square-o"></i> {{ 'Yes'|trans }}
|
||||
{%- else -%}
|
||||
<i class="fa fa-square-o"></i> {{ 'No'|trans }}
|
||||
{%- endif -%}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{{ 'emergency?'|trans }}</th>
|
||||
<td>
|
||||
{%- if entity.makeTicketEmergency -%}
|
||||
{{ entity.makeTicketEmergency.value|trans }}
|
||||
{%- else -%}
|
||||
-
|
||||
{%- endif -%}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if entity.supplementaryComments is not empty %}
|
||||
<h2>{{ 'Supplementary comments'|trans }}</h2>
|
||||
<div class="supplementary-comments">
|
||||
{% for comment in entity.supplementaryComments %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="comment-content">
|
||||
{{ comment.label|raw }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<h2>{{ 'Supplementary comments'|trans }}</h2>
|
||||
<p class="text-muted">{{ 'No supplementary comments'|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_crud_motive_index') }}" class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_crud_motive_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'Edit'|trans }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,13 @@
|
||||
{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %}
|
||||
|
||||
{% block vertical_menu_content %}
|
||||
{{ chill_menu('admin_ticket', {
|
||||
'layout': '@ChillMain/Admin/menu_admin_section.html.twig',
|
||||
}) }}
|
||||
{% endblock %}
|
||||
|
||||
{% block layout_wvm_content %}
|
||||
{% block admin_content %}<!-- block content empty -->
|
||||
<h1>{{ 'Tickets configuration' |trans }}</h1>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -1,32 +0,0 @@
|
||||
{% extends "@ChillPerson/Person/layout.html.twig" %}
|
||||
{% set ticketTitle = 'chill_ticket.list.title_with_name'|trans({'%name%': person|chill_entity_render_string }) %}
|
||||
{% set activeRouteKey = 'chill_person_ticket_list' %}
|
||||
{% set ticketFilterParams = {
|
||||
'byPerson': [person]
|
||||
} %}
|
||||
{% block title %}{{ ticketTitle }}{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('vue_ticket_list') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.ticketFilterParams = {{ ticketFilterParams|serialize|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_ticket_list') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ ticketTitle }}</h1>
|
||||
|
||||
<div id="ticketList"></div>
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_ticket_createticket__invoke') }}" class="btn btn-create">{{ 'Create'|trans }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,4 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
{% set ticketTitle = 'chill_ticket.list.title'|trans %}
|
||||
{% block title %}{{ ticketTitle }}{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
@@ -9,11 +7,13 @@
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script type="text/javascript">
|
||||
window.title = "{{ 'chill_ticket.list.title'|trans|escape('js') }}";
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_ticket_list') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ ticketTitle }}</h1>
|
||||
<div id="ticketList"></div>
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
|
||||
@@ -17,9 +17,6 @@ services:
|
||||
tags:
|
||||
- controller.service_arguments
|
||||
|
||||
Chill\TicketBundle\Event\EventSubscriber\:
|
||||
resource: '../Event/EventSubscriber/'
|
||||
|
||||
Chill\TicketBundle\Repository\:
|
||||
resource: '../Repository/'
|
||||
|
||||
@@ -38,11 +35,11 @@ services:
|
||||
Chill\TicketBundle\Menu\:
|
||||
resource: '../Menu/'
|
||||
|
||||
Chill\TicketBundle\Messenger\Handler\:
|
||||
resource: '../Messenger/Handler'
|
||||
|
||||
Chill\TicketBundle\Validation\:
|
||||
resource: '../Validation/'
|
||||
|
||||
Chill\TicketBundle\DataFixtures\:
|
||||
resource: '../DataFixtures/'
|
||||
|
||||
Chill\TicketBundle\Form\:
|
||||
resource: '../Form/'
|
||||
|
||||
@@ -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\Migrations\Ticket;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251022081554 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add ordering property on motive entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_ticket.motive ADD ordering DOUBLE PRECISION DEFAULT \'0.0\'');
|
||||
$this->addSql('UPDATE chill_ticket.motive SET ordering = id * 100');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_ticket.motive DROP ordering');
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
restore: Restaurer
|
||||
chill_ticket:
|
||||
list:
|
||||
title: "Tickets"
|
||||
title_with_name: "{name, select, null {Tickets} undefined {Tickets} other {Tickets de {name}}}"
|
||||
title_menu: "Tickets de l'usager"
|
||||
title: Tickets
|
||||
title_previous_tickets: "{name, select, other {Précédent ticket de {name}} undefined {Précédent ticket}}"
|
||||
no_tickets: "Aucun ticket"
|
||||
loading_ticket: "Chargement des tickets..."
|
||||
@@ -150,3 +148,35 @@ chill_ticket:
|
||||
open_new_tab: "Ouvrir dans un nouvel onglet"
|
||||
iframe_not_supported: "Votre navigateur ne supporte pas les iframes."
|
||||
click_to_open_pdf: "Cliquez ici pour ouvrir le PDF"
|
||||
|
||||
admin:
|
||||
ticket:
|
||||
motive:
|
||||
menu: Motifs
|
||||
motive:
|
||||
list:
|
||||
title: Liste des motifs
|
||||
view:
|
||||
title: Le motif
|
||||
new:
|
||||
title: Créer un motif
|
||||
crud:
|
||||
motive:
|
||||
title_edit: Modifier le motif
|
||||
new:
|
||||
"Create a new motive": "Créer un nouveau motif"
|
||||
|
||||
"Label": "Libellé"
|
||||
"Active": "Actif"
|
||||
"emergency?": "Urgent ?"
|
||||
"Supplementary comments": "Commentaires supplémentaires"
|
||||
"edit": "modifier"
|
||||
"show": "voir"
|
||||
"Yes": "Oui"
|
||||
"No": "Non"
|
||||
"Id": "ID"
|
||||
"Date": "Date"
|
||||
"User": "Utilisateur"
|
||||
"No supplementary comments": "Aucun commentaire supplémentaire"
|
||||
"Back to the list": "Retour à la liste"
|
||||
"Edit": "Modifier"
|
||||
|
||||
@@ -16,13 +16,9 @@ use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\EmergencyStatusUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -40,10 +36,7 @@ final class ChangeEmergencyStateCommandHandlerTest extends TestCase
|
||||
// Create a YES emergency status history
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(Argument::type(EmergencyStatusUpdateEvent::class), TicketUpdateEvent::class)->shouldNotBeCalled();
|
||||
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock(), $eventDispatcher->reveal());
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock());
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);
|
||||
|
||||
$result = $handler->__invoke($ticket, $command);
|
||||
@@ -64,17 +57,7 @@ final class ChangeEmergencyStateCommandHandlerTest extends TestCase
|
||||
// Create a YES emergency status history
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(
|
||||
fn ($e) => $e instanceof EmergencyStatusUpdateEvent
|
||||
&& EmergencyStatusEnum::YES === $e->previousEmergencyStatus
|
||||
&& EmergencyStatusEnum::NO === $e->newEmergencyStatus
|
||||
),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock(), $eventDispatcher->reveal());
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock());
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::NO);
|
||||
|
||||
$result = $handler->__invoke($ticket, $command);
|
||||
@@ -107,17 +90,7 @@ final class ChangeEmergencyStateCommandHandlerTest extends TestCase
|
||||
// Create a NO emergency status history
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::NO, $ticket);
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(
|
||||
fn ($e) => $e instanceof EmergencyStatusUpdateEvent
|
||||
&& EmergencyStatusEnum::NO === $e->previousEmergencyStatus
|
||||
&& EmergencyStatusEnum::YES === $e->newEmergencyStatus
|
||||
),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock(), $eventDispatcher->reveal());
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock());
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);
|
||||
|
||||
$result = $handler->__invoke($ticket, $command);
|
||||
|
||||
@@ -19,19 +19,16 @@ use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\MotiveUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @covers \Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler
|
||||
* @coversNothing
|
||||
*/
|
||||
final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
{
|
||||
@@ -40,18 +37,14 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
private function buildHandler(
|
||||
EntityManagerInterface $entityManager,
|
||||
?ChangeEmergencyStateCommandHandler $changeEmergencyStateCommandHandler = null,
|
||||
?EventDispatcherInterface $eventDispatcher = null,
|
||||
): ReplaceMotiveCommandHandler {
|
||||
$clock = new MockClock();
|
||||
|
||||
if (null === $changeEmergencyStateCommandHandler) {
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class)->reveal();
|
||||
}
|
||||
if (null === $eventDispatcher) {
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class)->reveal();
|
||||
}
|
||||
|
||||
return new ReplaceMotiveCommandHandler($clock, $entityManager, $changeEmergencyStateCommandHandler, $eventDispatcher);
|
||||
return new ReplaceMotiveCommandHandler($clock, $entityManager, $changeEmergencyStateCommandHandler);
|
||||
}
|
||||
|
||||
public function testHandleOnTicketWithoutMotive(): void
|
||||
@@ -68,16 +61,7 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
return $arg->getMotive() === $motive;
|
||||
}))->shouldBeCalled();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(fn ($event) => $event instanceof MotiveUpdateEvent
|
||||
&& $event->newMotive === $motive
|
||||
&& null === $event->previousMotive
|
||||
&& $event->hasChanges()),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), null, $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
@@ -99,17 +83,7 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
return $arg->getMotive() === $motive;
|
||||
}))->shouldBeCalled();
|
||||
|
||||
$previous = $history->getMotive();
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(fn ($event) => $event instanceof MotiveUpdateEvent
|
||||
&& $event->newMotive === $motive
|
||||
&& $previous === $event->previousMotive
|
||||
&& $event->hasChanges()),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), null, $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
@@ -132,10 +106,7 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
return $arg->getMotive() === $motive;
|
||||
}))->shouldNotBeCalled();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(Argument::any(), TicketUpdateEvent::class)->shouldNotBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), null, $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
@@ -163,15 +134,10 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
Argument::that(fn (ChangeEmergencyStateCommand $command) => EmergencyStatusEnum::YES === $command->newEmergencyStatus)
|
||||
)->shouldBeCalled();
|
||||
|
||||
// Expect event dispatch for motive update
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(Argument::type(MotiveUpdateEvent::class), TicketUpdateEvent::class)->shouldBeCalled();
|
||||
|
||||
// Create the handler with our mocks
|
||||
$handler = $this->buildHandler(
|
||||
$entityManager->reveal(),
|
||||
$changeEmergencyStateCommandHandler->reveal(),
|
||||
$eventDispatcher->reveal()
|
||||
$changeEmergencyStateCommandHandler->reveal()
|
||||
);
|
||||
|
||||
// Handle the command
|
||||
@@ -200,15 +166,10 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
Argument::cetera()
|
||||
)->shouldNotBeCalled();
|
||||
|
||||
// Expect event dispatch for motive update
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(Argument::type(MotiveUpdateEvent::class), TicketUpdateEvent::class)->shouldBeCalled();
|
||||
|
||||
// Create the handler with our mocks
|
||||
$handler = $this->buildHandler(
|
||||
$entityManager->reveal(),
|
||||
$changeEmergencyStateCommandHandler->reveal(),
|
||||
$eventDispatcher->reveal()
|
||||
$changeEmergencyStateCommandHandler->reveal()
|
||||
);
|
||||
|
||||
// Handle the command
|
||||
|
||||
@@ -17,15 +17,12 @@ use Chill\TicketBundle\Action\Ticket\Handler\SetPersonsCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\SetPersonsCommand;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\PersonsUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -45,16 +42,7 @@ final class SetPersonsCommandHandlerTest extends TestCase
|
||||
$entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person1))->shouldBeCalledOnce();
|
||||
$entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $group1))->shouldBeCalledOnce();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(fn ($arg) => $arg instanceof PersonsUpdateEvent
|
||||
&& in_array($person1, $arg->personsAdded, true)
|
||||
&& in_array($group1, $arg->personsAdded, true)
|
||||
&& [] === $arg->personsRemoved),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, $command);
|
||||
|
||||
@@ -71,15 +59,7 @@ final class SetPersonsCommandHandlerTest extends TestCase
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person))->shouldNotBeCalled();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(
|
||||
fn ($arg) => $arg instanceof PersonsUpdateEvent
|
||||
),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldNotBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, $command);
|
||||
|
||||
@@ -98,17 +78,7 @@ final class SetPersonsCommandHandlerTest extends TestCase
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person2))->shouldBeCalled();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(
|
||||
fn ($arg) => $arg instanceof PersonsUpdateEvent
|
||||
&& in_array($person, $arg->personsRemoved, true) && 1 === count($arg->personsRemoved)
|
||||
&& in_array($person2, $arg->personsAdded, true) && 1 === count($arg->personsAdded)
|
||||
),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, $command);
|
||||
|
||||
@@ -125,28 +95,18 @@ final class SetPersonsCommandHandlerTest extends TestCase
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::that(fn ($arg) => $arg instanceof PersonHistory && $arg->getPerson() === $person))->shouldBeCalledOnce();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(
|
||||
Argument::that(
|
||||
fn ($arg) => $arg instanceof PersonsUpdateEvent
|
||||
&& in_array($person, $arg->personsAdded, true) && 1 === count($arg->personsAdded)
|
||||
&& [] === $arg->personsRemoved
|
||||
),
|
||||
TicketUpdateEvent::class
|
||||
)->shouldBeCalled();
|
||||
|
||||
$handler = $this->buildHandler($entityManager->reveal(), $eventDispatcher->reveal());
|
||||
$handler = $this->buildHandler($entityManager->reveal());
|
||||
|
||||
$handler->handle($ticket, $command);
|
||||
|
||||
self::assertCount(1, $ticket->getPersons());
|
||||
}
|
||||
|
||||
private function buildHandler(EntityManagerInterface $entityManager, EventDispatcherInterface $eventDispatcher): SetPersonsCommandHandler
|
||||
private function buildHandler(EntityManagerInterface $entityManager): SetPersonsCommandHandler
|
||||
{
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->getUser()->willReturn(new User());
|
||||
|
||||
return new SetPersonsCommandHandler(new MockClock(), $entityManager, $security->reveal(), $eventDispatcher);
|
||||
return new SetPersonsCommandHandler(new MockClock(), $entityManager, $security->reveal());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<?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\TicketBundle\tests\Event\EventSubscriber;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\EventSubscriber\GeneratePostUpdateTicketEventSubscriber;
|
||||
use Chill\TicketBundle\Event\TicketUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateKindEnum;
|
||||
use Chill\TicketBundle\Messenger\PostTicketUpdateMessage;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\TerminateEvent;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
|
||||
/**
|
||||
* @covers \Chill\TicketBundle\Event\EventSubscriber\GeneratePostUpdateTicketEventSubscriber
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class GeneratePostUpdateTicketEventSubscriberTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testOnTicketUpdate(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$reflection = new \ReflectionClass(Ticket::class);
|
||||
$idProperty = $reflection->getProperty('id');
|
||||
$idProperty->setValue($ticket, 1);
|
||||
$event = new class (TicketUpdateKindEnum::UPDATE_MOTIVE, $ticket) extends TicketUpdateEvent {};
|
||||
|
||||
$messageBus = $this->prophesize(MessageBusInterface::class);
|
||||
$messageBus->dispatch(Argument::that(fn ($arg) => $arg instanceof PostTicketUpdateMessage && TicketUpdateKindEnum::UPDATE_MOTIVE === $arg->updateKind && 1 === $arg->ticketId))
|
||||
->will(fn ($args) => new Envelope($args[0]))
|
||||
->shouldBeCalled();
|
||||
|
||||
$eventSubscriber = new GeneratePostUpdateTicketEventSubscriber($messageBus->reveal());
|
||||
$eventSubscriber->onTicketUpdate($event);
|
||||
|
||||
$kernel = $this->prophesize(KernelInterface::class);
|
||||
$terminate = new TerminateEvent($kernel->reveal(), new Request(), new Response());
|
||||
$eventSubscriber->onKernelTerminate($terminate);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?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\TicketBundle\tests\Messenger\Handler;
|
||||
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Chill\TicketBundle\Event\PostTicketUpdateEvent;
|
||||
use Chill\TicketBundle\Event\TicketUpdateKindEnum;
|
||||
use Chill\TicketBundle\Messenger\PostTicketUpdateMessage;
|
||||
use Chill\TicketBundle\Messenger\Handler\PostTicketUpdateMessageHandler;
|
||||
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* @covers \Chill\TicketBundle\Messenger\Handler\PostTicketUpdateMessageHandler
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PostTicketUpdateMessageHandlerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testDispatchesEventWhenTicketExists(): void
|
||||
{
|
||||
// Arrange: a Ticket with an ID
|
||||
$ticket = new Ticket();
|
||||
$reflection = new \ReflectionClass(Ticket::class);
|
||||
$idProperty = $reflection->getProperty('id');
|
||||
$idProperty->setValue($ticket, 123);
|
||||
|
||||
$message = new PostTicketUpdateMessage($ticket, TicketUpdateKindEnum::UPDATE_MOTIVE);
|
||||
|
||||
// Mock repository to return the Ticket when searching by id
|
||||
$ticketRepository = $this->prophesize(TicketRepositoryInterface::class);
|
||||
$ticketRepository->find(123)->willReturn($ticket)->shouldBeCalledOnce();
|
||||
|
||||
// Expect the dispatcher to dispatch a PostTicketUpdateEvent with correct data
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher
|
||||
->dispatch(Argument::that(fn ($event) => $event instanceof PostTicketUpdateEvent
|
||||
&& TicketUpdateKindEnum::UPDATE_MOTIVE === $event->updateKind
|
||||
&& $event->ticket === $ticket))
|
||||
->will(fn ($args) => $args[0])
|
||||
->shouldBeCalledOnce();
|
||||
|
||||
$handler = new PostTicketUpdateMessageHandler($eventDispatcher->reveal(), $ticketRepository->reveal());
|
||||
|
||||
// Act
|
||||
$handler($message);
|
||||
|
||||
// Assert: expectations asserted by Prophecy
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
public function testThrowsWhenTicketNotFound(): void
|
||||
{
|
||||
// Arrange: a Ticket with an ID for the message, but repository will return null
|
||||
$ticket = new Ticket();
|
||||
$reflection = new \ReflectionClass(Ticket::class);
|
||||
$idProperty = $reflection->getProperty('id');
|
||||
$idProperty->setValue($ticket, 999);
|
||||
|
||||
$message = new PostTicketUpdateMessage($ticket, TicketUpdateKindEnum::UPDATE_MOTIVE);
|
||||
|
||||
$ticketRepository = $this->prophesize(TicketRepositoryInterface::class);
|
||||
$ticketRepository->find(999)->willReturn(null)->shouldBeCalledOnce();
|
||||
|
||||
$eventDispatcher = $this->prophesize(EventDispatcherInterface::class);
|
||||
$eventDispatcher->dispatch(Argument::any())->shouldNotBeCalled();
|
||||
|
||||
$handler = new PostTicketUpdateMessageHandler($eventDispatcher->reveal(), $ticketRepository->reveal());
|
||||
|
||||
// Assert: exception is thrown
|
||||
$this->expectException(UnrecoverableMessageHandlingException::class);
|
||||
$this->expectExceptionMessage('Ticket not found');
|
||||
|
||||
// Act
|
||||
$handler($message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user