Files
myep/src/Service/BookingEditSubmitGuard.php
T

216 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Service;
use App\BusProNet\DataProcessor\BookingDataProcessor;
use App\BusProNet\Model\Booking;
use App\BusProNet\Model\Pickup;
use App\BusProNet\Model\Service;
use App\Form\Model\BookingDto;
use App\Form\Model\ParticipantDto;
use App\Form\Service\Condition\TravelStartCutoffReachedCondition;
/**
* Enforces immutable edit categories before submitting booking updates.
*
* This guard reconciles the in-session edit DTO with a fresh booking snapshot.
* If a mutability cutoff passed during an edit session, stale user changes in
* immutable categories are reverted to the current booking state so update
* payloads cannot include blocked changes.
*/
class BookingEditSubmitGuard
{
public function __construct(
private readonly BookingDataProcessor $bookingDataProcessor,
) {
}
/**
* Reverts immutable category changes to fresh booking values.
*
* @return bool True when at least one immutable field was reverted
*/
public function reconcileImmutableCategories(BookingDto $workingDto, Booking $freshBooking): bool
{
$baselineDto = $this->bookingDataProcessor->createBookingDtoFromBooking($freshBooking, $workingDto->travel, $workingDto->isInternalAgencyBooking());
// Booking-level D-4 cutoff used as submit-time safety net for rentals.
$serviceCutoffReachedCondition = new TravelStartCutoffReachedCondition(
TravelStartCutoffReachedCondition::DEFAULT_DAYS_BEFORE_START
);
// TravelStartCutoffReachedCondition is booking-level (travel date only).
// The interface requires a participant index, but this condition does not use it,
// so we pass 0 as a neutral placeholder.
$rentalsLockedByCutoff = $serviceCutoffReachedCondition->evaluate($workingDto, 0, []);
$changed = false;
foreach ($workingDto->participants as $index => $participant) {
$baseline = $baselineDto->participants[$index] ?? null;
if (null === $baseline) {
continue;
}
if (false === $workingDto->travel->additionalServicesMutable) {
$changed = $this->reconcileAdditionalServices($participant, $baseline) || $changed;
}
// Reconcile rentals either when BPN additional services are immutable
// or when our D-4 rule has been reached.
if (false === $workingDto->travel->additionalServicesMutable || $rentalsLockedByCutoff) {
$changed = $this->reconcileRentals($participant, $baseline) || $changed;
}
if (false === $workingDto->travel->transportationServicesMutable) {
$changed = $this->reconcileTransportationServices($participant, $baseline) || $changed;
}
if (false === $workingDto->travel->pickupsMutable) {
$changed = $this->reconcilePickups($participant, $baseline) || $changed;
}
}
return $changed;
}
private function reconcileAdditionalServices(ParticipantDto $participant, ParticipantDto $baseline): bool
{
$changed = false;
if (false === $this->areServiceListsEqual($participant->courses, $baseline->courses)) {
$participant->courses = $baseline->courses;
$changed = true;
}
if (false === $this->areServiceListsEqual($participant->additionalServices, $baseline->additionalServices)) {
$participant->additionalServices = $baseline->additionalServices;
$changed = true;
}
if (false === $this->areServiceListsEqual($participant->board, $baseline->board)) {
$participant->board = $baseline->board;
$changed = true;
}
if (false === $this->isSameService($participant->veg, $baseline->veg)) {
$participant->veg = $baseline->veg;
$changed = true;
}
if (false === $this->isSameService($participant->skiPass, $baseline->skiPass)) {
$participant->skiPass = $baseline->skiPass;
$changed = true;
}
if (false === $this->isSameService($participant->rentalInsurance, $baseline->rentalInsurance)) {
$participant->rentalInsurance = $baseline->rentalInsurance;
$changed = true;
}
if ($participant->rentalInsuranceSelected !== $baseline->rentalInsuranceSelected) {
$participant->rentalInsuranceSelected = $baseline->rentalInsuranceSelected;
$changed = true;
}
return $changed;
}
private function reconcileRentals(ParticipantDto $participant, ParticipantDto $baseline): bool
{
if (false === $this->areServiceListsEqual($participant->rentals, $baseline->rentals)) {
$participant->rentals = $baseline->rentals;
return true;
}
return false;
}
private function reconcileTransportationServices(ParticipantDto $participant, ParticipantDto $baseline): bool
{
$changed = false;
if (false === $this->isSameService($participant->transportationOutbound, $baseline->transportationOutbound)) {
$participant->transportationOutbound = $baseline->transportationOutbound;
$changed = true;
}
if (false === $this->isSameService($participant->transportationInbound, $baseline->transportationInbound)) {
$participant->transportationInbound = $baseline->transportationInbound;
$changed = true;
}
if ($participant->parking !== $baseline->parking) {
$participant->parking = $baseline->parking;
$changed = true;
}
if (false === $this->isSameService($participant->parkingService, $baseline->parkingService)) {
$participant->parkingService = $baseline->parkingService;
$changed = true;
}
return $changed;
}
private function reconcilePickups(ParticipantDto $participant, ParticipantDto $baseline): bool
{
$changed = false;
if (false === $this->isSamePickup($participant->pickup, $baseline->pickup)) {
$participant->pickup = $baseline->pickup;
$changed = true;
}
if (false === $this->isSamePickup($participant->dropOff, $baseline->dropOff)) {
$participant->dropOff = $baseline->dropOff;
$changed = true;
}
if ($participant->differentDropOff !== $baseline->differentDropOff) {
$participant->differentDropOff = $baseline->differentDropOff;
$changed = true;
}
return $changed;
}
/**
* @param list<Service> $left
* @param list<Service> $right
*/
private function areServiceListsEqual(array $left, array $right): bool
{
return $this->normalizeServiceIds($left) === $this->normalizeServiceIds($right);
}
/**
* @param Service[] $services
*
* @return int[]
*/
private function normalizeServiceIds(array $services): array
{
$ids = [];
foreach ($services as $service) {
$ids[] = $service->id;
}
$ids = array_values(array_unique($ids));
sort($ids);
return $ids;
}
private function isSameService(?Service $left, ?Service $right): bool
{
return $left?->id === $right?->id;
}
private function isSamePickup(?Pickup $left, ?Pickup $right): bool
{
return $left?->id === $right?->id;
}
}