feat: record application deleted event for statistics

This commit is contained in:
2026-09-08 14:11:30 +02:00
parent 3d4d92b5c4
commit 66527515ef
4 changed files with 231 additions and 0 deletions
@@ -6,13 +6,16 @@ namespace App\Tests\EventListener;
use App\Entity\Application;
use App\Entity\Assignment;
use App\Entity\Destination;
use App\Entity\Disposition;
use App\Entity\JobProfile;
use App\Entity\Teamer;
use App\Enum\ApplicationDeletionOutcome;
use App\Enum\CallOffScope;
use App\Enum\StatisticsEventName;
use App\EventListener\StatisticsChangeSetListener;
use App\Service\Statistics\Collector\ApplicationCreatedCollector;
use App\Service\Statistics\Collector\ApplicationDeletedCollector;
use App\Service\Statistics\Collector\CallOffCollector;
use App\Service\Statistics\Collector\DispositionDeletedCollector;
use App\Service\Statistics\Collector\JobProfileChangeCollector;
@@ -345,6 +348,111 @@ class StatisticsChangeSetListenerTest extends TestCase
$this->flush([[$this->disposition(), ['remarks' => ['alt', 'neu']]]]);
}
/**
* A teamer withdrawing leaves nothing behind - the application row is hard deleted - so
* this is the only trace that they applied and then thought better of it.
*/
public function testRecordsAWithdrawnApplicationAsALoss(): void
{
$application = new Application(new Assignment(), new Teamer());
$application->setStatus(Application::STATUS_PENDING);
$this->recorder
->expects($this->once())
->method('record')
->with(
StatisticsEventName::APPLICATION_DELETED,
$this->anything(),
$this->callback(static function (array $payload) use ($application): bool {
return ApplicationDeletionOutcome::REMOVED->value === $payload['outcome']
&& Application::STATUS_PENDING === $payload['previous_status']
&& $application->getUuid() === $payload['application_uuid']
&& null === $payload['disposition_uuid'];
})
)
;
$this->flush([], [], [$application]);
}
/**
* Staffing somebody deletes their application too: the dispose controller persists the
* placement and removes the application in one flush. Counting that as a lost application
* would turn every success into a failure.
*/
public function testAnApplicationTurnedIntoAPlacementIsNotALoss(): void
{
$assignment = new Assignment();
$teamer = new Teamer();
$application = new Application($assignment, $teamer);
$disposition = new Disposition($application);
$this->recorder
->expects($this->once())
->method('record')
->with(
StatisticsEventName::APPLICATION_DELETED,
$this->anything(),
$this->callback(static function (array $payload) use ($disposition): bool {
return ApplicationDeletionOutcome::DISPOSED->value === $payload['outcome']
&& $disposition->getUuid() === $payload['disposition_uuid'];
})
)
;
$this->flush([], [$disposition], [$application]);
}
/**
* Two unrelated things happening in one flush do not make one the cause of the other. The
* application still counts as lost.
*/
public function testAPlacementForSomebodyElseDoesNotExcuseTheDeletion(): void
{
$assignment = new Assignment();
$application = new Application($assignment, new Teamer());
$disposition = new Disposition(new Application($assignment, new Teamer()));
$this->recorder
->expects($this->once())
->method('record')
->with(
StatisticsEventName::APPLICATION_DELETED,
$this->anything(),
$this->callback(static function (array $payload): bool {
return ApplicationDeletionOutcome::REMOVED->value === $payload['outcome']
&& null === $payload['disposition_uuid'];
})
)
;
$this->flush([], [$disposition], [$application]);
}
/**
* The dimensions have to be frozen while the entity is still whole. Going through
* recordForApplication() instead would resolve them in postFlush, by which time Doctrine
* has detached the application and nulled its id, and the row would name nothing.
*/
public function testADeletedApplicationCarriesDimensionsFrozenDuringTheFlush(): void
{
$assignment = (new Assignment())->setDestination((new Destination())->setHotelCode('SERZIL'));
$this->recorder->expects($this->never())->method('recordForApplication');
$this->recorder
->expects($this->once())
->method('record')
->with(
StatisticsEventName::APPLICATION_DELETED,
$this->callback(static fn (array $dimensions): bool => 'ZIL' === $dimensions['hotel_code']),
$this->anything()
)
;
$this->flush([], [], [new Application($assignment, new Teamer())]);
}
/**
* Editing an application later is not a second application.
*/
@@ -423,6 +531,7 @@ class StatisticsChangeSetListenerTest extends TestCase
new CallOffCollector(),
new DispositionDeletedCollector(),
new ApplicationCreatedCollector(),
new ApplicationDeletedCollector(),
]);
}