mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-26 12:06:13 +00:00
Record that a ticket can be in emergency, or not
This commit is contained in:
parent
d43b739654
commit
0a331aab37
@ -22,7 +22,7 @@ Chill is a comprehensive web application built as a set of Symfony bundles. It i
|
||||
- **Backend**: PHP 8.3+, Symfony 5.4
|
||||
- **Frontend**: JavaScript/TypeScript, Vue.js 3, Bootstrap 5
|
||||
- **Build Tools**: Webpack Encore, Yarn
|
||||
- **Database**: PostgreSQL with materialized views
|
||||
- **Database**: PostgreSQL with materialized views. We do not support other databases.
|
||||
- **Other Services**: Redis, AMQP (RabbitMQ), SMTP
|
||||
|
||||
## Project Structure
|
||||
@ -149,7 +149,35 @@ Key configuration files:
|
||||
- `package.json`: JavaScript dependencies and scripts
|
||||
- `.env`: Default environment variables. Must usually not be updated: use `.env.local` instead.
|
||||
|
||||
### Development guidelines
|
||||
### Database migrations
|
||||
|
||||
Each time a doctrine entity is created, we generate migration to adapt the database.
|
||||
|
||||
The migration are created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace <namespace>`, where the namespace is the relevant namespace for migration. As this is a bash script, do not forget to quote the `\` (`\` must become `\\` in your command).
|
||||
|
||||
Each bundle has his own namespace for migration (always ask me to confirm that command, with a list of updated / created entities so that I can confirm you that it is ok):
|
||||
|
||||
- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`;
|
||||
- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`;
|
||||
- `Chill\Bundle\CustomFieldsBundle` writes migrations to `Chill\Migrations\CustomFields`;
|
||||
- `Chill\Bundle\DocGeneratorBundle` writes migrations to `Chill\Migrations\DocGenerator`;
|
||||
- `Chill\Bundle\DocStoreBundle` writes migrations to `Chill\Migrations\DocStore`;
|
||||
- `Chill\Bundle\EventBundle` writes migrations to `Chill\Migrations\Event`;
|
||||
- `Chill\Bundle\CalendarBundle` writes migrations to `Chill\Migrations\Calendar`;
|
||||
- `Chill\Bundle\FamilyMembersBundle` writes migrations to `Chill\Migrations\FamilyMembers`;
|
||||
- `Chill\Bundle\FranceTravailApiBundle` writes migrations to `Chill\Migrations\FranceTravailApi`;
|
||||
- `Chill\Bundle\JobBundle` writes migrations to `Chill\Migrations\Job`;
|
||||
- `Chill\Bundle\MainBundle` writes migrations to `Chill\Migrations\Main`;
|
||||
- `Chill\Bundle\PersonBundle` writes migrations to `Chill\Migrations\Person`;
|
||||
- `Chill\Bundle\ReportBundle` writes migrations to `Chill\Migrations\Report`;
|
||||
- `Chill\Bundle\TaskBundle` writes migrations to `Chill\Migrations\Task`;
|
||||
- `Chill\Bundle\ThirdPartyBundle` writes migrations to `Chill\Migrations\ThirdParty`;
|
||||
- `Chill\Bundle\TicketBundle` writes migrations to `Chill\Migrations\Ticket`;
|
||||
- `Chill\Bundle\WopiBundle` writes migrations to `Chill\Migrations\Wopi`;
|
||||
|
||||
Once created the, comment's classes should be removed and a description of the changes made to the entities should be added to the migrations, using the `getDescription` method. The migration should not be cleaned by any artificial intelligence, as modifying this migration is error prone.
|
||||
|
||||
### Guidelines related to code structure and requirements
|
||||
|
||||
#### Usage of clock
|
||||
|
||||
|
@ -255,3 +255,36 @@ paths:
|
||||
description: "OK"
|
||||
401:
|
||||
description: "UNAUTHORIZED"
|
||||
/1.0/ticket/ticket/{id}/emergency/{emergency}:
|
||||
post:
|
||||
tags:
|
||||
- ticket
|
||||
summary: Set a ticket as emergency
|
||||
description: |
|
||||
Re-open an existing ticket.
|
||||
|
||||
If the ticket is already opened, no action will be performed on this ticket: his state will remains unchanged, and the
|
||||
ticket will be returned.
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The ticket id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: emergency
|
||||
in: path
|
||||
required: true
|
||||
description: the new state of emergency
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
- yes
|
||||
- no
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
401:
|
||||
description: "UNAUTHORIZED"
|
||||
|
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Action\Ticket;
|
||||
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
|
||||
/**
|
||||
* Command to change the emergency status of a ticket.
|
||||
*/
|
||||
final readonly class ChangeEmergencyStateCommand
|
||||
{
|
||||
public function __construct(
|
||||
public EmergencyStatusEnum $newEmergencyStatus,
|
||||
) {}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?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\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
|
||||
/**
|
||||
* Handler for changing the emergency status of a ticket.
|
||||
*/
|
||||
class ChangeEmergencyStateCommandHandler
|
||||
{
|
||||
public function __construct(private readonly ClockInterface $clock) {}
|
||||
|
||||
public function __invoke(Ticket $ticket, ChangeEmergencyStateCommand $command): Ticket
|
||||
{
|
||||
// If the ticket is already in the requested emergency status, return it without changes
|
||||
if ($command->newEmergencyStatus === $ticket->getEmergencyStatus()) {
|
||||
return $ticket;
|
||||
}
|
||||
|
||||
// End the current emergency status history (if any)
|
||||
foreach ($ticket->getEmergencyStatusHistories() as $emergencyStatusHistory) {
|
||||
if (null === $emergencyStatusHistory->getEndDate()) {
|
||||
$emergencyStatusHistory->setEndDate($this->clock->now());
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new emergency status history with the new status
|
||||
new EmergencyStatusHistory(
|
||||
$command->newEmergencyStatus,
|
||||
$ticket,
|
||||
$this->clock->now(),
|
||||
);
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
}
|
@ -12,6 +12,8 @@ declare(strict_types=1);
|
||||
namespace Chill\TicketBundle\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\CreateTicketCommand;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\StateEnum;
|
||||
use Chill\TicketBundle\Entity\StateHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
@ -26,7 +28,9 @@ class CreateTicketCommandHandler
|
||||
$ticket = new Ticket();
|
||||
$ticket->setExternalRef($command->externalReference);
|
||||
|
||||
// initialize the first states
|
||||
new StateHistory(StateEnum::OPEN, $ticket, $this->clock->now());
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::NO, $ticket, $this->clock->now());
|
||||
|
||||
return $ticket;
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
@ -22,6 +23,7 @@ final readonly class ReplaceMotiveCommandHandler
|
||||
public function __construct(
|
||||
private ClockInterface $clock,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private ChangeEmergencyStateCommandHandler $changeEmergencyStateCommandHandler,
|
||||
) {}
|
||||
|
||||
public function handle(Ticket $ticket, ReplaceMotiveCommand $command): void
|
||||
@ -50,6 +52,12 @@ final readonly class ReplaceMotiveCommandHandler
|
||||
if ($readyToAdd) {
|
||||
$history = new MotiveHistory($command->motive, $ticket, $this->clock->now());
|
||||
$this->entityManager->persist($history);
|
||||
|
||||
// Check if the motive has makeTicketEmergency set and update the ticket's emergency status if needed
|
||||
if ($command->motive->isMakeTicketEmergency()) {
|
||||
$changeEmergencyCommand = new ChangeEmergencyStateCommand($command->motive->getMakeTicketEmergency());
|
||||
($this->changeEmergencyStateCommandHandler)($ticket, $changeEmergencyCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Controller;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* Controller for changing the emergency status of a ticket.
|
||||
*/
|
||||
final readonly class ChangeEmergencyStateApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ChangeEmergencyStateCommandHandler $changeEmergencyStateCommandHandler,
|
||||
private Security $security,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private SerializerInterface $serializer,
|
||||
) {}
|
||||
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/emergency/yes', name: 'chill_ticket_ticket_emergency_yes_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function setEmergencyYes(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to set emergency status to YES.');
|
||||
}
|
||||
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);
|
||||
$this->changeEmergencyStateCommandHandler->__invoke($ticket, $command);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($ticket, 'json', ['groups' => ['read']]),
|
||||
json: true
|
||||
);
|
||||
}
|
||||
|
||||
#[Route('/api/1.0/ticket/ticket/{id}/emergency/no', name: 'chill_ticket_ticket_emergency_no_api', requirements: ['id' => '\d+'], methods: ['POST'])]
|
||||
public function setEmergencyNo(Ticket $ticket): Response
|
||||
{
|
||||
if (!$this->security->isGranted('ROLE_USER')) {
|
||||
throw new AccessDeniedHttpException('Only users are allowed to set emergency status to NO.');
|
||||
}
|
||||
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::NO);
|
||||
$this->changeEmergencyStateCommandHandler->__invoke($ticket, $command);
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($ticket, 'json', ['groups' => ['read']]),
|
||||
json: true
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?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\Entity;
|
||||
|
||||
/**
|
||||
* Represents the emergency status of a ticket (yes or no).
|
||||
*/
|
||||
enum EmergencyStatusEnum: string
|
||||
{
|
||||
case YES = 'yes';
|
||||
case NO = 'no';
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Entity;
|
||||
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
|
||||
/**
|
||||
* Represents the history of an emergency status associated with a ticket.
|
||||
*
|
||||
* This entity is used to track the changes in a ticket's emergency status over time.
|
||||
* Implements the TrackCreationInterface for tracking entity lifecycle creation.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'emergency_status_history', schema: 'chill_ticket')]
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_emergency_status_history' => EmergencyStatusHistory::class])]
|
||||
class EmergencyStatusHistory implements TrackCreationInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: false)]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private ?\DateTimeImmutable $endDate = null;
|
||||
|
||||
public function __construct(
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: false, enumType: EmergencyStatusEnum::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private EmergencyStatusEnum $emergencyStatus,
|
||||
#[ORM\ManyToOne(targetEntity: Ticket::class)]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private Ticket $ticket,
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: false)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private \DateTimeImmutable $startDate = new \DateTimeImmutable('now'),
|
||||
) {
|
||||
$ticket->addEmergencyStatusHistory($this);
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getEmergencyStatus(): EmergencyStatusEnum
|
||||
{
|
||||
return $this->emergencyStatus;
|
||||
}
|
||||
|
||||
public function getStartDate(): \DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
|
||||
public function setEndDate(?\DateTimeImmutable $endDate): void
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
}
|
@ -33,6 +33,10 @@ class Motive
|
||||
#[Serializer\Groups(['read'])]
|
||||
private bool $active = true;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, nullable: true, enumType: EmergencyStatusEnum::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private ?EmergencyStatusEnum $makeTicketEmergency = null;
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->active;
|
||||
@ -57,4 +61,19 @@ class Motive
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
public function getMakeTicketEmergency(): ?EmergencyStatusEnum
|
||||
{
|
||||
return $this->makeTicketEmergency;
|
||||
}
|
||||
|
||||
public function setMakeTicketEmergency(?EmergencyStatusEnum $makeTicketEmergency): void
|
||||
{
|
||||
$this->makeTicketEmergency = $makeTicketEmergency;
|
||||
}
|
||||
|
||||
public function isMakeTicketEmergency(): bool
|
||||
{
|
||||
return null !== $this->makeTicketEmergency;
|
||||
}
|
||||
}
|
||||
|
@ -90,6 +90,12 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
#[ORM\OneToMany(targetEntity: StateHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||
private Collection $stateHistories;
|
||||
|
||||
/**
|
||||
* @var Collection<int, EmergencyStatusHistory>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: EmergencyStatusHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||
private Collection $emergencyStatusHistories;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresseeHistory = new ArrayCollection();
|
||||
@ -98,6 +104,7 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
$this->personHistories = new ArrayCollection();
|
||||
$this->inputHistories = new ArrayCollection();
|
||||
$this->stateHistories = new ArrayCollection();
|
||||
$this->emergencyStatusHistories = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@ -260,4 +267,31 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
return $this->stateHistories;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use @see{EmergencyStatusHistory::__construct} instead
|
||||
*/
|
||||
public function addEmergencyStatusHistory(EmergencyStatusHistory $emergencyStatusHistory): void
|
||||
{
|
||||
$this->emergencyStatusHistories->add($emergencyStatusHistory);
|
||||
}
|
||||
|
||||
public function getEmergencyStatus(): ?EmergencyStatusEnum
|
||||
{
|
||||
foreach ($this->emergencyStatusHistories as $emergencyStatusHistory) {
|
||||
if (null === $emergencyStatusHistory->getEndDate()) {
|
||||
return $emergencyStatusHistory->getEmergencyStatus();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReadableCollection<int, EmergencyStatusHistory>
|
||||
*/
|
||||
public function getEmergencyStatusHistories(): ReadableCollection
|
||||
{
|
||||
return $this->emergencyStatusHistories;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ export interface Motive {
|
||||
|
||||
export type TicketState = "open"|"closed";
|
||||
|
||||
export type TicketEmergencyState = "yes"|"no";
|
||||
|
||||
interface TicketHistory<T extends string, D extends object> {
|
||||
event_type: T;
|
||||
at: DateTime;
|
||||
@ -116,6 +118,7 @@ export interface Ticket {
|
||||
createdAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
currentState: TicketState | null;
|
||||
emergency: TicketEmergencyState | null;
|
||||
}
|
||||
|
||||
export interface addNewPersons {
|
||||
|
@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||
use Chill\TicketBundle\Entity\Comment;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\StateHistory;
|
||||
@ -50,7 +51,8 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
||||
'updatedAt' => $this->normalizer->normalize($object->getUpdatedAt(), $format, $context),
|
||||
'updatedBy' => $this->normalizer->normalize($object->getUpdatedBy(), $format, $context),
|
||||
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context),
|
||||
'currentState' => $object->getState()?->value,
|
||||
'currentState' => $object->getState()?->value ?? 'open',
|
||||
'emergency' => $object->getEmergencyStatus()?->value ?? 'no',
|
||||
];
|
||||
}
|
||||
|
||||
@ -102,6 +104,17 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
||||
),
|
||||
...$this->addresseesStates($ticket),
|
||||
...$this->personStates($ticket),
|
||||
...array_map(
|
||||
fn (EmergencyStatusHistory $stateHistory) => [
|
||||
'event_type' => 'emergency_change',
|
||||
'at' => $stateHistory->getStartDate(),
|
||||
'by' => $stateHistory->getCreatedBy(),
|
||||
'data' => [
|
||||
'new_emergency' => $stateHistory->getEmergencyStatus()->value,
|
||||
],
|
||||
],
|
||||
$ticket->getEmergencyStatusHistories()->toArray(),
|
||||
),
|
||||
];
|
||||
|
||||
if (null !== $ticket->getCreatedBy() && null !== $ticket->getCreatedAt()) {
|
||||
|
@ -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\Migrations\Ticket;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250620145414 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add emergency status to ticket';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE SEQUENCE chill_ticket.emergency_status_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_ticket.emergency_status_history (
|
||||
id INT NOT NULL,
|
||||
ticket_id INT NOT NULL,
|
||||
endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
emergencyStatus VARCHAR(255) NOT NULL,
|
||||
startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
|
||||
createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_16CF4FDB700047D2 ON chill_ticket.emergency_status_history (ticket_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_16CF4FDB3174800F ON chill_ticket.emergency_status_history (createdBy_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
COMMENT ON COLUMN chill_ticket.emergency_status_history.endDate IS '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
COMMENT ON COLUMN chill_ticket.emergency_status_history.startDate IS '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
COMMENT ON COLUMN chill_ticket.emergency_status_history.createdAt IS '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.emergency_status_history ADD CONSTRAINT FK_16CF4FDB700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.emergency_status_history ADD CONSTRAINT FK_16CF4FDB3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.emergency_status_history ADD CONSTRAINT ticket_emergency_state_not_overlaps
|
||||
exclude using gist (ticket_id with =, tsrange(startdate, enddate) with &&)
|
||||
deferrable initially deferred
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.emergency_status_history DROP CONSTRAINT FK_16CF4FDB700047D2
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.emergency_status_history DROP CONSTRAINT FK_16CF4FDB3174800F
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_ticket.emergency_status_history
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP SEQUENCE chill_ticket.emergency_status_history_id_seq
|
||||
SQL);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Ticket;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250620164517 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add makeTicketEmergency field to Motive entity to allow automatic emergency status changes when a motive is associated with a ticket';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.motive ADD makeTicketEmergency VARCHAR(255) DEFAULT NULL
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.motive DROP makeTicketEmergency
|
||||
SQL);
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
<?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\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\Clock\MockClock;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class ChangeEmergencyStateCommandHandlerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testInvokeWithAlreadyYesEmergencyStatus(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
// Create a YES emergency status history
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);
|
||||
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock());
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);
|
||||
|
||||
$result = $handler->__invoke($ticket, $command);
|
||||
|
||||
// Assert that the ticket is returned unchanged
|
||||
$this->assertSame($ticket, $result);
|
||||
$this->assertSame(EmergencyStatusEnum::YES, $ticket->getEmergencyStatus());
|
||||
|
||||
// Assert that no new emergency status history was created
|
||||
$emergencyStatusHistories = $ticket->getEmergencyStatusHistories();
|
||||
$this->assertCount(1, $emergencyStatusHistories);
|
||||
}
|
||||
|
||||
public function testInvokeWithYesEmergencyStatusToNo(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
// Create a YES emergency status history
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);
|
||||
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock());
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::NO);
|
||||
|
||||
$result = $handler->__invoke($ticket, $command);
|
||||
|
||||
// Assert that the ticket is returned
|
||||
$this->assertSame($ticket, $result);
|
||||
|
||||
// Assert that the ticket emergency status is now NO
|
||||
$this->assertSame(EmergencyStatusEnum::NO, $ticket->getEmergencyStatus());
|
||||
|
||||
// Assert that the old emergency status history was ended and a new one was created
|
||||
$emergencyStatusHistories = $ticket->getEmergencyStatusHistories();
|
||||
$this->assertCount(2, $emergencyStatusHistories);
|
||||
|
||||
// The first emergency status history should be ended
|
||||
$yesEmergencyStatusHistory = $emergencyStatusHistories->first();
|
||||
$this->assertNotNull($yesEmergencyStatusHistory->getEndDate());
|
||||
$this->assertSame(EmergencyStatusEnum::YES, $yesEmergencyStatusHistory->getEmergencyStatus());
|
||||
|
||||
// The last emergency status history should be NO and active
|
||||
$noEmergencyStatusHistory = $emergencyStatusHistories->last();
|
||||
$this->assertNull($noEmergencyStatusHistory->getEndDate());
|
||||
$this->assertSame(EmergencyStatusEnum::NO, $noEmergencyStatusHistory->getEmergencyStatus());
|
||||
}
|
||||
|
||||
public function testInvokeWithNoEmergencyStatusToYes(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
// Create a NO emergency status history
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::NO, $ticket);
|
||||
|
||||
$handler = new ChangeEmergencyStateCommandHandler(new MockClock());
|
||||
$command = new ChangeEmergencyStateCommand(EmergencyStatusEnum::YES);
|
||||
|
||||
$result = $handler->__invoke($ticket, $command);
|
||||
|
||||
// Assert that the ticket is returned
|
||||
$this->assertSame($ticket, $result);
|
||||
|
||||
// Assert that the ticket emergency status is now YES
|
||||
$this->assertSame(EmergencyStatusEnum::YES, $ticket->getEmergencyStatus());
|
||||
|
||||
// Assert that the old emergency status history was ended and a new one was created
|
||||
$emergencyStatusHistories = $ticket->getEmergencyStatusHistories();
|
||||
$this->assertCount(2, $emergencyStatusHistories);
|
||||
|
||||
// The first emergency status history should be ended
|
||||
$noEmergencyStatusHistory = $emergencyStatusHistories->first();
|
||||
$this->assertNotNull($noEmergencyStatusHistory->getEndDate());
|
||||
$this->assertSame(EmergencyStatusEnum::NO, $noEmergencyStatusHistory->getEmergencyStatus());
|
||||
|
||||
// The last emergency status history should be YES and active
|
||||
$yesEmergencyStatusHistory = $emergencyStatusHistories->last();
|
||||
$this->assertNull($yesEmergencyStatusHistory->getEndDate());
|
||||
$this->assertSame(EmergencyStatusEnum::YES, $yesEmergencyStatusHistory->getEmergencyStatus());
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Tests\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\CreateTicketCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\CreateTicketCommandHandler;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\StateEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@ -38,6 +39,7 @@ class CreateTicketCommandHandlerTest extends TestCase
|
||||
self::assertInstanceOf(Ticket::class, $actual);
|
||||
self::assertEquals('', $actual->getExternalRef());
|
||||
self::assertEquals(StateEnum::OPEN, $actual->getState());
|
||||
self::assertEquals(EmergencyStatusEnum::NO, $actual->getEmergencyStatus());
|
||||
}
|
||||
|
||||
public function testHandleWithReference(): void
|
||||
@ -48,5 +50,6 @@ class CreateTicketCommandHandlerTest extends TestCase
|
||||
self::assertInstanceOf(Ticket::class, $actual);
|
||||
self::assertEquals($ref, $actual->getExternalRef());
|
||||
self::assertEquals(StateEnum::OPEN, $actual->getState());
|
||||
self::assertEquals(EmergencyStatusEnum::NO, $actual->getEmergencyStatus());
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,11 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Tests\Action\Ticket\Handler;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\ReplaceMotiveCommand;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
@ -33,10 +36,15 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
|
||||
private function buildHandler(
|
||||
EntityManagerInterface $entityManager,
|
||||
?ChangeEmergencyStateCommandHandler $changeEmergencyStateCommandHandler = null,
|
||||
): ReplaceMotiveCommandHandler {
|
||||
$clock = new MockClock();
|
||||
|
||||
return new ReplaceMotiveCommandHandler($clock, $entityManager);
|
||||
if (null === $changeEmergencyStateCommandHandler) {
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class)->reveal();
|
||||
}
|
||||
|
||||
return new ReplaceMotiveCommandHandler($clock, $entityManager, $changeEmergencyStateCommandHandler);
|
||||
}
|
||||
|
||||
public function testHandleOnTicketWithoutMotive(): void
|
||||
@ -105,4 +113,69 @@ final class ReplaceMotiveCommandHandlerTest extends KernelTestCase
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
self::assertCount(1, $ticket->getMotiveHistories());
|
||||
}
|
||||
|
||||
public function testHandleUpdatesEmergencyStatusWhenMotiveHasMakeTicketEmergency(): void
|
||||
{
|
||||
// Create a motive with makeTicketEmergency set to YES
|
||||
$motive = new Motive();
|
||||
$motive->setMakeTicketEmergency(EmergencyStatusEnum::YES);
|
||||
|
||||
// Create a ticket with no emergency status
|
||||
$ticket = new Ticket();
|
||||
|
||||
// Mock the entity manager
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::type(MotiveHistory::class))->shouldBeCalled();
|
||||
|
||||
// Mock the ChangeEmergencyStateCommandHandler
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$changeEmergencyStateCommandHandler->__invoke(
|
||||
$ticket,
|
||||
Argument::that(fn (ChangeEmergencyStateCommand $command) => EmergencyStatusEnum::YES === $command->newEmergencyStatus)
|
||||
)->shouldBeCalled();
|
||||
|
||||
// Create the handler with our mocks
|
||||
$handler = $this->buildHandler(
|
||||
$entityManager->reveal(),
|
||||
$changeEmergencyStateCommandHandler->reveal()
|
||||
);
|
||||
|
||||
// Handle the command
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
// Assert that the motive was set on the ticket
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
}
|
||||
|
||||
public function testHandleDoesNotUpdateEmergencyStatusWhenMotiveHasNoMakeTicketEmergency(): void
|
||||
{
|
||||
// Create a motive with makeTicketEmergency set to null
|
||||
$motive = new Motive();
|
||||
$motive->setMakeTicketEmergency(null);
|
||||
|
||||
// Create a ticket with no emergency status
|
||||
$ticket = new Ticket();
|
||||
|
||||
// Mock the entity manager
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->persist(Argument::type(MotiveHistory::class))->shouldBeCalled();
|
||||
|
||||
// Mock the ChangeEmergencyStateCommandHandler - it should NOT be called
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$changeEmergencyStateCommandHandler->__invoke(
|
||||
Argument::cetera()
|
||||
)->shouldNotBeCalled();
|
||||
|
||||
// Create the handler with our mocks
|
||||
$handler = $this->buildHandler(
|
||||
$entityManager->reveal(),
|
||||
$changeEmergencyStateCommandHandler->reveal()
|
||||
);
|
||||
|
||||
// Handle the command
|
||||
$handler->handle($ticket, new ReplaceMotiveCommand($motive));
|
||||
|
||||
// Assert that the motive was set on the ticket
|
||||
self::assertSame($motive, $ticket->getMotive());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,144 @@
|
||||
<?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\Controller;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\ChangeEmergencyStateCommand;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Controller\ChangeEmergencyStateApiController;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Argument;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class ChangeEmergencyStateApiControllerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testSetEmergencyYesWithoutPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
|
||||
$controller = new ChangeEmergencyStateApiController(
|
||||
$changeEmergencyStateCommandHandler->reveal(),
|
||||
$security->reveal(),
|
||||
$entityManager->reveal(),
|
||||
$serializer->reveal(),
|
||||
);
|
||||
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
$controller->setEmergencyYes($ticket);
|
||||
}
|
||||
|
||||
public function testSetEmergencyYesWithPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$changeEmergencyStateCommandHandler->__invoke(
|
||||
$ticket,
|
||||
Argument::that(fn (ChangeEmergencyStateCommand $command) => EmergencyStatusEnum::YES === $command->newEmergencyStatus)
|
||||
)->willReturn($ticket)->shouldBeCalled();
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->serialize($ticket, 'json', ['groups' => ['read']])
|
||||
->willReturn('{}')
|
||||
->shouldBeCalled();
|
||||
|
||||
$controller = new ChangeEmergencyStateApiController(
|
||||
$changeEmergencyStateCommandHandler->reveal(),
|
||||
$security->reveal(),
|
||||
$entityManager->reveal(),
|
||||
$serializer->reveal(),
|
||||
);
|
||||
|
||||
$response = $controller->setEmergencyYes($ticket);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
}
|
||||
|
||||
public function testSetEmergencyNoWithoutPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(false);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
|
||||
$controller = new ChangeEmergencyStateApiController(
|
||||
$changeEmergencyStateCommandHandler->reveal(),
|
||||
$security->reveal(),
|
||||
$entityManager->reveal(),
|
||||
$serializer->reveal(),
|
||||
);
|
||||
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
$controller->setEmergencyNo($ticket);
|
||||
}
|
||||
|
||||
public function testSetEmergencyNoWithPermission(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted('ROLE_USER')->willReturn(true);
|
||||
|
||||
$changeEmergencyStateCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
$changeEmergencyStateCommandHandler->__invoke(
|
||||
$ticket,
|
||||
Argument::that(fn (ChangeEmergencyStateCommand $command) => EmergencyStatusEnum::NO === $command->newEmergencyStatus)
|
||||
)->willReturn($ticket)->shouldBeCalled();
|
||||
|
||||
$entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$serializer = $this->prophesize(SerializerInterface::class);
|
||||
$serializer->serialize($ticket, 'json', ['groups' => ['read']])
|
||||
->willReturn('{}')
|
||||
->shouldBeCalled();
|
||||
|
||||
$controller = new ChangeEmergencyStateApiController(
|
||||
$changeEmergencyStateCommandHandler->reveal(),
|
||||
$security->reveal(),
|
||||
$entityManager->reveal(),
|
||||
$serializer->reveal(),
|
||||
);
|
||||
|
||||
$response = $controller->setEmergencyNo($ticket);
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TicketBundle\Tests\Controller;
|
||||
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ChangeEmergencyStateCommandHandler;
|
||||
use Chill\TicketBundle\Action\Ticket\Handler\ReplaceMotiveCommandHandler;
|
||||
use Chill\TicketBundle\Controller\ReplaceMotiveController;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
@ -97,9 +98,12 @@ class ReplaceMotiveControllerTest extends KernelTestCase
|
||||
$entityManager->persist(Argument::type(MotiveHistory::class))->shouldBeCalled();
|
||||
$entityManager->flush()->shouldBeCalled();
|
||||
|
||||
$changeEmergencyCommandHandler = $this->prophesize(ChangeEmergencyStateCommandHandler::class);
|
||||
|
||||
$handler = new ReplaceMotiveCommandHandler(
|
||||
new MockClock(),
|
||||
$entityManager->reveal()
|
||||
$entityManager->reveal(),
|
||||
$changeEmergencyCommandHandler->reveal(),
|
||||
);
|
||||
|
||||
return new ReplaceMotiveController(
|
||||
|
@ -18,6 +18,8 @@ use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\StateEnum;
|
||||
use Chill\TicketBundle\Entity\StateHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
@ -133,4 +135,27 @@ class TicketTest extends KernelTestCase
|
||||
self::assertCount(2, $ticket->getStateHistories());
|
||||
self::assertSame(StateEnum::CLOSED, $ticket->getState());
|
||||
}
|
||||
|
||||
public function testGetEmergencyStatus(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
|
||||
// Initially, the ticket has no emergency status
|
||||
self::assertNull($ticket->getEmergencyStatus());
|
||||
|
||||
// Create an emergency status history entry with the YES status
|
||||
$history = new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket);
|
||||
|
||||
// Verify that the ticket now has the YES status
|
||||
self::assertSame(EmergencyStatusEnum::YES, $ticket->getEmergencyStatus());
|
||||
self::assertCount(1, $ticket->getEmergencyStatusHistories());
|
||||
|
||||
// Change the emergency status to NO
|
||||
$history->setEndDate(new \DateTimeImmutable());
|
||||
$history2 = new EmergencyStatusHistory(EmergencyStatusEnum::NO, $ticket);
|
||||
|
||||
// Verify that the ticket now has the NO status
|
||||
self::assertCount(2, $ticket->getEmergencyStatusHistories());
|
||||
self::assertSame(EmergencyStatusEnum::NO, $ticket->getEmergencyStatus());
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ use Chill\MainBundle\Entity\UserGroup;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Entity\AddresseeHistory;
|
||||
use Chill\TicketBundle\Entity\Comment;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusEnum;
|
||||
use Chill\TicketBundle\Entity\EmergencyStatusHistory;
|
||||
use Chill\TicketBundle\Entity\Motive;
|
||||
use Chill\TicketBundle\Entity\MotiveHistory;
|
||||
use Chill\TicketBundle\Entity\PersonHistory;
|
||||
@ -109,6 +111,7 @@ class TicketNormalizerTest extends KernelTestCase
|
||||
'currentState' => 'open',
|
||||
'updatedAt' => $t->getUpdatedAt()->getTimestamp(),
|
||||
'updatedBy' => ['user'],
|
||||
'emergency' => 'no',
|
||||
],
|
||||
];
|
||||
|
||||
@ -117,6 +120,7 @@ class TicketNormalizerTest extends KernelTestCase
|
||||
|
||||
// added by action
|
||||
new StateHistory(StateEnum::OPEN, $ticket, new \DateTimeImmutable('2024-06-16T00:00:00Z'));
|
||||
new EmergencyStatusHistory(EmergencyStatusEnum::YES, $ticket, new \DateTimeImmutable('2024-06-16T00:00:10Z'));
|
||||
|
||||
// those are added by doctrine listeners
|
||||
$ticket->setCreatedAt(new \DateTimeImmutable('2024-06-16T00:00:00Z'));
|
||||
@ -155,10 +159,12 @@ class TicketNormalizerTest extends KernelTestCase
|
||||
['event_type' => 'addressees_state'],
|
||||
['event_type' => 'create_ticket'],
|
||||
['event_type' => 'state_change'],
|
||||
['event_type' => 'emergency_change'],
|
||||
],
|
||||
'currentState' => 'open',
|
||||
'updatedAt' => $ticket->getUpdatedAt()->getTimestamp(),
|
||||
'updatedBy' => ['user'],
|
||||
'emergency' => 'yes',
|
||||
],
|
||||
];
|
||||
}
|
||||
@ -213,6 +219,11 @@ class TicketNormalizerTest extends KernelTestCase
|
||||
'json',
|
||||
['groups' => 'read']
|
||||
)->will(fn ($args): array => $args[0]);
|
||||
$normalizer->normalize(
|
||||
Argument::that(fn ($arg) => is_array($arg) && 1 === count($arg) && array_key_exists('new_emergency', $arg)),
|
||||
'json',
|
||||
['groups' => 'read']
|
||||
)->will(fn ($args): array => $args[0]);
|
||||
|
||||
// datetime
|
||||
$normalizer->normalize(Argument::type(\DateTimeImmutable::class), 'json', Argument::type('array'))
|
||||
|
Loading…
x
Reference in New Issue
Block a user