feat: pre-flight check of agency bookings
addresses #869bqxr4q
This commit is contained in:
@@ -12,6 +12,7 @@ use App\BusProNet\Model\Service;
|
||||
use App\Validator\Constraints as AppAssert;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use function Symfony\Component\String\u;
|
||||
|
||||
#[AppAssert\Participant(groups: ['booking_edit', 'booking_create'])]
|
||||
class ParticipantDto
|
||||
@@ -374,6 +375,37 @@ class ParticipantDto
|
||||
$this->shoeSize = $this->normalizeBodyDimensionValue($this->shoeSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes participant data loaded from API/session sources.
|
||||
*
|
||||
* This is deliberately limited to mechanical cleanup:
|
||||
* - trim whitespace
|
||||
* - convert empty strings to null
|
||||
* - normalize legacy body dimension values
|
||||
*
|
||||
* It does not fill business-required values.
|
||||
*/
|
||||
public function normalizeLoadedData(): void
|
||||
{
|
||||
$this->firstName = $this->normalizeNullableString($this->firstName);
|
||||
$this->lastName = $this->normalizeNullableString($this->lastName);
|
||||
$this->title = $this->normalizeNullableString($this->title);
|
||||
$this->gender = $this->normalizeNullableString($this->gender);
|
||||
$this->nationality = $this->normalizeNullableString($this->nationality);
|
||||
$this->email = $this->normalizeNullableString($this->email);
|
||||
$this->mobile = $this->normalizeNullableString($this->mobile);
|
||||
$this->remarksRoom = $this->normalizeNullableString($this->remarksRoom);
|
||||
$this->licensePlate = $this->normalizeNullableString($this->licensePlate);
|
||||
$this->purchaseVoucherCode = $this->normalizeNullableString($this->purchaseVoucherCode);
|
||||
$this->promoVoucherCode = $this->normalizeNullableString($this->promoVoucherCode);
|
||||
|
||||
if (null !== $this->address) {
|
||||
$this->address->normalize();
|
||||
}
|
||||
|
||||
$this->normalizeBodyDimensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the DTO from session/serialization data.
|
||||
*
|
||||
@@ -393,8 +425,7 @@ class ParticipantDto
|
||||
$this->address = new Address();
|
||||
}
|
||||
|
||||
// TODO: Remove after all legacy drafts/session snapshots with body-dimension strings have expired.
|
||||
$this->normalizeBodyDimensions();
|
||||
$this->normalizeLoadedData();
|
||||
}
|
||||
|
||||
private function normalizeBodyDimensionValue(mixed $value): ?string
|
||||
@@ -407,7 +438,7 @@ class ParticipantDto
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim($value);
|
||||
$value = u($value)->trim()->toString();
|
||||
if ('' === $value || false === ctype_digit($value)) {
|
||||
return null;
|
||||
}
|
||||
@@ -415,22 +446,37 @@ class ParticipantDto
|
||||
return (string) (int) $value;
|
||||
}
|
||||
|
||||
private function normalizeNullableString(?string $value): ?string
|
||||
{
|
||||
if (null === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$trimmed = u($value)->trim()->toString();
|
||||
|
||||
return '' === $trimmed ? null : $trimmed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the applicant (index 0) has a complete address.
|
||||
* Validates that the participant has a complete address when required.
|
||||
*
|
||||
* This callback only applies to the applicant participant. Address validation includes:
|
||||
* - Address object must exist
|
||||
* - All required address fields must be filled (street, postCode, city, country)
|
||||
* In regular create/edit submission flows this only applies to participant 0.
|
||||
* The edit overview preflight checks address fields separately for the applicant only.
|
||||
*/
|
||||
#[Assert\Callback(groups: ['strict_required'])]
|
||||
public function validateApplicantAddress(ExecutionContextInterface $context): void
|
||||
{
|
||||
// Only validate applicant's address
|
||||
// Only validate applicant's address in regular create/edit submission flows.
|
||||
if (0 !== $this->index) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Address object is required for applicant
|
||||
$this->validateAddressFields($context);
|
||||
}
|
||||
|
||||
private function validateAddressFields(ExecutionContextInterface $context): void
|
||||
{
|
||||
// Address object is required for the validated participant
|
||||
if (null === $this->address) {
|
||||
$context->buildViolation('Bitte angeben')
|
||||
->atPath('address')
|
||||
@@ -440,25 +486,25 @@ class ParticipantDto
|
||||
}
|
||||
|
||||
// Validate address subfields
|
||||
if (null === $this->address->street || '' === trim($this->address->street)) {
|
||||
if (null === $this->address->street || u($this->address->street)->trim()->isEmpty()) {
|
||||
$context->buildViolation('Bitte angeben')
|
||||
->atPath('address.street')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $this->address->postCode || '' === trim($this->address->postCode)) {
|
||||
if (null === $this->address->postCode || u($this->address->postCode)->trim()->isEmpty()) {
|
||||
$context->buildViolation('Bitte angeben')
|
||||
->atPath('address.postCode')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $this->address->city || '' === trim($this->address->city)) {
|
||||
if (null === $this->address->city || u($this->address->city)->trim()->isEmpty()) {
|
||||
$context->buildViolation('Bitte angeben')
|
||||
->atPath('address.city')
|
||||
->addViolation();
|
||||
}
|
||||
|
||||
if (null === $this->address->country || '' === trim($this->address->country)) {
|
||||
if (null === $this->address->country || u($this->address->country)->trim()->isEmpty()) {
|
||||
$context->buildViolation('Bitte angeben')
|
||||
->atPath('address.country')
|
||||
->addViolation();
|
||||
|
||||
Reference in New Issue
Block a user