feat: additional confirmation cycle and status for groups inquiries
This commit is contained in:
@@ -169,7 +169,7 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setEmail('[email protected]');
|
||||
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||
$booking->setStatus(AccommodationBookingStatus::Confirmed);
|
||||
$booking->setAccessLinkIssuedAt(new \DateTimeImmutable());
|
||||
|
||||
$linkSigner = $this->createMock(AccommodationBookingLinkSigner::class);
|
||||
@@ -265,7 +265,7 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
self::assertNotSame($previousIssuedAt, $booking->getAccessLinkIssuedAt());
|
||||
}
|
||||
|
||||
public function testAcceptBookingAcceptsOpenInquiryAndSendsNotifications(): void
|
||||
public function testAcceptBookingMovesOpenInquiryToReceivedAndNotifiesTheOfficeOnly(): void
|
||||
{
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::once())->method('flush');
|
||||
@@ -274,22 +274,23 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||
$booking->setEmail('[email protected]');
|
||||
|
||||
// The customer hears nothing until the office has validated the booking.
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::exactly(2))
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->with(
|
||||
self::anything(),
|
||||
self::callback(static fn (array $options) => in_array($options['to'], ['[email protected]', '[email protected]'], true)
|
||||
self::callback(static fn (array $options) => '[email protected]' === $options['to']
|
||||
&& '[email protected]' === $options['from']
|
||||
&& in_array($options['template'], ['email/offer_accepted.html.twig', 'email/offer_accepted_customer.html.twig'], true)),
|
||||
&& 'email/offer_accepted.html.twig' === $options['template']),
|
||||
);
|
||||
|
||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer);
|
||||
|
||||
$service->acceptBooking($booking);
|
||||
|
||||
self::assertSame(AccommodationBookingStatus::Accepted, $booking->getStatus());
|
||||
self::assertSame(AccommodationBookingStatus::Received, $booking->getStatus());
|
||||
self::assertSame(AccommodationBookingType::Inquiry, $booking->getType(), 'an accepted offer stays an Anfrage');
|
||||
self::assertNotNull($booking->getAcceptedAt());
|
||||
}
|
||||
@@ -339,7 +340,7 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
public function testAcceptBookingDoesNotStoreRemarksWhenOfferIsNotOpen(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||
$booking->setStatus(AccommodationBookingStatus::Received);
|
||||
$booking->setRemarks('vom Telefonat');
|
||||
|
||||
$service = $this->createServiceWithAccommodation(mailer: $this->createMock(Mailer::class));
|
||||
@@ -349,32 +350,105 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
self::assertSame('vom Telefonat', $booking->getRemarks());
|
||||
}
|
||||
|
||||
public function testAcceptBookingSkipsTheCustomerEmailWhenNoAddressIsStored(): void
|
||||
public function testConfirmBookingConfirmsReceivedBookingAndNotifiesTheCustomer(): void
|
||||
{
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||
$booking->setStatus(AccommodationBookingStatus::Received);
|
||||
$booking->setEmail('[email protected]');
|
||||
|
||||
$linkSigner = $this->createMock(AccommodationBookingLinkSigner::class);
|
||||
$linkSigner->method('sign')->with($booking)->willReturn('https://example.com/offer/signed-link');
|
||||
|
||||
// Only the office notification goes out — the customer copy has no recipient.
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->with(
|
||||
self::anything(),
|
||||
self::callback(static fn (array $options) => 'office@example.com' === $options['to']),
|
||||
self::callback(static fn (array $context) => 'https://example.com/offer/signed-link' === $context['accessLink']),
|
||||
self::callback(static fn (array $options) => 'customer@example.com' === $options['to']
|
||||
&& 'Deine Buchung ist bestätigt' === $options['subject']
|
||||
&& 'email/booking_confirmed_customer.html.twig' === $options['template']),
|
||||
);
|
||||
|
||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer, linkSigner: $linkSigner);
|
||||
|
||||
$service->confirmBooking($booking);
|
||||
|
||||
self::assertSame(AccommodationBookingStatus::Confirmed, $booking->getStatus());
|
||||
self::assertNotNull($booking->getConfirmedAt());
|
||||
self::assertNotNull($booking->getAccessLinkIssuedAt(), 'the confirmation links to the booking view');
|
||||
}
|
||||
|
||||
public function testConfirmBookingNoOpsForAnythingNotAwaitingValidation(): void
|
||||
{
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects(self::never())->method('flush');
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->expects(self::never())->method('createAndSendEmail');
|
||||
|
||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer);
|
||||
|
||||
foreach ([AccommodationBookingStatus::Draft, AccommodationBookingStatus::Open, AccommodationBookingStatus::Confirmed, AccommodationBookingStatus::Discarded] as $status) {
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setStatus($status);
|
||||
$booking->setEmail('[email protected]');
|
||||
|
||||
$service->confirmBooking($booking);
|
||||
|
||||
self::assertSame($status, $booking->getStatus());
|
||||
self::assertNull($booking->getConfirmedAt());
|
||||
}
|
||||
}
|
||||
|
||||
public function testBookingEmailsCarryThePriceBreakdown(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setEmail('[email protected]');
|
||||
$booking->setStatus(AccommodationBookingStatus::Open);
|
||||
|
||||
$breakdown = ['total' => 10000, 'currency' => 'EUR'];
|
||||
$booking->setPriceSnapshot($breakdown, 9000, 'CHF', 1);
|
||||
$breakdownCalculator = $this->createMock(AccommodationBookingBreakdownCalculator::class);
|
||||
$breakdownCalculator->method('compute')->with($booking)->willReturn($breakdown);
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer
|
||||
->expects(self::once())
|
||||
->method('createAndSendEmail')
|
||||
->with(
|
||||
// The stored pricing currency wins over the one frozen in the breakdown.
|
||||
self::callback(static fn (array $context) => $breakdown === $context['priceBreakdown']
|
||||
&& 'CHF' === $context['currency']),
|
||||
self::anything(),
|
||||
);
|
||||
|
||||
$service = $this->createServiceWithAccommodation(mailer: $mailer, breakdownCalculator: $breakdownCalculator);
|
||||
|
||||
$service->sendCustomerConfirmationEmail($booking);
|
||||
}
|
||||
|
||||
public function testConfirmBookingSkipsTheCustomerEmailWhenNoAddressIsStored(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setStatus(AccommodationBookingStatus::Received);
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->expects(self::never())->method('createAndSendEmail');
|
||||
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
$logger
|
||||
->expects(self::once())
|
||||
->method('warning')
|
||||
->with('Failed to send offer accepted customer email', self::anything());
|
||||
->with('Failed to send booking confirmed customer email', self::anything());
|
||||
|
||||
$service = $this->createServiceWithAccommodation(mailer: $mailer, logger: $logger);
|
||||
|
||||
$service->acceptBooking($booking);
|
||||
$service->confirmBooking($booking);
|
||||
|
||||
self::assertSame(AccommodationBookingStatus::Accepted, $booking->getStatus(), 'the acceptance itself must not fail');
|
||||
self::assertSame(AccommodationBookingStatus::Confirmed, $booking->getStatus(), 'the confirmation itself must not fail');
|
||||
}
|
||||
|
||||
public function testAcceptBookingIsIdempotentAndSendsNoNotifications(): void
|
||||
@@ -388,7 +462,7 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
$service = $this->createServiceWithAccommodation(entityManager: $entityManager, mailer: $mailer);
|
||||
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||
$booking->setStatus(AccommodationBookingStatus::Received);
|
||||
|
||||
$service->acceptBooking($booking);
|
||||
|
||||
@@ -418,7 +492,7 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
public function testSendOfferAcceptedNotificationEmailLogsAndSwallowsMailerFailures(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||
$booking->setStatus(AccommodationBookingStatus::Received);
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->method('createAndSendEmail')->willThrowException(new \RuntimeException('SMTP down'));
|
||||
@@ -431,11 +505,11 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
$service->sendOfferAcceptedNotificationEmail($booking);
|
||||
}
|
||||
|
||||
public function testSendOfferAcceptedCustomerEmailLogsAndSwallowsMailerFailures(): void
|
||||
public function testSendBookingConfirmedCustomerEmailLogsAndSwallowsMailerFailures(): void
|
||||
{
|
||||
$booking = new AccommodationBooking();
|
||||
$booking->setEmail('[email protected]');
|
||||
$booking->setStatus(AccommodationBookingStatus::Accepted);
|
||||
$booking->setStatus(AccommodationBookingStatus::Confirmed);
|
||||
|
||||
$mailer = $this->createMock(Mailer::class);
|
||||
$mailer->method('createAndSendEmail')->willThrowException(new \RuntimeException('SMTP down'));
|
||||
@@ -445,7 +519,7 @@ class AccommodationBookingServiceTest extends TestCase
|
||||
|
||||
$service = $this->createServiceWithAccommodation(mailer: $mailer, logger: $logger);
|
||||
|
||||
$service->sendOfferAcceptedCustomerEmail($booking);
|
||||
$service->sendBookingConfirmedCustomerEmail($booking);
|
||||
}
|
||||
|
||||
public function testRefreshPriceSnapshotStoresDiscountedFinalTotal(): void
|
||||
|
||||
Reference in New Issue
Block a user