feat: additional functionality and layout adjustments for season teamers
This commit is contained in:
1 parent
80c6016da3
commit
4cab57d136
30 files changed
+916
-192
No files matched your search
@@ -18,9 +18,8 @@ use Psr\Log\LoggerInterface;
|
||||
* Both are idempotent without recording anything, like UploadReminderService: the office summary
|
||||
* picks out exactly one day per deadline, and the teamer digest picks out exactly one weekday.
|
||||
*
|
||||
* Only "done" stops either of them. An arriving file does not, and neither does the "uploaded"
|
||||
* tick, which is a note for the office and gates nothing. Neither does doneTeamer on the
|
||||
* assignment: that flag is informative only.
|
||||
* Only "done" stops either of them. An arriving file does not, and neither does the "office done"
|
||||
* tick, which is a note for the office and gates nothing.
|
||||
*/
|
||||
class SeasonTaskReminderService
|
||||
{
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
|
||||
namespace App\Service\Season;
|
||||
|
||||
use App\Config\SeasonChecklistStatus;
|
||||
use App\Config\SeasonTask;
|
||||
use App\Config\SeasonTaskProvision;
|
||||
use App\Entity\SeasonAssignment;
|
||||
use App\Entity\SeasonAssignmentTask;
|
||||
use App\Entity\SeasonTaskDocument;
|
||||
use App\Repository\SeasonTaskDocumentRepository;
|
||||
|
||||
/**
|
||||
* Puts one checklist row together: the stored state, the entry it belongs to, both resolved
|
||||
* deadlines and - for an entry handing out a standard document - the library file it points at.
|
||||
* deadlines and the document the row was given when it was created.
|
||||
*
|
||||
* Shared by the detail page and the per-row htmx toggles, which re-render a single row and have
|
||||
* to arrive at exactly the same one.
|
||||
* The document comes off the row, never out of the library: the library is what the next
|
||||
* assignments get, and replacing a file there must not change what an assignment that already
|
||||
* exists hands out.
|
||||
*
|
||||
* Shared by the detail page and the htmx toggle, which re-renders the checklist after a tick and
|
||||
* has to arrive at exactly the same rows.
|
||||
*/
|
||||
class SeasonChecklistBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SeasonTaskDeadlineResolver $deadlineResolver,
|
||||
private readonly SeasonTaskDocumentRepository $seasonTaskDocumentRepository,
|
||||
) {
|
||||
public function __construct(private readonly SeasonTaskDeadlineResolver $deadlineResolver)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,8 +40,6 @@ class SeasonChecklistBuilder
|
||||
}
|
||||
}
|
||||
|
||||
$documents = $this->seasonTaskDocumentRepository->findIndexedByTaskKey();
|
||||
|
||||
$rows = [];
|
||||
|
||||
foreach (SeasonTask::cases() as $case) {
|
||||
@@ -49,16 +47,49 @@ class SeasonChecklistBuilder
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = $this->buildRow($tasks[$case->value], $documents[$case->value] ?? null);
|
||||
$rows[] = $this->buildRow($tasks[$case->value]);
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* How far the checklist has been worked through. Rows whose key no longer resolves are
|
||||
* skipped here just like in build(): the entry they belong to was removed from the enum, and
|
||||
* counting it would leave the assignment permanently unfinished.
|
||||
*/
|
||||
public function status(SeasonAssignment $seasonAssignment): SeasonChecklistStatus
|
||||
{
|
||||
$open = 0;
|
||||
$openWithReturn = 0;
|
||||
|
||||
foreach ($seasonAssignment->getTasks() as $task) {
|
||||
if (null === $definition = $task->getTask()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($task->isDone()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
++$open;
|
||||
|
||||
if ($definition->requiresReturn()) {
|
||||
++$openWithReturn;
|
||||
}
|
||||
}
|
||||
|
||||
return match (true) {
|
||||
0 === $open => SeasonChecklistStatus::COMPLETE,
|
||||
0 === $openWithReturn => SeasonChecklistStatus::RETURNS_COMPLETE,
|
||||
default => SeasonChecklistStatus::OPEN,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function buildRow(SeasonAssignmentTask $task, ?SeasonTaskDocument $seasonTaskDocument = null): array
|
||||
private function buildRow(SeasonAssignmentTask $task): array
|
||||
{
|
||||
$definition = $task->getTask();
|
||||
|
||||
@@ -66,16 +97,12 @@ class SeasonChecklistBuilder
|
||||
throw new \InvalidArgumentException(sprintf('Task key "%s" is not defined.', $task->getTaskKey()));
|
||||
}
|
||||
|
||||
if (null === $seasonTaskDocument && SeasonTaskProvision::STANDARD === $definition->provides()) {
|
||||
$seasonTaskDocument = $this->seasonTaskDocumentRepository->findForTask($definition);
|
||||
}
|
||||
|
||||
return [
|
||||
'task' => $task,
|
||||
'definition' => $definition,
|
||||
'deadline1' => $this->deadlineResolver->resolveFirst($task),
|
||||
'deadline2' => $this->deadlineResolver->resolveSecond($task),
|
||||
'document' => $seasonTaskDocument?->getDocument(),
|
||||
'document' => $task->getDocument(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace App\Service\Season;
|
||||
|
||||
use App\Config\SeasonTask;
|
||||
use App\Config\SeasonTaskProvision;
|
||||
use App\Entity\SeasonAssignment;
|
||||
use App\Entity\SeasonAssignmentTask;
|
||||
use App\Repository\SeasonTaskDocumentRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
@@ -18,11 +20,18 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
* It only ever creates. A case removed from the enum leaves its row behind: every read resolves
|
||||
* the stored key through SeasonTask::tryFrom() and skips what no longer exists, which keeps a
|
||||
* removed entry from erroring out and keeps its history recoverable.
|
||||
*
|
||||
* Creating is also the only point at which the library is read: the document is copied onto the
|
||||
* row and the office tick set with it. Both are a starting state, not a rule, so neither is
|
||||
* undone by a later sync - a deposited file reaches an assignment that already exists only
|
||||
* through the propagate action.
|
||||
*/
|
||||
class SeasonTaskSynchronizer
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly SeasonTaskDocumentRepository $seasonTaskDocumentRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function sync(SeasonAssignment $seasonAssignment): void
|
||||
@@ -33,23 +42,42 @@ class SeasonTaskSynchronizer
|
||||
$existing[$task->getTaskKey()] = true;
|
||||
}
|
||||
|
||||
$created = false;
|
||||
$missing = [];
|
||||
|
||||
foreach (SeasonTask::cases() as $case) {
|
||||
if (true === isset($existing[$case->value])) {
|
||||
continue;
|
||||
if (false === isset($existing[$case->value])) {
|
||||
$missing[] = $case;
|
||||
}
|
||||
}
|
||||
|
||||
if ([] === $missing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Queried once, and only when there is something to create: sync() runs on every render
|
||||
// of the administrative detail page, where nothing is usually missing.
|
||||
$documents = $this->seasonTaskDocumentRepository->findIndexedByTaskKey();
|
||||
|
||||
foreach ($missing as $case) {
|
||||
$task = (new SeasonAssignmentTask())->setTaskKey($case->value);
|
||||
|
||||
// The library holds what the next assignments get and is replaced from season to
|
||||
// season, so the file is copied onto the row here instead of being resolved on every
|
||||
// read. What an assignment hands out is decided once, when it is laid out.
|
||||
if (SeasonTaskProvision::STANDARD === $case->provides() && true === isset($documents[$case->value])) {
|
||||
// The office step is covered the moment the row has its document: there is
|
||||
// nothing left to put together for this teamer. The checkbox stays, so it can
|
||||
// be unticked.
|
||||
$task
|
||||
->setDocument($documents[$case->value]->getDocument())
|
||||
->setOfficeDone(true)
|
||||
;
|
||||
}
|
||||
|
||||
$seasonAssignment->addTask($task);
|
||||
$this->entityManager->persist($task);
|
||||
|
||||
$created = true;
|
||||
}
|
||||
|
||||
if (true === $created) {
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user