diff --git a/docs/service-simplification-plan.md b/docs/service-simplification-plan.md index 89ef305..115c7e0 100644 --- a/docs/service-simplification-plan.md +++ b/docs/service-simplification-plan.md @@ -21,6 +21,7 @@ The codebase is already in a better place than it was at the start of the refact - `BookingService` still covers hydration, booking bootstrap, service preselection, and booking status rules. - `BookingParticipantCountService` now handles only participant count shaping. - `ParticipantPrepopulationService` now owns applicant prefill plus the create-mode dummy-data shortcut. +- `BookingEditParticipantContextFactory` now prepares the edit participant page context directly, replacing the older pass-through participant form service. - `BookingPriceCalculatorService` is focused on pricing, but it still sits close to display-oriented behavior in adjacent code paths. - `TravelDataService` remains broad and is likely the next larger boundary after booking orchestration is reduced. diff --git a/src/Controller/Booking/Edit/ParticipantController.php b/src/Controller/Booking/Edit/ParticipantController.php index 90665d0..fd899d3 100644 --- a/src/Controller/Booking/Edit/ParticipantController.php +++ b/src/Controller/Booking/Edit/ParticipantController.php @@ -4,11 +4,15 @@ declare(strict_types=1); namespace App\Controller\Booking\Edit; +use App\BusProNet\Model\Booking; use App\BusProNet\Model\Notification; use App\Entity\User; use App\Form\BookingParticipantType; +use App\Form\Model\BookingDto; use App\Htmx\HxTrait; -use App\Service\BookingEditParticipantFormService; +use App\Service\BookingEditDataLoaderService; +use App\Service\BookingEditDraftService; +use App\Service\BookingEditParticipantContextFactory; use App\Service\BookingSessionService; use App\Service\ParticipantFormSupportService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -25,9 +29,11 @@ class ParticipantController extends AbstractController use HxTrait; public function __construct( - private readonly ParticipantFormSupportService $participantFormSupportService, - private readonly BookingEditParticipantFormService $participantFormService, + private readonly BookingEditDataLoaderService $dataLoader, + private readonly BookingEditDraftService $draftService, + private readonly BookingEditParticipantContextFactory $participantContextFactory, private readonly BookingSessionService $bookingSessionService, + private readonly ParticipantFormSupportService $participantFormSupportService, ) { } @@ -45,7 +51,7 @@ class ParticipantController extends AbstractController /** @var User $user */ $user = $this->getUser(); - $bookingDto = $this->participantFormService->loadBookingDto($request); + $bookingDto = $this->bookingSessionService->getBookingDto($request, BookingDto::MODE_EDIT); if (null === $bookingDto) { $this->addFlash('error', 'Sitzung abgelaufen. Bitte neu laden.'); @@ -55,20 +61,20 @@ class ParticipantController extends AbstractController $this->participantFormSupportService->ensureParticipantExists($bookingDto, $index); - $bookingData = $this->participantFormService->fetchBookingData($id, $user); + $bookingData = $this->dataLoader->fetchBookingData($id, $user); if (null === $bookingData || $bookingData instanceof Notification) { $this->addFlash('error', 'Buchungsdaten nicht (mehr) verfügbar'); return $this->redirectToRoute('app_bookings'); } - if ($this->participantFormService->isParticipantCanceled($bookingData, $index)) { + if ($this->isParticipantCanceled($bookingData, $index)) { $this->addFlash('warning', 'Stornierte Teilnehmer können nicht bearbeitet werden'); return $this->redirectToRoute('app_booking_edit', ['id' => $id]); } - $this->participantFormService->enrichTravelData($bookingDto); + $this->participantContextFactory->prepareBookingDto($bookingDto); $wrapper = $this->participantFormSupportService->createParticipantEditDto($bookingDto, $index); @@ -83,23 +89,22 @@ class ParticipantController extends AbstractController $notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingDto); if ($form->isSubmitted() && $form->isValid()) { - $this->participantFormService->saveBookingDto($request, $bookingDto); - $this->participantFormService->saveDraft($user, $id, $bookingDto); + $this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT); + $this->draftService->saveDraft($user, $id, $bookingDto); $this->addNotificationsAsFlashMessages($notifications); return $this->redirectToRoute('app_booking_edit', ['id' => $id]); } - $summaryData = $this->participantFormService->getSummaryData($bookingDto); - $mutableData = $this->participantFormService->getMutableData($bookingData->dateId); + $context = $this->participantContextFactory->create($bookingDto, $bookingData); return $this->render('booking/edit/participant.html', [ 'form' => $form->createView(), 'participantIndex' => $index, - 'bookingDto' => $bookingDto, - 'bookingData' => $bookingData, - 'mutableData' => $mutableData, - 'summaryData' => $summaryData, + 'bookingDto' => $context->bookingDto, + 'bookingData' => $context->bookingData, + 'mutableData' => $context->mutableData, + 'summaryData' => $context->summaryData, 'refreshRouteName' => 'app_booking_edit_participant_refresh', 'refreshRouteParams' => ['id' => $id, 'index' => $index], 'cancelRouteName' => 'app_booking_edit', @@ -122,19 +127,16 @@ class ParticipantController extends AbstractController /** @var User $user */ $user = $this->getUser(); - $bookingDto = $this->participantFormService->loadBookingDto($request); + $bookingDto = $this->bookingSessionService->getBookingDto($request, BookingDto::MODE_EDIT); if (null === $bookingDto) { return new Response('Session expired. Please reload page.', Response::HTTP_BAD_REQUEST); } $this->participantFormSupportService->ensureParticipantExists($bookingDto, $index); - $this->participantFormService->enrichTravelData($bookingDto); + $this->participantContextFactory->prepareBookingDto($bookingDto); - $bookingData = $this->participantFormService->fetchBookingData($id, $user); - $mutableData = null !== $bookingData && !($bookingData instanceof Notification) - ? $this->participantFormService->getMutableData($bookingData->dateId) - : null; + $bookingData = $this->dataLoader->fetchBookingData($id, $user); $wrapper = $this->participantFormSupportService->createParticipantEditDto($bookingDto, $index); @@ -147,7 +149,9 @@ class ParticipantController extends AbstractController $form->handleRequest($request); $notifications = $this->participantFormSupportService->collectAndClearNotifications($bookingDto); - $summaryData = $this->participantFormService->getSummaryData($bookingDto); + $context = null !== $bookingData && !($bookingData instanceof Notification) + ? $this->participantContextFactory->create($bookingDto, $bookingData) + : null; $response = $this->htmxOobResponse( 'booking/_participant_form.html.twig', @@ -157,8 +161,8 @@ class ParticipantController extends AbstractController 'participantIndex' => $index, 'bookingDto' => $bookingDto, 'bookingData' => $bookingData, - 'mutableData' => $mutableData, - 'summaryData' => $summaryData, + 'mutableData' => $context?->mutableData, + 'summaryData' => $context?->summaryData, 'refreshRouteName' => 'app_booking_edit_participant_refresh', 'refreshRouteParams' => ['id' => $id, 'index' => $index], 'cancelRouteName' => 'app_booking_edit', @@ -184,4 +188,9 @@ class ParticipantController extends AbstractController $this->addFlash($notification['type'], $notification['message']); } } + + private function isParticipantCanceled(Booking $bookingData, int $index): bool + { + return 'S' === ($bookingData->participantsStatus[$index] ?? null); + } } diff --git a/src/Form/Model/BookingEditParticipantContext.php b/src/Form/Model/BookingEditParticipantContext.php new file mode 100644 index 0000000..1f2c824 --- /dev/null +++ b/src/Form/Model/BookingEditParticipantContext.php @@ -0,0 +1,22 @@ +travelDataService->enrichWithFreshAvailabilities($bookingDto->travel); + } + + public function create(BookingDto $bookingDto, Booking $bookingData): BookingEditParticipantContext + { + return new BookingEditParticipantContext( + bookingDto: $bookingDto, + bookingData: $bookingData, + mutableData: $this->travelDataService->getMutabilityData($bookingData->dateId), + summaryData: $this->summaryDataService->getSummaryData($bookingDto), + ); + } +} diff --git a/src/Service/BookingEditParticipantFormService.php b/src/Service/BookingEditParticipantFormService.php deleted file mode 100644 index 4a7d212..0000000 --- a/src/Service/BookingEditParticipantFormService.php +++ /dev/null @@ -1,72 +0,0 @@ -bookingSessionService->getBookingDto($request, BookingDto::MODE_EDIT); - } - - public function fetchBookingData(int $bookingId, User $user): Booking|Notification|null - { - return $this->dataLoader->fetchBookingData($bookingId, $user); - } - - public function isParticipantCanceled(Booking $bookingData, int $index): bool - { - return 'S' === ($bookingData->participantsStatus[$index] ?? null); - } - - public function enrichTravelData(BookingDto $bookingDto): void - { - $this->travelDataService->enrichWithFreshAvailabilities($bookingDto->travel); - } - - public function saveBookingDto(Request $request, BookingDto $bookingDto): void - { - $this->bookingSessionService->saveBookingDto($request, $bookingDto, BookingDto::MODE_EDIT); - } - - public function saveDraft(User $user, int $bookingId, BookingDto $bookingDto): void - { - $this->draftService->saveDraft($user, $bookingId, $bookingDto); - } - - public function getSummaryData(BookingDto $bookingDto): BookingSummaryDto - { - return $this->summaryDataService->getSummaryData($bookingDto); - } - - public function getMutableData(int $dateId): ?BaseData - { - return $this->travelDataService->getMutabilityData($dateId); - } -} diff --git a/tests/Service/BookingEditParticipantContextFactoryTest.php b/tests/Service/BookingEditParticipantContextFactoryTest.php new file mode 100644 index 0000000..f477dc6 --- /dev/null +++ b/tests/Service/BookingEditParticipantContextFactoryTest.php @@ -0,0 +1,77 @@ +dateFrom = new \DateTimeImmutable('2030-01-01'); + $travel->dateTo = new \DateTimeImmutable('2030-01-06'); + $bookingDto = new BookingDto($travel, 157047); + + $travelDataService = $this->createMock(TravelDataService::class); + $travelDataService->expects($this->once()) + ->method('enrichWithFreshAvailabilities') + ->with($travel); + + $service = new BookingEditParticipantContextFactory( + $this->createMock(BookingSummaryDataService::class), + $travelDataService, + ); + + $service->prepareBookingDto($bookingDto); + } + + public function testCreateBuildsEditContext(): void + { + $travel = new Travel(); + $travel->dateFrom = new \DateTimeImmutable('2030-01-01'); + $travel->dateTo = new \DateTimeImmutable('2030-01-06'); + + $bookingDto = new BookingDto($travel, 157047); + $bookingDto->booking = new Booking(); + + $bookingData = new Booking(); + $bookingData->dateId = 1234; + + $mutableData = new BaseData([]); + $summaryData = $this->createMock(BookingSummaryDto::class); + + $summaryDataService = $this->createMock(BookingSummaryDataService::class); + $summaryDataService->expects($this->once()) + ->method('getSummaryData') + ->with($bookingDto) + ->willReturn($summaryData); + + $travelDataService = $this->createMock(TravelDataService::class); + $travelDataService->expects($this->once()) + ->method('getMutabilityData') + ->with(1234) + ->willReturn($mutableData); + + $service = new BookingEditParticipantContextFactory($summaryDataService, $travelDataService); + + $context = $service->create($bookingDto, $bookingData); + + $this->assertInstanceOf(BookingEditParticipantContext::class, $context); + $this->assertSame($bookingDto, $context->bookingDto); + $this->assertSame($bookingData, $context->bookingData); + $this->assertSame($mutableData, $context->mutableData); + $this->assertSame($summaryData, $context->summaryData); + } +} diff --git a/tests/Service/BookingEditParticipantFormServiceTest.php b/tests/Service/BookingEditParticipantFormServiceTest.php deleted file mode 100644 index c8ac3ff..0000000 --- a/tests/Service/BookingEditParticipantFormServiceTest.php +++ /dev/null @@ -1,94 +0,0 @@ -createBookingDto(); - $bookingSessionService = $this->createMock(BookingSessionService::class); - $bookingSessionService->expects($this->once()) - ->method('getBookingDto') - ->with($request, BookingDto::MODE_EDIT) - ->willReturn($bookingDto); - - $service = $this->createService(bookingSessionService: $bookingSessionService); - - $this->assertSame($bookingDto, $service->loadBookingDto($request)); - } - - public function testIsParticipantCanceledUsesBookingStatus(): void - { - $service = $this->createService(); - $booking = new Booking(); - $booking->participantsStatus = [0 => 'S', 1 => 'A']; - - $this->assertTrue($service->isParticipantCanceled($booking, 0)); - $this->assertFalse($service->isParticipantCanceled($booking, 1)); - } - - public function testGetMutableDataDelegatesToTravelDataService(): void - { - $mutableData = new BaseData([]); - $travelDataService = $this->createMock(TravelDataService::class); - $travelDataService->expects($this->once()) - ->method('getMutabilityData') - ->with(1234) - ->willReturn($mutableData); - - $service = new BookingEditParticipantFormService( - $this->createMock(BookingEditDataLoaderService::class), - $this->createMock(BookingEditDraftService::class), - $this->createMock(BookingSessionService::class), - $this->createMock(BookingSummaryDataService::class), - $travelDataService, - ); - - $this->assertSame($mutableData, $service->getMutableData(1234)); - } - - private function createService( - ?BookingEditDataLoaderService $dataLoader = null, - ?BookingEditDraftService $draftService = null, - ?BookingSessionService $bookingSessionService = null, - ?BookingSummaryDataService $summaryDataService = null, - ?TravelDataService $travelDataService = null, - ): BookingEditParticipantFormService { - return new BookingEditParticipantFormService( - $dataLoader ?? $this->createMock(BookingEditDataLoaderService::class), - $draftService ?? $this->createMock(BookingEditDraftService::class), - $bookingSessionService ?? $this->createMock(BookingSessionService::class), - $summaryDataService ?? $this->createMock(BookingSummaryDataService::class), - $travelDataService ?? $this->createMock(TravelDataService::class), - ); - } - - private function createBookingDto(): BookingDto - { - $travel = new Travel(); - $travel->dateFrom = new \DateTimeImmutable('2030-01-01'); - $travel->dateTo = new \DateTimeImmutable('2030-01-06'); - - $bookingDto = new BookingDto($travel, 157047); - $bookingDto->booking = new Booking(); - - return $bookingDto; - } -}