From 77986776c235ccca5b5172c3bd0fac8432ea2ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Thu, 17 Sep 2026 11:40:26 +0200 Subject: [PATCH] feat: send season deadline summary to the office address --- .../Cron/SeasonTaskReminderService.php | 26 ++++------ .../Cron/SeasonTaskReminderServiceTest.php | 47 +++++++++---------- 2 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/Service/Cron/SeasonTaskReminderService.php b/src/Service/Cron/SeasonTaskReminderService.php index 8ca2857..f497902 100644 --- a/src/Service/Cron/SeasonTaskReminderService.php +++ b/src/Service/Cron/SeasonTaskReminderService.php @@ -7,7 +7,6 @@ use App\Config\SeasonCalendar; use App\Email\Mailer; use App\Entity\SeasonAssignmentTask; use App\Repository\SeasonAssignmentTaskRepository; -use App\Repository\UserRepository; use App\Service\Season\SeasonAssignmentLabeler; use App\Service\Season\SeasonTaskDeadlineResolver; use Psr\Log\LoggerInterface; @@ -32,7 +31,6 @@ class SeasonTaskReminderService private readonly SeasonTaskDeadlineResolver $deadlineResolver, private readonly SeasonAssignmentLabeler $labeler, private readonly SeasonCalendar $seasonCalendar, - private readonly UserRepository $userRepository, private readonly Mailer $mailer, private readonly LoggerInterface $logger, ) { @@ -72,21 +70,15 @@ class SeasonTaskReminderService return $message; } - // the two roles that carry ROLE_ADMINISTRATIVE: the repository matches roles as they are - // stored on the user, and nobody is ever stored with the hierarchy-granted one - foreach ($this->userRepository->getUsersByRoles(['ROLE_TEAM_ADMIN', 'ROLE_MANAGER']) as $user) { - if (true === $user->isMuteNotifications()) { - continue; - } - - $this->mailer->createAndSendEmail([ - 'violations' => $violations, - ], [ - 'to' => $user->getEmail(), - 'subject' => 'Fristen für Saisonunterlagen erreicht', - 'template' => 'email/season_deadlines.html.twig', - ]); - } + // One mail to the office mailbox instead of a copy per admin: the summary is + // office-wide, not personal, so it goes to the Mailer default (APP_DEFAULT_EMAIL_TO) + // and no 'to' is passed here. + $this->mailer->createAndSendEmail([ + 'violations' => $violations, + ], [ + 'subject' => 'Fristen für Saisonunterlagen erreicht', + 'template' => 'email/season_deadlines.html.twig', + ]); $message = 'Sent the season task deadline summary for '.$count.' teamers'; $this->logger->info($message); diff --git a/tests/Service/Cron/SeasonTaskReminderServiceTest.php b/tests/Service/Cron/SeasonTaskReminderServiceTest.php index 12bc46f..8b3c6af 100644 --- a/tests/Service/Cron/SeasonTaskReminderServiceTest.php +++ b/tests/Service/Cron/SeasonTaskReminderServiceTest.php @@ -13,9 +13,7 @@ use App\Entity\Embeddable\Communication; use App\Entity\SeasonAssignment; use App\Entity\SeasonAssignmentTask; use App\Entity\Teamer; -use App\Entity\User; use App\Repository\SeasonAssignmentTaskRepository; -use App\Repository\UserRepository; use App\Service\Cron\SeasonTaskReminderService; use App\Service\Season\SeasonAssignmentLabeler; use App\Service\Season\SeasonTaskDeadlineResolver; @@ -31,14 +29,12 @@ use Psr\Log\NullLogger; class SeasonTaskReminderServiceTest extends TestCase { private SeasonAssignmentTaskRepository&MockObject $repository; - private UserRepository&MockObject $userRepository; private Mailer&MockObject $mailer; private SeasonTaskReminderService $service; protected function setUp(): void { $this->repository = $this->createMock(SeasonAssignmentTaskRepository::class); - $this->userRepository = $this->createMock(UserRepository::class); $this->mailer = $this->createMock(Mailer::class); $this->service = new SeasonTaskReminderService( @@ -49,7 +45,6 @@ class SeasonTaskReminderServiceTest extends TestCase new HouseCatalog(['AGR' => 'Rotbach']) ), new SeasonCalendar('11-01', '04-01'), - $this->userRepository, $this->mailer, new NullLogger(), ); @@ -58,7 +53,6 @@ class SeasonTaskReminderServiceTest extends TestCase public function testTheSummaryFiresOnTheDeadlineDay(): void { $this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]); - $this->userRepository->method('getUsersByRoles')->willReturn([$this->admin()]); $this->mailer ->expects($this->once()) @@ -76,6 +70,28 @@ class SeasonTaskReminderServiceTest extends TestCase $this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-01')); } + /** + * The summary is one office mail, not a copy per admin: it passes no recipient at all and so + * lands on the Mailer default, APP_DEFAULT_EMAIL_TO. + */ + public function testTheSummaryGoesOutOnceToTheDefaultAddress(): void + { + $this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]); + + $this->mailer + ->expects($this->once()) + ->method('createAndSendEmail') + ->with( + $this->anything(), + $this->callback( + static fn (array $options): bool => false === array_key_exists('to', $options) + ) + ) + ; + + $this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-01')); + } + public function testTheSummaryDoesNotFireTheDayAfter(): void { $this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]); @@ -92,7 +108,6 @@ class SeasonTaskReminderServiceTest extends TestCase public function testCoincidingDeadlinesAreReportedOnceAsTheFirst(): void { $this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]); - $this->userRepository->method('getUsersByRoles')->willReturn([$this->admin()]); $levels = []; @@ -115,7 +130,6 @@ class SeasonTaskReminderServiceTest extends TestCase $task = $this->task('2026-11-01')->setDeadlineOverride2(new \DateTimeImmutable('2026-10-15')); $this->repository->method('findOpen')->willReturn([$task]); - $this->userRepository->method('getUsersByRoles')->willReturn([$this->admin()]); $levels = []; @@ -133,18 +147,6 @@ class SeasonTaskReminderServiceTest extends TestCase $this->assertSame([2], $levels); } - public function testAnAdminMutingNotificationsIsSkipped(): void - { - $this->repository->method('findOpen')->willReturn([$this->task('2026-11-01')]); - $this->userRepository->method('getUsersByRoles')->willReturn([ - $this->admin()->setMuteNotifications(true), - ]); - - $this->mailer->expects($this->never())->method('createAndSendEmail'); - - $this->service->sendDeadlineSummary(new \DateTimeImmutable('2026-10-01')); - } - public function testTheDigestOnlyGoesOutOnMondays(): void { $this->repository->expects($this->never())->method('findOpen'); @@ -221,9 +223,4 @@ class SeasonTaskReminderServiceTest extends TestCase return $task; } - - private function admin(): User - { - return (new User())->setEmail('office@example.org'); - } }