Add SetCallerCommand and handler with tests to manage ticket caller changes

This commit is contained in:
Julien Fastré 2025-06-24 15:06:11 +02:00
parent df745a1c6a
commit de18f4e3aa
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 257 additions and 0 deletions

View File

@ -0,0 +1,50 @@
<?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\SetCallerCommand;
use Chill\TicketBundle\Entity\CallerHistory;
use Chill\TicketBundle\Entity\Ticket;
use Symfony\Component\Clock\ClockInterface;
/**
* Handler for setting the caller of a ticket.
*/
class SetCallerCommandHandler
{
public function __construct(private readonly ClockInterface $clock) {}
public function __invoke(Ticket $ticket, SetCallerCommand $command): Ticket
{
// If the ticket already has the requested caller, return it without changes
$currentCaller = $ticket->getCaller();
if ($currentCaller === $command->caller) {
return $ticket;
}
// End the current caller history (if any)
foreach ($ticket->getCallerHistories() as $callerHistory) {
if (null === $callerHistory->getEndDate()) {
$callerHistory->setEndDate($this->clock->now());
}
}
// Create a new caller history with the new caller
new CallerHistory(
$command->caller,
$ticket,
$this->clock->now(),
);
return $ticket;
}
}

View File

@ -0,0 +1,29 @@
<?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\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
/**
* Command to set the caller of a ticket.
* The caller can be either a Person or a ThirdParty.
*/
final readonly class SetCallerCommand
{
/**
* @param Person|ThirdParty|null $caller The caller to associate with the ticket
*/
public function __construct(
public Person|ThirdParty|null $caller,
) {}
}

View File

@ -0,0 +1,178 @@
<?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\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\TicketBundle\Action\Ticket\Handler\SetCallerCommandHandler;
use Chill\TicketBundle\Action\Ticket\SetCallerCommand;
use Chill\TicketBundle\Entity\CallerHistory;
use Chill\TicketBundle\Entity\Ticket;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Clock\MockClock;
/**
* @internal
*
* @coversNothing
*/
class SetCallerCommandHandlerTest extends TestCase
{
use ProphecyTrait;
private \DateTimeImmutable $now;
private ClockInterface $clock;
private SetCallerCommandHandler $handler;
protected function setUp(): void
{
$this->now = new \DateTimeImmutable('2023-01-01 12:00:00');
$this->clock = new MockClock($this->now);
$this->handler = new SetCallerCommandHandler($this->clock);
}
public function testSetPersonAsCaller(): void
{
// Arrange
$ticket = new Ticket();
$person = new Person();
$command = new SetCallerCommand($person);
// Act
$result = ($this->handler)($ticket, $command);
// Assert
self::assertSame($ticket, $result);
self::assertSame($person, $ticket->getCaller());
self::assertCount(1, $ticket->getCallerHistories());
$callerHistory = $ticket->getCallerHistories()->first();
self::assertInstanceOf(CallerHistory::class, $callerHistory);
self::assertSame($person, $callerHistory->getPerson());
self::assertNull($callerHistory->getThirdParty());
self::assertEquals($this->now->getTimestamp(), $callerHistory->getStartDate()->getTimestamp());
self::assertNull($callerHistory->getEndDate());
}
public function testSetThirdPartyAsCaller(): void
{
// Arrange
$ticket = new Ticket();
$thirdParty = new ThirdParty();
$command = new SetCallerCommand($thirdParty);
// Act
$result = ($this->handler)($ticket, $command);
// Assert
self::assertSame($ticket, $result);
self::assertSame($thirdParty, $ticket->getCaller());
self::assertCount(1, $ticket->getCallerHistories());
$callerHistory = $ticket->getCallerHistories()->first();
self::assertInstanceOf(CallerHistory::class, $callerHistory);
self::assertNull($callerHistory->getPerson());
self::assertSame($thirdParty, $callerHistory->getThirdParty());
self::assertEquals($this->now->getTimestamp(), $callerHistory->getStartDate()->getTimestamp());
self::assertNull($callerHistory->getEndDate());
}
public function testChangeCallerFromPersonToThirdParty(): void
{
// Arrange
$ticket = new Ticket();
$person = new Person();
$thirdParty = new ThirdParty();
// Set initial person caller
$initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
$initialCallerHistory->setPerson($person);
// Create command to change to third party
$command = new SetCallerCommand($thirdParty);
// Act
$this->clock->modify('+ 10 minutes');
$result = ($this->handler)($ticket, $command);
// Assert
self::assertSame($ticket, $result);
self::assertSame($thirdParty, $ticket->getCaller());
self::assertCount(2, $ticket->getCallerHistories());
// Check that the first history is ended
$firstCallerHistory = $ticket->getCallerHistories()->first();
self::assertSame($person, $firstCallerHistory->getPerson());
self::assertEquals($this->clock->now()->getTimestamp(), $firstCallerHistory->getEndDate()->getTimestamp());
// Check that the new history is created correctly
$lastCallerHistory = $ticket->getCallerHistories()->last();
self::assertNull($lastCallerHistory->getPerson());
self::assertSame($thirdParty, $lastCallerHistory->getThirdParty());
self::assertSame($this->clock->now()->getTimestamp(), $lastCallerHistory->getStartDate()->getTimestamp());
self::assertNull($lastCallerHistory->getEndDate());
}
public function testRemoveCaller(): void
{
// Arrange
$ticket = new Ticket();
$person = new Person();
// Set initial person caller
$initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
// Create command to remove caller
$command = new SetCallerCommand(null);
// Act
$result = ($this->handler)($ticket, $command);
// Assert
self::assertSame($ticket, $result);
self::assertNull($ticket->getCaller());
self::assertCount(2, $ticket->getCallerHistories());
// Check that the history is ended
$callerHistory = $ticket->getCallerHistories()->first();
self::assertSame($person, $callerHistory->getPerson());
self::assertEquals($this->now->getTimestamp(), $callerHistory->getEndDate()->getTimestamp());
}
public function testNoChangeWhenCallerIsAlreadySet(): void
{
// Arrange
$ticket = new Ticket();
$person = new Person();
// Set initial person caller
$initialCallerHistory = new CallerHistory($person, $ticket, $this->now);
// Create command with the same person
$command = new SetCallerCommand($person);
// Act
$result = ($this->handler)($ticket, $command);
// Assert
self::assertSame($ticket, $result);
self::assertSame($person, $ticket->getCaller());
self::assertCount(1, $ticket->getCallerHistories());
// Check that the history is unchanged
$callerHistory = $ticket->getCallerHistories()->first();
self::assertSame($person, $callerHistory->getPerson());
self::assertNull($callerHistory->getEndDate());
}
}