feat: implement mutability cutoff for rentals
addresses #869cca42x
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Service\Condition;
|
||||
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Service\Contract\FieldConditionInterface;
|
||||
use Carbon\CarbonImmutable;
|
||||
|
||||
/**
|
||||
* Condition that becomes true once a configurable day-based cutoff is reached.
|
||||
*
|
||||
* Cutoff semantics are inclusive and day-based:
|
||||
* now(startOfDay) >= travelStart(startOfDay) - N days.
|
||||
*/
|
||||
class TravelStartCutoffReachedCondition implements FieldConditionInterface
|
||||
{
|
||||
public const int DEFAULT_DAYS_BEFORE_START = 4;
|
||||
|
||||
/**
|
||||
* @param int $daysBeforeStart Number of days before travel start when the cutoff is reached
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly int $daysBeforeStart,
|
||||
) {
|
||||
if ($daysBeforeStart < 0) {
|
||||
throw new \InvalidArgumentException('daysBeforeStart must be greater than or equal to 0.');
|
||||
}
|
||||
}
|
||||
|
||||
public function evaluate(BookingDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
{
|
||||
if (null === $bookingDto->travel->dateFrom) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$travelStartDate = CarbonImmutable::instance($bookingDto->travel->dateFrom)->startOfDay();
|
||||
$cutoffDate = $travelStartDate->subDays($this->daysBeforeStart);
|
||||
$today = CarbonImmutable::now()->startOfDay();
|
||||
|
||||
return $today->greaterThanOrEqualTo($cutoffDate);
|
||||
}
|
||||
|
||||
public function getDependentFields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return sprintf(
|
||||
'Cutoff reached from %d day(s) before travel start (inclusive)',
|
||||
$this->daysBeforeStart
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ use App\Form\Service\Condition\RoomSelectionCondition;
|
||||
use App\Form\Service\Condition\ServiceSubTypeCondition;
|
||||
use App\Form\Service\Condition\SingleRoomTypeCondition;
|
||||
use App\Form\Service\Condition\SkiPassSelectionCondition;
|
||||
use App\Form\Service\Condition\TravelStartCutoffReachedCondition;
|
||||
use App\Service\ParticipantEligibilityService;
|
||||
|
||||
/**
|
||||
@@ -79,6 +80,10 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
||||
|
||||
$rentalCondition = new RentalSelectionCondition();
|
||||
$skiPassCondition = new SkiPassSelectionCondition();
|
||||
// Booking-level D-4 cutoff used to lock mutable service selections close to departure.
|
||||
$serviceCutoffReachedCondition = new TravelStartCutoffReachedCondition(
|
||||
TravelStartCutoffReachedCondition::DEFAULT_DAYS_BEFORE_START
|
||||
);
|
||||
|
||||
// First participant read-only condition
|
||||
// Render personal data fields as static text for first participant in non-internal agency bookings
|
||||
@@ -177,13 +182,15 @@ class CreateFieldStateProvider extends AbstractFieldStateProvider
|
||||
),
|
||||
];
|
||||
|
||||
// Show rentals only when both date of birth is provided AND skipass is selected AND participant is eligible
|
||||
// Show rentals only when both date of birth is provided AND skipass is selected AND participant is eligible.
|
||||
// From D-4 onward, keep rentals visible but make them read-only.
|
||||
$this->fieldStateConditions['rentals'] = [
|
||||
'hidden' => CompositeCondition::or(
|
||||
CompositeCondition::not($dateOfBirthProvidedCondition),
|
||||
CompositeCondition::not($skiPassCondition),
|
||||
$bookingEligibilityCondition
|
||||
),
|
||||
'readonly' => $serviceCutoffReachedCondition,
|
||||
];
|
||||
|
||||
$this->fieldStateConditions['board'] = [
|
||||
|
||||
@@ -22,6 +22,7 @@ use App\Form\Service\Condition\RentalSelectionCondition;
|
||||
use App\Form\Service\Condition\RoomSelectionCondition;
|
||||
use App\Form\Service\Condition\ServiceSubTypeCondition;
|
||||
use App\Form\Service\Condition\SkiPassSelectionCondition;
|
||||
use App\Form\Service\Condition\TravelStartCutoffReachedCondition;
|
||||
use App\Form\Service\Condition\TransportationServicesMutabilityCondition;
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,10 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
||||
$additionalServicesMutabilityCondition = new AdditionalServicesMutabilityCondition();
|
||||
$transportationServicesMutabilityCondition = new TransportationServicesMutabilityCondition();
|
||||
$pickupsMutabilityCondition = new PickupsMutabilityCondition();
|
||||
// Booking-level D-4 cutoff used in addition to BPN mutability flags.
|
||||
$serviceCutoffReachedCondition = new TravelStartCutoffReachedCondition(
|
||||
TravelStartCutoffReachedCondition::DEFAULT_DAYS_BEFORE_START
|
||||
);
|
||||
|
||||
// Personal data protection in edit mode:
|
||||
// 1. First participant in non-internal agency booking - always read-only (first participant = applicant)
|
||||
@@ -155,14 +160,18 @@ class EditFieldStateProvider extends AbstractFieldStateProvider
|
||||
'readonly' => $additionalServicesMutabilityCondition,
|
||||
];
|
||||
|
||||
// Rentals - shown only when skipass selected, readonly if services not mutable
|
||||
// Rentals - shown only when skipass selected.
|
||||
// Read-only when either BPN additional services are immutable OR the D-4 cutoff is reached.
|
||||
// For first participant: only check skipass, not DOB
|
||||
$this->fieldStateConditions['rentals'] = [
|
||||
'hidden' => CompositeCondition::or(
|
||||
$hideUntilDobCondition,
|
||||
CompositeCondition::not($skiPassCondition)
|
||||
),
|
||||
'readonly' => $additionalServicesMutabilityCondition,
|
||||
'readonly' => CompositeCondition::or(
|
||||
$additionalServicesMutabilityCondition,
|
||||
$serviceCutoffReachedCondition
|
||||
),
|
||||
];
|
||||
|
||||
// Rental insurance - shown only when rentals selected AND LVS services exist, readonly if services not mutable
|
||||
|
||||
Reference in New Issue
Block a user