feat: financial data check for teamers, improved enforced checks logic
addresses #869at5rtp
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\RequiredTeamerCheck;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
class FinancialDataRequiredCheck extends AbstractRequiredTeamerCheck
|
||||
{
|
||||
public function getCode(): string
|
||||
{
|
||||
return 'financial_data';
|
||||
}
|
||||
|
||||
public function isSatisfied(User $user): bool
|
||||
{
|
||||
$teamer = $user->getTeamer();
|
||||
|
||||
if (null === $teamer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === empty($teamer->getTaxId())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$bankAccount = $teamer->getBankAccount();
|
||||
|
||||
if (null === $bankAccount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === empty($bankAccount->getIban())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === empty($bankAccount->getBic())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === empty($bankAccount->getBank())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === empty($bankAccount->getHolder())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return 'app_teamer_check_financial_data';
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ use Carbon\CarbonImmutable;
|
||||
class PersonalDataVerificationRequiredCheck extends AbstractRequiredTeamerCheck
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $dataVerificationDeadlines = ['04-01', '10-01'],
|
||||
private readonly RecurringDeadlines $deadlines,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -26,84 +26,28 @@ class PersonalDataVerificationRequiredCheck extends AbstractRequiredTeamerCheck
|
||||
return false;
|
||||
}
|
||||
|
||||
return false === $this->isVerificationRequired($teamer);
|
||||
return false === $this->deadlines->isDue(
|
||||
$teamer->getDataVerifiedAt(),
|
||||
$this->getRegisteredAt($teamer),
|
||||
CarbonImmutable::now(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRouteName(): string
|
||||
{
|
||||
return 'app_teamer_profile_index';
|
||||
}
|
||||
|
||||
private function isVerificationRequired(Teamer $teamer): bool
|
||||
{
|
||||
$now = CarbonImmutable::now()->startOfDay();
|
||||
$deadline = $this->getCurrentDeadline($now);
|
||||
|
||||
if (null === $deadline) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === $this->isRegisteredAfterAnyCurrentYearDeadline($teamer, $now)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dataVerifiedAt = $teamer->getDataVerifiedAt();
|
||||
|
||||
if (null === $dataVerifiedAt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CarbonImmutable::instance($dataVerifiedAt) < $deadline;
|
||||
}
|
||||
|
||||
private function isRegisteredAfterAnyCurrentYearDeadline(Teamer $teamer, CarbonImmutable $now): bool
|
||||
{
|
||||
try {
|
||||
$registeredAt = CarbonImmutable::instance($teamer->getCreatedAt())->startOfDay();
|
||||
} catch (\TypeError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->getDeadlinesForYear($now) as $deadline) {
|
||||
if ($registeredAt > $deadline) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return 'app_teamer_check_personal_data';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CarbonImmutable[]
|
||||
* Teamer::getCreatedAt() is declared non-nullable but backed by a nullable
|
||||
* column, so it throws for an unpersisted teamer.
|
||||
*/
|
||||
private function getDeadlinesForYear(CarbonImmutable $now): array
|
||||
private function getRegisteredAt(Teamer $teamer): ?\DateTimeImmutable
|
||||
{
|
||||
$deadlines = [];
|
||||
|
||||
foreach (array_filter(array_map('trim', $this->dataVerificationDeadlines)) as $date) {
|
||||
$deadline = CarbonImmutable::createFromFormat('Y-m-d', sprintf('%s-%s', $now->format('Y'), $date));
|
||||
|
||||
if (false === $deadline instanceof CarbonImmutable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$deadlines[] = $deadline->startOfDay();
|
||||
try {
|
||||
return $teamer->getCreatedAt();
|
||||
} catch (\TypeError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $deadlines;
|
||||
}
|
||||
|
||||
private function getCurrentDeadline(CarbonImmutable $now): ?CarbonImmutable
|
||||
{
|
||||
$deadlines = $this->getDeadlinesForYear($now);
|
||||
rsort($deadlines);
|
||||
|
||||
foreach ($deadlines as $deadline) {
|
||||
if ($deadline <= $now) {
|
||||
return $deadline;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\RequiredTeamerCheck;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
/**
|
||||
* A set of yearly recurring dates after which something must be confirmed again.
|
||||
*
|
||||
* Belongs to the individual check that composes it, never to the application as a
|
||||
* whole: a deadline expresses "this particular confirmation has expired", not
|
||||
* "portal access has expired".
|
||||
*/
|
||||
final class RecurringDeadlines
|
||||
{
|
||||
/**
|
||||
* @param string[] $deadlines dates as MM-DD, e.g. ['04-01', '10-01']; an empty
|
||||
* list is never due
|
||||
*/
|
||||
public function __construct(private readonly array $deadlines)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a confirmation is outstanding: never confirmed, or last confirmed
|
||||
* before the most recent deadline that has passed.
|
||||
*/
|
||||
public function isDue(?\DateTimeInterface $confirmedAt, ?\DateTimeInterface $registeredAt, CarbonImmutable $now): bool
|
||||
{
|
||||
$deadline = $this->getCurrentDeadline($now);
|
||||
|
||||
if (null === $deadline) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === $this->isRegisteredAfterAnyDeadline($registeredAt, $now)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (null === $confirmedAt) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return CarbonImmutable::instance($confirmedAt) < $deadline;
|
||||
}
|
||||
|
||||
/**
|
||||
* The most recent deadline that has already passed, or null when none has.
|
||||
*/
|
||||
public function getCurrentDeadline(CarbonImmutable $now): ?CarbonImmutable
|
||||
{
|
||||
$deadlines = $this->getDeadlinesForYear($now);
|
||||
rsort($deadlines);
|
||||
|
||||
foreach ($deadlines as $deadline) {
|
||||
if ($deadline <= $now) {
|
||||
return $deadline;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Someone who registered after a deadline has effectively confirmed their data
|
||||
* by registering, so they are exempt until the next one.
|
||||
*/
|
||||
private function isRegisteredAfterAnyDeadline(?\DateTimeInterface $registeredAt, CarbonImmutable $now): bool
|
||||
{
|
||||
if (null === $registeredAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$registeredAt = CarbonImmutable::instance($registeredAt)->startOfDay();
|
||||
|
||||
foreach ($this->getDeadlinesForYear($now) as $deadline) {
|
||||
if ($registeredAt > $deadline) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return CarbonImmutable[]
|
||||
*/
|
||||
private function getDeadlinesForYear(CarbonImmutable $now): array
|
||||
{
|
||||
$deadlines = [];
|
||||
|
||||
foreach (array_filter(array_map('trim', $this->deadlines)) as $date) {
|
||||
$deadline = CarbonImmutable::createFromFormat('Y-m-d', sprintf('%s-%s', $now->format('Y'), $date));
|
||||
|
||||
if (false === $deadline instanceof CarbonImmutable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$deadlines[] = $deadline->startOfDay();
|
||||
}
|
||||
|
||||
return $deadlines;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user