mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-07-01 14:36:13 +00:00
Add CallerHistory entity to track ticket caller details and history
This commit is contained in:
parent
0566ab0910
commit
0cf922be64
149
src/Bundle/ChillTicketBundle/src/Entity/CallerHistory.php
Normal file
149
src/Bundle/ChillTicketBundle/src/Entity/CallerHistory.php
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<?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 Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the history of a caller associated with a ticket.
|
||||||
|
*
|
||||||
|
* This entity is used to track the changes in a ticket's caller over time.
|
||||||
|
* The caller can be either a Person or a ThirdParty.
|
||||||
|
* Implements the TrackCreationInterface for tracking entity lifecycle creation.
|
||||||
|
*/
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'caller_history', schema: 'chill_ticket')]
|
||||||
|
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['ticket_caller_history' => CallerHistory::class])]
|
||||||
|
class CallerHistory 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;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: Person::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private ?Person $person = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: ThirdParty::class)]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
#[Serializer\Groups(['read'])]
|
||||||
|
private ?ThirdParty $thirdParty = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ThirdParty|Person $caller,
|
||||||
|
#[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'),
|
||||||
|
) {
|
||||||
|
$this->setCaller($caller);
|
||||||
|
$ticket->addCallerHistory($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEndDate(): ?\DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPerson(): ?Person
|
||||||
|
{
|
||||||
|
return $this->person;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getThirdParty(): ?ThirdParty
|
||||||
|
{
|
||||||
|
return $this->thirdParty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStartDate(): \DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->startDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTicket(): Ticket
|
||||||
|
{
|
||||||
|
return $this->ticket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setEndDate(?\DateTimeImmutable $endDate): void
|
||||||
|
{
|
||||||
|
$this->endDate = $endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPerson(?Person $person): self
|
||||||
|
{
|
||||||
|
$this->person = $person;
|
||||||
|
|
||||||
|
// If setting a person, ensure thirdParty is null
|
||||||
|
if (null !== $person) {
|
||||||
|
$this->thirdParty = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setThirdParty(?ThirdParty $thirdParty): self
|
||||||
|
{
|
||||||
|
$this->thirdParty = $thirdParty;
|
||||||
|
|
||||||
|
// If setting a thirdParty, ensure person is null
|
||||||
|
if (null !== $thirdParty) {
|
||||||
|
$this->person = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the caller.
|
||||||
|
*
|
||||||
|
* This is a private method and should be only called while instance creation
|
||||||
|
*/
|
||||||
|
private function setCaller(Person|ThirdParty $caller): void
|
||||||
|
{
|
||||||
|
if ($caller instanceof Person) {
|
||||||
|
$this->setPerson($caller);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->setThirdParty($caller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the caller, which can be either a Person or a ThirdParty.
|
||||||
|
*/
|
||||||
|
public function getCaller(): Person|ThirdParty
|
||||||
|
{
|
||||||
|
return $this->person ?? $this->thirdParty;
|
||||||
|
}
|
||||||
|
}
|
@ -96,6 +96,12 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
#[ORM\OneToMany(targetEntity: EmergencyStatusHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
#[ORM\OneToMany(targetEntity: EmergencyStatusHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||||
private Collection $emergencyStatusHistories;
|
private Collection $emergencyStatusHistories;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, CallerHistory>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: CallerHistory::class, mappedBy: 'ticket', cascade: ['persist', 'remove'])]
|
||||||
|
private Collection $callerHistories;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->addresseeHistory = new ArrayCollection();
|
$this->addresseeHistory = new ArrayCollection();
|
||||||
@ -105,6 +111,7 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
$this->inputHistories = new ArrayCollection();
|
$this->inputHistories = new ArrayCollection();
|
||||||
$this->stateHistories = new ArrayCollection();
|
$this->stateHistories = new ArrayCollection();
|
||||||
$this->emergencyStatusHistories = new ArrayCollection();
|
$this->emergencyStatusHistories = new ArrayCollection();
|
||||||
|
$this->callerHistories = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@ -294,4 +301,36 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
{
|
{
|
||||||
return $this->emergencyStatusHistories;
|
return $this->emergencyStatusHistories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @see{CallerHistory::__construct} instead
|
||||||
|
*/
|
||||||
|
public function addCallerHistory(CallerHistory $callerHistory): void
|
||||||
|
{
|
||||||
|
$this->callerHistories->add($callerHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current caller (Person or ThirdParty) associated with this ticket.
|
||||||
|
*
|
||||||
|
* @return Person|ThirdParty|null
|
||||||
|
*/
|
||||||
|
public function getCaller()
|
||||||
|
{
|
||||||
|
foreach ($this->callerHistories as $callerHistory) {
|
||||||
|
if (null === $callerHistory->getEndDate()) {
|
||||||
|
return $callerHistory->getCaller();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ReadableCollection<int, CallerHistory>
|
||||||
|
*/
|
||||||
|
public function getCallerHistories(): ReadableCollection
|
||||||
|
{
|
||||||
|
return $this->callerHistories;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
<?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 Version20250624105842 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add CallerHistory entity to associate a ticket with either a Person or a ThirdParty entity';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE SEQUENCE chill_ticket.caller_history_id_seq INCREMENT BY 1 MINVALUE 1 START 1
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE chill_ticket.caller_history (id INT NOT NULL, person_id INT DEFAULT NULL, ticket_id INT NOT NULL, endDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, startDate TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, thirdParty_id INT DEFAULT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE24217BBB47 ON chill_ticket.caller_history (person_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE243EA5CAB0 ON chill_ticket.caller_history (thirdParty_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE24700047D2 ON chill_ticket.caller_history (ticket_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_AD0DCE243174800F ON chill_ticket.caller_history (createdBy_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
COMMENT ON COLUMN chill_ticket.caller_history.endDate IS '(DC2Type:datetime_immutable)'
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
COMMENT ON COLUMN chill_ticket.caller_history.startDate IS '(DC2Type:datetime_immutable)'
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
COMMENT ON COLUMN chill_ticket.caller_history.createdAt IS '(DC2Type:datetime_immutable)'
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE24217BBB47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE243EA5CAB0 FOREIGN KEY (thirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE24700047D2 FOREIGN KEY (ticket_id) REFERENCES chill_ticket.ticket (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT FK_AD0DCE243174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history ADD CONSTRAINT caller_history_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'
|
||||||
|
DROP SEQUENCE chill_ticket.caller_history_id_seq CASCADE
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE24217BBB47
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE243EA5CAB0
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE24700047D2
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
ALTER TABLE chill_ticket.caller_history DROP CONSTRAINT FK_AD0DCE243174800F
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE chill_ticket.caller_history
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
<?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\Entity;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class CallerHistoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testConstructorWithPerson(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
$callerHistory = new CallerHistory($person = new Person(), $ticket);
|
||||||
|
|
||||||
|
self::assertSame($ticket, $callerHistory->getTicket());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
self::assertSame($person, $callerHistory->getPerson());
|
||||||
|
self::assertNull($callerHistory->getThirdParty());
|
||||||
|
self::assertSame($person, $callerHistory->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConstructorWithThirdParty(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
$callerHistory = new CallerHistory($thirdParty = new ThirdParty(), $ticket);
|
||||||
|
|
||||||
|
self::assertSame($ticket, $callerHistory->getTicket());
|
||||||
|
self::assertNull($callerHistory->getEndDate());
|
||||||
|
self::assertNull($callerHistory->getPerson());
|
||||||
|
self::assertSame($thirdParty, $callerHistory->getThirdParty());
|
||||||
|
self::assertSame($thirdParty, $callerHistory->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetEndDate(): void
|
||||||
|
{
|
||||||
|
$ticket = $this->createMock(Ticket::class);
|
||||||
|
$callerHistory = new CallerHistory(new ThirdParty(), $ticket);
|
||||||
|
|
||||||
|
$endDate = new \DateTimeImmutable('2023-01-01');
|
||||||
|
$callerHistory->setEndDate($endDate);
|
||||||
|
|
||||||
|
self::assertSame($endDate, $callerHistory->getEndDate());
|
||||||
|
}
|
||||||
|
}
|
@ -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\TicketBundle\Tests\Entity;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||||
|
use Chill\TicketBundle\Entity\CallerHistory;
|
||||||
|
use Chill\TicketBundle\Entity\Ticket;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class TicketCallerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetCaller(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
// Initially, there should be no caller
|
||||||
|
self::assertNull($ticket->getCaller());
|
||||||
|
|
||||||
|
// Create a person
|
||||||
|
$person = new Person();
|
||||||
|
|
||||||
|
// Create a caller history with the person
|
||||||
|
$callerHistory = new CallerHistory($person, $ticket);
|
||||||
|
|
||||||
|
// The ticket should now return the person as the caller
|
||||||
|
self::assertSame($person, $ticket->getCaller());
|
||||||
|
|
||||||
|
// Create a third party
|
||||||
|
$thirdParty = new ThirdParty();
|
||||||
|
|
||||||
|
// Create a new caller history with the third party
|
||||||
|
$callerHistory2 = new CallerHistory($thirdParty, $ticket);
|
||||||
|
|
||||||
|
// End the first caller history
|
||||||
|
$callerHistory->setEndDate(new \DateTimeImmutable());
|
||||||
|
|
||||||
|
// The ticket should now return the third party as the caller
|
||||||
|
self::assertSame($thirdParty, $ticket->getCaller());
|
||||||
|
|
||||||
|
// End the second caller history
|
||||||
|
$callerHistory2->setEndDate(new \DateTimeImmutable());
|
||||||
|
|
||||||
|
// The ticket should now return null as there is no active caller
|
||||||
|
self::assertNull($ticket->getCaller());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCallerHistories(): void
|
||||||
|
{
|
||||||
|
$ticket = new Ticket();
|
||||||
|
|
||||||
|
// Initially, there should be no caller histories
|
||||||
|
self::assertCount(0, $ticket->getCallerHistories());
|
||||||
|
|
||||||
|
// Create a caller history
|
||||||
|
$callerHistory = new CallerHistory(new Person(), $ticket);
|
||||||
|
|
||||||
|
// The ticket should now have one caller history
|
||||||
|
self::assertCount(1, $ticket->getCallerHistories());
|
||||||
|
self::assertSame($callerHistory, $ticket->getCallerHistories()->first());
|
||||||
|
|
||||||
|
// Create another caller history
|
||||||
|
$callerHistory2 = new CallerHistory(new ThirdParty(), $ticket);
|
||||||
|
|
||||||
|
// The ticket should now have two caller histories
|
||||||
|
self::assertCount(2, $ticket->getCallerHistories());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user