mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-24 03:04:22 +00:00
Add StateHistory
and StateEnum
entities to track ticket state changes
Integrated the `StateHistory` entity to manage state transitions in tickets and the `StateEnum` for defining state values (`open`, `closed`). Updated `Ticket` to handle associations with state histories and provide state management methods. Added migration for `state_history` table and extended `TicketTest` for state-related tests.
This commit is contained in:
parent
7633e587bb
commit
2b99a480ac
21
src/Bundle/ChillTicketBundle/src/Entity/StateEnum.php
Normal file
21
src/Bundle/ChillTicketBundle/src/Entity/StateEnum.php
Normal file
@ -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 state of a ticket (open or closed).
|
||||
*/
|
||||
enum StateEnum: string
|
||||
{
|
||||
case OPEN = 'open';
|
||||
case CLOSED = 'closed';
|
||||
}
|
85
src/Bundle/ChillTicketBundle/src/Entity/StateHistory.php
Normal file
85
src/Bundle/ChillTicketBundle/src/Entity/StateHistory.php
Normal file
@ -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 a state associated with a ticket.
|
||||
*
|
||||
* This entity is used to track the changes in a ticket's state over time.
|
||||
* Implements the TrackCreationInterface for tracking entity lifecycle creation.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'state_history', schema: 'chill_ticket')]
|
||||
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_state_history' => StateHistory::class])]
|
||||
class StateHistory 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: StateEnum::class)]
|
||||
#[Serializer\Groups(['read'])]
|
||||
private StateEnum $state,
|
||||
#[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->addStateHistory($this);
|
||||
}
|
||||
|
||||
public function getEndDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->endDate;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getState(): StateEnum
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function getStartDate(): \DateTimeImmutable
|
||||
{
|
||||
return $this->startDate;
|
||||
}
|
||||
|
||||
public function getTicket(): Ticket
|
||||
{
|
||||
return $this->ticket;
|
||||
}
|
||||
|
||||
public function setEndDate(?\DateTimeImmutable $endDate): void
|
||||
{
|
||||
$this->endDate = $endDate;
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
* - association between the ticket and motive: @see{MotiveHistory};
|
||||
* - association between the ticket and addresses: @see{AddresseeHistory};
|
||||
* - association between the ticket and input: @see{InputHistory};
|
||||
* - association between the ticket and state: @see{StateHistory};
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'ticket', schema: 'chill_ticket')]
|
||||
@ -83,6 +84,12 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
#[ORM\OneToMany(targetEntity: PersonHistory::class, mappedBy: 'ticket')]
|
||||
private Collection $personHistories;
|
||||
|
||||
/**
|
||||
* @var Collection<int, StateHistory>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: StateHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||
private Collection $stateHistories;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresseeHistory = new ArrayCollection();
|
||||
@ -90,6 +97,7 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
$this->motiveHistories = new ArrayCollection();
|
||||
$this->personHistories = new ArrayCollection();
|
||||
$this->inputHistories = new ArrayCollection();
|
||||
$this->stateHistories = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@ -225,4 +233,31 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
return $this->addresseeHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal use @see{StateHistory::__construct} instead
|
||||
*/
|
||||
public function addStateHistory(StateHistory $stateHistory): void
|
||||
{
|
||||
$this->stateHistories->add($stateHistory);
|
||||
}
|
||||
|
||||
public function getState(): ?StateEnum
|
||||
{
|
||||
foreach ($this->stateHistories as $stateHistory) {
|
||||
if (null === $stateHistory->getEndDate()) {
|
||||
return $stateHistory->getState();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ReadableCollection<int, StateHistory>
|
||||
*/
|
||||
public function getStateHistories(): ReadableCollection
|
||||
{
|
||||
return $this->stateHistories;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\Migrations\Ticket;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20250603085035 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add a state to tickets';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE SEQUENCE chill_ticket.state_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE chill_ticket.state_history (id INT NOT NULL, ticket_id INT NOT NULL,
|
||||
endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, state VARCHAR(255) NOT NULL,
|
||||
startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
|
||||
createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_B1DCD379700047D2 ON chill_ticket.state_history (ticket_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_B1DCD3793174800F ON chill_ticket.state_history (createdBy_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
COMMENT ON COLUMN chill_ticket.state_history.endDate IS '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
COMMENT ON COLUMN chill_ticket.state_history.startDate IS '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
COMMENT ON COLUMN chill_ticket.state_history.createdAt IS '(DC2Type:datetime_immutable)'
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.state_history ADD CONSTRAINT FK_B1DCD379700047D2 FOREIGN KEY (ticket_id)
|
||||
REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.state_history ADD CONSTRAINT FK_B1DCD3793174800F FOREIGN KEY (createdBy_id)
|
||||
REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.state_history ADD CONSTRAINT ticket_state_not_overlaps
|
||||
exclude using gist (ticket_id with =, tsrange(startdate, enddate) with &&)
|
||||
deferrable initially deferred
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
INSERT INTO chill_ticket.state_history (id, ticket_id, state, startDate, createdAt, createdBy_id)
|
||||
SELECT nextval('chill_ticket.state_history_id_seq'), id, 'open', createdAt, createdAt, createdBy_id
|
||||
FROM chill_ticket.ticket
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP SEQUENCE chill_ticket.state_history_id_seq CASCADE
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.state_history DROP CONSTRAINT FK_B1DCD379700047D2
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.state_history DROP CONSTRAINT FK_B1DCD3793174800F
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
ALTER TABLE chill_ticket.state_history DROP CONSTRAINT ticket_state_not_overlaps
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE chill_ticket.state_history
|
||||
SQL);
|
||||
}
|
||||
}
|
@ -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\StateEnum;
|
||||
use Chill\TicketBundle\Entity\StateHistory;
|
||||
use Chill\TicketBundle\Entity\Ticket;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
@ -108,4 +110,29 @@ class TicketTest extends KernelTestCase
|
||||
// Verify that getExternalRef returns the updated value
|
||||
self::assertSame($newExternalRef, $ticket->getExternalRef());
|
||||
}
|
||||
|
||||
public function testGetState(): void
|
||||
{
|
||||
$ticket = new Ticket();
|
||||
$openState = new StateEnum(StateEnum::OPEN, ['en' => 'Open', 'fr' => 'Ouvert']);
|
||||
|
||||
// Initially, the ticket has no state
|
||||
self::assertNull($ticket->getState());
|
||||
|
||||
// Create a state history entry with the open state
|
||||
$history = new StateHistory($openState, $ticket);
|
||||
|
||||
// Verify that the ticket now has the open state
|
||||
self::assertSame($openState, $ticket->getState());
|
||||
self::assertCount(1, $ticket->getStateHistories());
|
||||
|
||||
// Change the state to closed
|
||||
$closedState = new StateEnum(StateEnum::CLOSED, ['en' => 'Closed', 'fr' => 'Fermé']);
|
||||
$history->setEndDate(new \DateTimeImmutable());
|
||||
$history2 = new StateHistory($closedState, $ticket);
|
||||
|
||||
// Verify that the ticket now has the closed state
|
||||
self::assertCount(2, $ticket->getStateHistories());
|
||||
self::assertSame($closedState, $ticket->getState());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user