feat: forced teamer data verification after configured due dates
addresses #869b33c7p
This commit is contained in:
@@ -75,3 +75,5 @@ FEATURE_SANITIZE_UPLOADS=false
|
||||
FEATURE_STAMP_INVOICES=false
|
||||
|
||||
UPLOAD_REPLACEMENT_FILE=assets/pdf/chicken.pdf
|
||||
|
||||
PERSONAL_DATA_CHECK_DEADLINES=04-01,10-01
|
||||
|
||||
@@ -160,12 +160,15 @@ services:
|
||||
$datevEmailRecipient: '%env(DATEV_EMAIL_RECIPIENT)%'
|
||||
$datevEmailSender: '%env(DATEV_EMAIL_SENDER)%'
|
||||
|
||||
App\Security\RequiredCheck\RequiredTeamerCheckRegistry:
|
||||
App\RequiredTeamerCheck\RequiredTeamerCheckRegistry:
|
||||
arguments:
|
||||
$checks: !tagged_iterator app.required_teamer_check
|
||||
$checks:
|
||||
- '@App\RequiredTeamerCheck\DriverLicenseRequiredCheck'
|
||||
- '@App\RequiredTeamerCheck\PersonalDataVerificationRequiredCheck'
|
||||
|
||||
App\Security\RequiredCheck\DriverLicenseRequiredCheck:
|
||||
tags: [ 'app.required_teamer_check' ]
|
||||
App\RequiredTeamerCheck\PersonalDataVerificationRequiredCheck:
|
||||
arguments:
|
||||
$dataVerificationDeadlines: '%env(csv:PERSONAL_DATA_CHECK_DEADLINES)%'
|
||||
|
||||
app.upload_namer:
|
||||
class: App\Service\Upload\UploadNamer
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260218173855 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql("ALTER TABLE teamer ADD data_verified_at DATETIME DEFAULT NULL COMMENT '(DC2Type:datetime_immutable)'");
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE teamer DROP data_verified_at');
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use App\BusProNet\Model\BaseDataResponse;
|
||||
use App\BusProNet\Model\CrmAttributesResponse;
|
||||
use App\BusProNet\Model\NotificationResponse;
|
||||
use App\BusProNet\Model\ProfileResponse;
|
||||
use App\BusProNet\Model\ProfileUpdateResponse;
|
||||
use App\Entity\User;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use League\Flysystem\FilesystemOperator;
|
||||
@@ -60,7 +61,7 @@ class ApiClient
|
||||
* @throws ApiClientException
|
||||
* @throws ResponseParserException
|
||||
*/
|
||||
public function updateProfile(User $user, string $password): NotificationResponse|ProfileResponse
|
||||
public function updateProfile(User $user, string $password): NotificationResponse|ProfileUpdateResponse
|
||||
{
|
||||
if (null === $teamer = $user->getTeamer()) {
|
||||
throw new ApiClientException('Invalid argument');
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
class ProfileUpdateResponse
|
||||
{
|
||||
private ?int $addressId = null;
|
||||
private ?int $personId = null;
|
||||
private bool $updated = false;
|
||||
|
||||
public function getAddressId(): ?int
|
||||
{
|
||||
return $this->addressId;
|
||||
}
|
||||
|
||||
public function setAddressId(?int $addressId): static
|
||||
{
|
||||
$this->addressId = $addressId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPersonId(): ?int
|
||||
{
|
||||
return $this->personId;
|
||||
}
|
||||
|
||||
public function setPersonId(?int $personId): static
|
||||
{
|
||||
$this->personId = $personId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isUpdated(): bool
|
||||
{
|
||||
return $this->updated;
|
||||
}
|
||||
|
||||
public function setUpdated(bool $updated): static
|
||||
{
|
||||
$this->updated = $updated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use App\BusProNet\Model\Hotel;
|
||||
use App\BusProNet\Model\NotificationResponse;
|
||||
use App\BusProNet\Model\Pickup;
|
||||
use App\BusProNet\Model\ProfileResponse;
|
||||
use App\BusProNet\Model\ProfileUpdateResponse;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class ResponseParser
|
||||
@@ -45,8 +46,9 @@ class ResponseParser
|
||||
$subType = (string) $xml->art;
|
||||
switch ($subType) {
|
||||
case 'Adressdaten':
|
||||
case 'Adressdaten_Ändern':
|
||||
return $this->createProfileResponse($xml);
|
||||
case 'Adressdaten_Ändern':
|
||||
return $this->createProfileUpdateResponse($xml);
|
||||
case 'SelektionCRM':
|
||||
case 'SelektionCRM_Ändern':
|
||||
return $this->createCrmAttributesResponse($xml);
|
||||
@@ -138,6 +140,27 @@ class ResponseParser
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function createProfileUpdateResponse(\SimpleXMLElement $xml): ProfileUpdateResponse
|
||||
{
|
||||
$addressId = (int) $xml->idadresse;
|
||||
$personId = (int) $xml->idperson;
|
||||
|
||||
$updated = false;
|
||||
$updatedXml = $xml->xpath('änderung|aenderung');
|
||||
if (0 < count($updatedXml)) {
|
||||
$updated = 'true' === strtolower((string) $updatedXml[0]);
|
||||
}
|
||||
|
||||
$response = new ProfileUpdateResponse();
|
||||
$response
|
||||
->setAddressId($addressId)
|
||||
->setPersonId($personId)
|
||||
->setUpdated($updated)
|
||||
;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function createCrmAttributesResponse(\SimpleXMLElement $xml): CrmAttributesResponse
|
||||
{
|
||||
$groups = [];
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Entity\Upload;
|
||||
use App\Entity\User;
|
||||
use App\Form\TeamerProfileType;
|
||||
use App\Model\UploadSessionDto;
|
||||
use App\RequiredTeamerCheck\PersonalDataVerificationRequiredCheck;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -27,6 +28,7 @@ class IndexController extends AbstractController
|
||||
private readonly ApiClient $apiClient,
|
||||
private readonly UploadHandler $uploadHandler,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly PersonalDataVerificationRequiredCheck $personalDataVerificationRequiredCheck,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -53,8 +55,11 @@ class IndexController extends AbstractController
|
||||
// Validate teamer data to show missing data right away
|
||||
$errors = $this->validator->validate($teamer, null, ['profile_preflight']);
|
||||
|
||||
$verificationMode = false === $this->personalDataVerificationRequiredCheck->isSatisfied($user);
|
||||
|
||||
$form = $this->createForm(TeamerProfileType::class, $teamer, [
|
||||
'upload_session' => $uploadSession,
|
||||
'verification_mode' => $verificationMode,
|
||||
]);
|
||||
$form->handleRequest($request);
|
||||
|
||||
@@ -66,6 +71,10 @@ class IndexController extends AbstractController
|
||||
} catch (ApiClientException $e) {
|
||||
}
|
||||
|
||||
if (true === $verificationMode) {
|
||||
$teamer->setDataVerifiedAt(new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Deine Daten wurden aktualisiert');
|
||||
@@ -81,6 +90,7 @@ class IndexController extends AbstractController
|
||||
'form' => $form->createView(),
|
||||
'teamer' => $teamer,
|
||||
'errors' => $errors,
|
||||
'verification_mode' => $verificationMode,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,9 @@ class Teamer implements TimestampableEntityInterface
|
||||
#[ORM\Column]
|
||||
private bool $allowOverlappingApplications = false;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||
private ?\DateTimeImmutable $dataVerifiedAt = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?bool $driverLicenseDeclaration = null;
|
||||
|
||||
@@ -922,4 +925,16 @@ class Teamer implements TimestampableEntityInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDataVerifiedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->dataVerifiedAt;
|
||||
}
|
||||
|
||||
public function setDataVerifiedAt(?\DateTimeImmutable $dataVerifiedAt): static
|
||||
{
|
||||
$this->dataVerifiedAt = $dataVerifiedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace App\EventListener;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Security\RequiredCheck\RequiredTeamerCheckRegistry;
|
||||
use App\RequiredTeamerCheck\RequiredTeamerCheckRegistry;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Model\UploadSessionDto;
|
||||
use App\Service\Upload\UploadHandler;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
@@ -14,6 +15,7 @@ use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\IsTrue;
|
||||
|
||||
class TeamerProfileType extends AbstractType
|
||||
{
|
||||
@@ -94,6 +96,19 @@ class TeamerProfileType extends AbstractType
|
||||
],
|
||||
])
|
||||
;
|
||||
|
||||
if (true === $options['verification_mode']) {
|
||||
$builder->add('confirmDataVerification', CheckboxType::class, [
|
||||
'label' => 'Ich bestätige, dass meine Daten korrekt und aktuell sind',
|
||||
'mapped' => false,
|
||||
'constraints' => [
|
||||
new IsTrue([
|
||||
'message' => 'Deine Bestätigung ist erforderlich',
|
||||
'groups' => ['profile'],
|
||||
]),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function buildView(FormView $view, FormInterface $form, array $options): void
|
||||
@@ -114,9 +129,11 @@ class TeamerProfileType extends AbstractType
|
||||
'profile',
|
||||
],
|
||||
'anti_xss' => true,
|
||||
'verification_mode' => false,
|
||||
])
|
||||
->setRequired(['upload_session'])
|
||||
->setAllowedTypes('upload_session', UploadSessionDto::class)
|
||||
->setAllowedTypes('verification_mode', 'bool')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,10 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\RequiredCheck;
|
||||
namespace App\RequiredTeamerCheck;
|
||||
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\Upload;
|
||||
use App\Entity\User;
|
||||
use App\RequiredTeamerCheck\RequiredTeamerCheckInterface;
|
||||
|
||||
class DriverLicenseRequiredCheck implements RequiredTeamerCheckInterface
|
||||
{
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\RequiredTeamerCheck;
|
||||
|
||||
use App\Entity\User;
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
class PersonalDataVerificationRequiredCheck implements RequiredTeamerCheckInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $dataVerificationDeadlines = ['04-01', '10-01'],
|
||||
) {
|
||||
}
|
||||
|
||||
public function getCode(): string
|
||||
{
|
||||
return 'personal_data_verification';
|
||||
}
|
||||
|
||||
public function appliesTo(User $user): bool
|
||||
{
|
||||
return true === $user->hasRole('ROLE_TEAMER');
|
||||
}
|
||||
|
||||
public function isSatisfied(User $user): bool
|
||||
{
|
||||
$teamer = $user->getTeamer();
|
||||
|
||||
if (null === $teamer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false === $this->isVerificationRequired($teamer->getDataVerifiedAt());
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return 'app_teamer_profile_index';
|
||||
}
|
||||
|
||||
private function isVerificationRequired(?\DateTimeImmutable $dataVerifiedAt): bool
|
||||
{
|
||||
$deadline = $this->getCurrentDeadline(CarbonImmutable::now()->startOfDay());
|
||||
|
||||
if (null === $deadline) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null === $dataVerifiedAt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CarbonImmutable::instance($dataVerifiedAt) < $deadline;
|
||||
}
|
||||
|
||||
private function getCurrentDeadline(CarbonImmutable $now): ?CarbonImmutable
|
||||
{
|
||||
$deadlines = array_filter(array_map('trim', $this->dataVerificationDeadlines));
|
||||
rsort($deadlines);
|
||||
|
||||
foreach ($deadlines as $date) {
|
||||
$deadline = CarbonImmutable::createFromFormat('Y-m-d', sprintf('%s-%s', $now->format('Y'), $date));
|
||||
|
||||
if (false === $deadline instanceof CarbonImmutable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$deadline = $deadline->startOfDay();
|
||||
|
||||
if ($deadline <= $now) {
|
||||
return $deadline;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\RequiredCheck;
|
||||
namespace App\RequiredTeamerCheck;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\RequiredCheck;
|
||||
namespace App\RequiredTeamerCheck;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\RequiredTeamerCheck\RequiredTeamerCheckInterface;
|
||||
|
||||
class RequiredTeamerCheckRegistry
|
||||
{
|
||||
@@ -8,7 +8,7 @@ use App\BusProNet\Model\CrmAttributesResponse;
|
||||
use App\BusProNet\Model\ProfileResponse;
|
||||
use App\BusProNet\UserDataHandler;
|
||||
use App\Entity\User;
|
||||
use App\Security\RequiredCheck\RequiredTeamerCheckRegistry;
|
||||
use App\RequiredTeamerCheck\RequiredTeamerCheckRegistry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
@@ -53,6 +53,11 @@
|
||||
<h1 class="text-2xl font-bold pb-8">
|
||||
Mein Profil
|
||||
</h1>
|
||||
{% if verification_mode %}
|
||||
<div class="border border-blue-500 rounded-md p-4 text-blue-700 bg-blue-50 mb-8">
|
||||
Bitte überprüfe deine Daten und bestätige sie am Ende des Formulars.
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not form.vars.valid %}
|
||||
<div class="border border-red-500 rounded-md p-4 text-red-500 mb-8">
|
||||
{% include '_partials/_form_errors.html.twig' with { 'form': form } %}
|
||||
@@ -189,8 +194,14 @@
|
||||
} %}
|
||||
</div>
|
||||
|
||||
{% if verification_mode %}
|
||||
<div class="pb-4">
|
||||
{{ form_row(form.confirmDataVerification) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<button type="submit" class="btn" {{ stimulus_target('form-upload-guard', 'submit') }}>
|
||||
Aktualisieren
|
||||
{{ verification_mode ? 'Daten bestätigen' : 'Aktualisieren' }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ use App\BusProNet\Model\CrmAttributesResponse;
|
||||
use App\BusProNet\Model\Hotel;
|
||||
use App\BusProNet\Model\Pickup;
|
||||
use App\BusProNet\Model\ProfileResponse;
|
||||
use App\BusProNet\Model\ProfileUpdateResponse;
|
||||
use App\BusProNet\ResponseParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@@ -47,6 +48,19 @@ class ResponseParserTest extends TestCase
|
||||
$this->assertTrue($response->isTeamer());
|
||||
}
|
||||
|
||||
public function testParseSuccessfulProfileUpdateResponseWithoutAddressData(): void
|
||||
{
|
||||
$content = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="KUNDENKONTO"></satz><art>Adressdaten_Ändern</art><idadresse>141747</idadresse><idperson>224526</idperson><änderung>True</änderung></ergebnis>';
|
||||
|
||||
$parser = $this->getParserInstance();
|
||||
$response = $parser->parseXmlString(ApiClient::TYPE_CUSTOMER_DATA, $content);
|
||||
|
||||
$this->assertInstanceOf(ProfileUpdateResponse::class, $response);
|
||||
$this->assertSame(141747, $response->getAddressId());
|
||||
$this->assertSame(224526, $response->getPersonId());
|
||||
$this->assertTrue($response->isUpdated());
|
||||
}
|
||||
|
||||
public function testParseSuccessfulPickupsResponse(): void
|
||||
{
|
||||
$content = '<?xml version="1.0" encoding="utf-8" ?><zustiege><zustieg id="1" idbuspro="1" code="298"><ort>Karlsruhe</ort><strasse>A5 - Autohof Bruchsal</strasse><art>BUS</art><hausabholung>False</hausabholung><crsbuchbar>True</crsbuchbar><internetbuchbar>True</internetbuchbar></zustieg><zustieg id="2" idbuspro="2" code="2"><ort>Bonn</ort><strasse>Bahnhof</strasse><art>BUS</art><hausabholung>False</hausabholung><crsbuchbar>True</crsbuchbar><internetbuchbar>True</internetbuchbar></zustieg></zustiege>';
|
||||
@@ -100,4 +114,4 @@ class ResponseParserTest extends TestCase
|
||||
'bpn_crm_id_teamer' => 1070,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace App\Tests\Security\RequiredCheck;
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\Upload;
|
||||
use App\Entity\User;
|
||||
use App\Security\RequiredCheck\DriverLicenseRequiredCheck;
|
||||
use App\RequiredTeamerCheck\DriverLicenseRequiredCheck;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DriverLicenseRequiredCheckTest extends TestCase
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Security\RequiredCheck;
|
||||
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\User;
|
||||
use App\RequiredTeamerCheck\PersonalDataVerificationRequiredCheck;
|
||||
use Carbon\CarbonImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class PersonalDataVerificationRequiredCheckTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow();
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow();
|
||||
}
|
||||
|
||||
public function testAppliesToTeamerUsers(): void
|
||||
{
|
||||
$check = new PersonalDataVerificationRequiredCheck();
|
||||
$user = (new User())->setRoles(['ROLE_TEAMER']);
|
||||
|
||||
$this->assertTrue($check->appliesTo($user));
|
||||
}
|
||||
|
||||
public function testIsNotSatisfiedWhenTeamerIsMissing(): void
|
||||
{
|
||||
$check = new PersonalDataVerificationRequiredCheck();
|
||||
$user = (new User())->setRoles(['ROLE_TEAMER']);
|
||||
|
||||
$this->assertFalse($check->isSatisfied($user));
|
||||
}
|
||||
|
||||
public function testIsSatisfiedBeforeFirstDeadlineEvenWithoutVerification(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow('2026-01-10 12:00:00');
|
||||
$check = new PersonalDataVerificationRequiredCheck(['01-15', '07-15']);
|
||||
|
||||
$teamer = new Teamer();
|
||||
$user = (new User())
|
||||
->setRoles(['ROLE_TEAMER'])
|
||||
->setTeamer($teamer)
|
||||
;
|
||||
|
||||
$this->assertTrue($check->isSatisfied($user));
|
||||
}
|
||||
|
||||
public function testIsNotSatisfiedAfterFirstDeadlineWhenNeverVerified(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow('2026-01-20 12:00:00');
|
||||
$check = new PersonalDataVerificationRequiredCheck(['01-15', '07-15']);
|
||||
|
||||
$teamer = new Teamer();
|
||||
$user = (new User())
|
||||
->setRoles(['ROLE_TEAMER'])
|
||||
->setTeamer($teamer)
|
||||
;
|
||||
|
||||
$this->assertFalse($check->isSatisfied($user));
|
||||
}
|
||||
|
||||
public function testIsSatisfiedWhenVerifiedAfterCurrentDeadline(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow('2026-05-20 12:00:00');
|
||||
$check = new PersonalDataVerificationRequiredCheck(['01-15', '07-15']);
|
||||
|
||||
$teamer = (new Teamer())->setDataVerifiedAt(new \DateTimeImmutable('2026-01-16 10:00:00'));
|
||||
$user = (new User())
|
||||
->setRoles(['ROLE_TEAMER'])
|
||||
->setTeamer($teamer)
|
||||
;
|
||||
|
||||
$this->assertTrue($check->isSatisfied($user));
|
||||
}
|
||||
|
||||
public function testIsNotSatisfiedWhenVerificationIsOutdated(): void
|
||||
{
|
||||
CarbonImmutable::setTestNow('2026-07-20 12:00:00');
|
||||
$check = new PersonalDataVerificationRequiredCheck(['01-15', '07-15']);
|
||||
|
||||
$teamer = (new Teamer())->setDataVerifiedAt(new \DateTimeImmutable('2026-02-01 10:00:00'));
|
||||
$user = (new User())
|
||||
->setRoles(['ROLE_TEAMER'])
|
||||
->setTeamer($teamer)
|
||||
;
|
||||
|
||||
$this->assertFalse($check->isSatisfied($user));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user