105 lines
2.8 KiB
PHP
105 lines
2.8 KiB
PHP
<?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;
|
|
}
|
|
}
|