64 lines
2.0 KiB
PHP
64 lines
2.0 KiB
PHP
<?php
|
|
|
|
namespace App\Validator\Constraints;
|
|
|
|
use App\Config\SeasonCalendar;
|
|
use App\Repository\SeasonAssignmentRepository;
|
|
use Symfony\Component\Validator\Constraint;
|
|
use Symfony\Component\Validator\ConstraintValidator;
|
|
|
|
/**
|
|
* One season assignment per teamer per season.
|
|
*
|
|
* "Once a year" was a stated fact rather than an enforced one; this is what enforces it, on
|
|
* create and on edit alike, so the teamer's season view can rely on resolving a single
|
|
* assignment.
|
|
*/
|
|
class SeasonAssignmentValidator extends ConstraintValidator
|
|
{
|
|
public function __construct(
|
|
private readonly SeasonAssignmentRepository $seasonAssignmentRepository,
|
|
private readonly SeasonCalendar $seasonCalendar,
|
|
) {
|
|
}
|
|
|
|
public function validate(mixed $value, Constraint $constraint): void
|
|
{
|
|
/** @var \App\Entity\SeasonAssignment $seasonAssignment */
|
|
$seasonAssignment = $value;
|
|
|
|
// An assignment the office has laid out but not staffed yet is a normal working state,
|
|
// and a missing start date is the create form's own problem to report.
|
|
if (null === $teamer = $seasonAssignment->getTeamer()) {
|
|
return;
|
|
}
|
|
|
|
if (null === $dateFrom = $seasonAssignment->getDateFrom()) {
|
|
return;
|
|
}
|
|
|
|
// A placement starting outside any season window belongs to no season, so there is
|
|
// nothing to be the second of.
|
|
if (null === $seasonYear = $this->seasonCalendar->seasonYearFor($dateFrom)) {
|
|
return;
|
|
}
|
|
|
|
[$windowStart, $windowEnd] = $this->seasonCalendar->windowFor($seasonYear);
|
|
|
|
$existing = $this->seasonAssignmentRepository->countInSeasonWindow(
|
|
$teamer,
|
|
$windowStart->toDateTimeImmutable(),
|
|
$windowEnd->toDateTimeImmutable(),
|
|
$seasonAssignment->getId()
|
|
);
|
|
|
|
if (0 < $existing) {
|
|
$this
|
|
->context
|
|
->buildViolation($constraint->message)
|
|
->addViolation()
|
|
;
|
|
}
|
|
}
|
|
}
|