feat: soft-delete for teamer accounts
addresses #869dv9br3
This commit is contained in:
@@ -33,6 +33,10 @@ class TeamerFilterHandler extends AbstractFilterHandler
|
||||
$filterDto->setIncludeInactive((bool) $data['include_inactive']);
|
||||
}
|
||||
|
||||
if (isset($data['include_deleted'])) {
|
||||
$filterDto->setIncludeDeleted((bool) $data['include_deleted']);
|
||||
}
|
||||
|
||||
if (0 < count($data['job_profiles'])) {
|
||||
$jobProfiles = $this
|
||||
->entityManager
|
||||
@@ -57,6 +61,7 @@ class TeamerFilterHandler extends AbstractFilterHandler
|
||||
'driver_license_verified' => $filterDto->hasDriverLicenseVerified(),
|
||||
'no_trainings' => $filterDto->hasNoTrainings(),
|
||||
'include_inactive' => $filterDto->isIncludeInactive(),
|
||||
'include_deleted' => $filterDto->isIncludeDeleted(),
|
||||
'job_profiles' => array_map(function (JobProfile $jobProfile) {
|
||||
return $jobProfile->getId();
|
||||
}, $filterDto->getJobProfiles()),
|
||||
|
||||
@@ -30,7 +30,9 @@ class DispositionReminderService
|
||||
->innerJoin('disposition.assignment', 'assignment')
|
||||
->innerJoin('assignment.destination', 'destination')
|
||||
->innerJoin('disposition.documents', 'document', Join::WITH, 'document.type = :documentType')
|
||||
->innerJoin('disposition.teamer', 'teamer')
|
||||
->where($qb->expr()->andX(
|
||||
$qb->expr()->isNull('teamer.deletedAt'),
|
||||
$qb->expr()->notIn('assignment.status', ':statusAssignment'),
|
||||
$qb->expr()->eq('destination.dateFrom', ':dateFrom'),
|
||||
$qb->expr()->eq('document.status', ':statusContract'),
|
||||
|
||||
@@ -35,8 +35,12 @@ class UploadReminderService
|
||||
->innerJoin('disposition.assignment', 'assignment')
|
||||
->innerJoin('assignment.destination', 'destination')
|
||||
->leftJoin('disposition.documents', 'document', Join::WITH, 'document.type = :documentType')
|
||||
->innerJoin('disposition.teamer', 'teamer')
|
||||
->where($qb->expr()->andX(
|
||||
$qb->expr()->isNull('document'),
|
||||
// deleted teamers are excluded here rather than when sending, so that the
|
||||
// count reported below stays truthful
|
||||
$qb->expr()->isNull('teamer.deletedAt'),
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('document.createdAt', ':contractDueDateFirst'),
|
||||
$qb->expr()->eq('document.createdAt', ':contractDueDateSecond'),
|
||||
@@ -90,8 +94,10 @@ class UploadReminderService
|
||||
->innerJoin('disposition.assignment', 'assignment')
|
||||
->innerJoin('assignment.destination', 'destination')
|
||||
->leftJoin('disposition.documents', 'document', Join::WITH, 'document.type = :documentType')
|
||||
->innerJoin('disposition.teamer', 'teamer')
|
||||
->where($qb->expr()->andX(
|
||||
$qb->expr()->isNull('document'),
|
||||
$qb->expr()->isNull('teamer.deletedAt'),
|
||||
$qb->expr()->orX(
|
||||
// Due date can be determined by assignment's date which potentially overrides destination date
|
||||
$qb->expr()->andX(
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\Teamer;
|
||||
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Soft-deletes a teamer together with their user account, and restores them again.
|
||||
*
|
||||
* A deletion excludes the person from every forward-looking process - the teamer list,
|
||||
* autocompletes, forms, mailings and cron reminders - while every existing record stays
|
||||
* untouched and visible: dispositions, applications, documents, feedback and the
|
||||
* contracts and invoices rendered from them are business records that have to survive.
|
||||
*
|
||||
* Both Teamer and User carry their own flag, because neither side is guaranteed to
|
||||
* exist and the queries that have to filter only ever have one of the two aliases at
|
||||
* hand. This class is the only place that writes either flag, so the two cannot drift
|
||||
* apart.
|
||||
*
|
||||
* Deletion is deliberately independent of the block expressed by User::$disabledAt: a
|
||||
* disciplinary block has to survive a deletion and the restore that follows it.
|
||||
*/
|
||||
class AccountDeletionHandler
|
||||
{
|
||||
public const SOURCE_ADMIN = 'admin';
|
||||
public const SOURCE_SELF = 'self';
|
||||
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function delete(Teamer|User $subject, string $source): void
|
||||
{
|
||||
[$teamer, $user] = $this->resolvePair($subject);
|
||||
|
||||
if (false === $this->isDeleted($teamer, $user)) {
|
||||
$teamer?->setDeleted();
|
||||
$user?->setDeleted();
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->logger->info('Delete account', [...$this->logContext($teamer, $user), 'source' => $source]);
|
||||
}
|
||||
}
|
||||
|
||||
public function restore(Teamer|User $subject): void
|
||||
{
|
||||
[$teamer, $user] = $this->resolvePair($subject);
|
||||
|
||||
if (true === $this->isDeleted($teamer, $user)) {
|
||||
$teamer?->setRestored();
|
||||
$user?->setRestored();
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->logger->info('Restore account', $this->logContext($teamer, $user));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: ?Teamer, 1: ?User}
|
||||
*/
|
||||
private function resolvePair(Teamer|User $subject): array
|
||||
{
|
||||
if ($subject instanceof Teamer) {
|
||||
return [$subject, $subject->getUser()];
|
||||
}
|
||||
|
||||
return [$subject->getTeamer(), $subject];
|
||||
}
|
||||
|
||||
/**
|
||||
* Either side being flagged counts as deleted, so that a pair left inconsistent by
|
||||
* an earlier failure is repaired rather than skipped.
|
||||
*/
|
||||
private function isDeleted(?Teamer $teamer, ?User $user): bool
|
||||
{
|
||||
return true === $teamer?->isDeleted() || true === $user?->isDeleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function logContext(?Teamer $teamer, ?User $user): array
|
||||
{
|
||||
return [
|
||||
'teamer' => $teamer?->getFullName(),
|
||||
'teamer_id' => $teamer?->getId(),
|
||||
'user' => $user?->getEmail(),
|
||||
'user_id' => $user?->getId(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user