feat: streamlined booking number property

This commit is contained in:
Björn Fromme
2026-03-16 12:02:27 +01:00
parent 99190b0ef1
commit bdcd54687d
7 changed files with 71 additions and 23 deletions
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260110105253 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add booking_number column to booking_edit_draft table';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE booking_edit_draft ADD booking_number INT DEFAULT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE booking_edit_draft DROP booking_number');
}
}
+6 -6
View File
@@ -16,15 +16,15 @@ namespace App\BusProNet\Model;
class BookingResponse class BookingResponse
{ {
/** /**
* @param string $status Booking status (möglich|erfolgt) * @param string $status Booking status (möglich|erfolgt)
* @param string|null $transactionNumber Transaction number (vorgang) * @param int|null $bookingNumber Booking number (BPN XML: vorgang)
* @param array<int, PriceItem> $priceItems Individual price items from response * @param array<int, PriceItem> $priceItems Individual price items from response
* @param float|null $totalPrice Total price (gesamtpreis) * @param float|null $totalPrice Total price (gesamtpreis)
* @param PaymentTerms|null $paymentTerms Payment terms (anzahlung/restzahlung) * @param PaymentTerms|null $paymentTerms Payment terms (anzahlung/restzahlung)
*/ */
public function __construct( public function __construct(
public readonly string $status, public readonly string $status,
public readonly ?string $transactionNumber = null, public readonly ?int $bookingNumber = null,
public readonly array $priceItems = [], public readonly array $priceItems = [],
public readonly ?float $totalPrice = null, public readonly ?float $totalPrice = null,
public readonly ?PaymentTerms $paymentTerms = null, public readonly ?PaymentTerms $paymentTerms = null,
@@ -33,7 +33,7 @@ class BookingResponseParser extends AbstractParser
public function parse(Crawler $node): BookingResponse public function parse(Crawler $node): BookingResponse
{ {
$status = $node->filterXPath('//buchung')->text(); $status = $node->filterXPath('//buchung')->text();
$transactionNumber = $this->getStringOrNullValue($node->filterXPath('//vorgang')); $bookingNumber = $this->getIntOrNullValue($node->filterXPath('//vorgang'));
$totalPrice = $this->getFloatOrNullValue($node->filterXPath('//gesamtpreis')); $totalPrice = $this->getFloatOrNullValue($node->filterXPath('//gesamtpreis'));
$message = $this->getStringOrNullValue($node->filterXPath('//hinweis')); $message = $this->getStringOrNullValue($node->filterXPath('//hinweis'));
@@ -42,7 +42,7 @@ class BookingResponseParser extends AbstractParser
return new BookingResponse( return new BookingResponse(
status: $status, status: $status,
transactionNumber: $transactionNumber, bookingNumber: $bookingNumber,
priceItems: $priceItems, priceItems: $priceItems,
totalPrice: $totalPrice, totalPrice: $totalPrice,
paymentTerms: $paymentTerms, paymentTerms: $paymentTerms,
@@ -16,10 +16,10 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand( #[AsCommand(
name: 'app:draft:backfill-travel-date', name: 'app:draft:backfill-booking-number',
description: 'Backfill travel dates for existing drafts from API', description: 'Backfill booking numbers (vorgang) for existing drafts from API',
)] )]
class DraftBackfillTravelDateCommand extends Command class DraftBackfillBookingNumberCommand extends Command
{ {
public function __construct( public function __construct(
private readonly BookingEditDraftRepository $draftRepository, private readonly BookingEditDraftRepository $draftRepository,
@@ -43,10 +43,10 @@ class DraftBackfillTravelDateCommand extends Command
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
$dryRun = $input->getOption('dry-run'); $dryRun = $input->getOption('dry-run');
$drafts = $this->draftRepository->findAll(); $drafts = $this->draftRepository->findBy(['bookingNumber' => null]);
if (0 === \count($drafts)) { if (0 === \count($drafts)) {
$io->success('No drafts found.'); $io->success('No drafts without booking number found.');
return Command::SUCCESS; return Command::SUCCESS;
} }
@@ -70,10 +70,11 @@ class DraftBackfillTravelDateCommand extends Command
continue; continue;
} }
$travelDate = $bookingData->travelDate; // bookingNumber maps to BPN XML vorgang
$bookingNumber = $bookingData->bookingNumber;
if (null === $travelDate) { if (null === $bookingNumber) {
$io->warning(sprintf('No travel date found for booking %d', $bookingId)); $io->warning(sprintf('No booking number (vorgang) found for booking %d', $bookingId));
++$skipped; ++$skipped;
continue; continue;
@@ -81,13 +82,13 @@ class DraftBackfillTravelDateCommand extends Command
if ($dryRun) { if ($dryRun) {
$io->text(sprintf( $io->text(sprintf(
'[DRY RUN] Would set travel date %s for draft %d (booking %d)', '[DRY RUN] Would set booking number %d for draft %d (bookingId %d)',
$travelDate->format('Y-m-d'), $bookingNumber,
$draft->getId(), $draft->getId(),
$bookingId $bookingId
)); ));
} else { } else {
$draft->setTravelDate($travelDate); $draft->setBookingNumber($bookingNumber);
} }
++$updated; ++$updated;
@@ -103,7 +104,7 @@ class DraftBackfillTravelDateCommand extends Command
[ [
['Updated', $updated], ['Updated', $updated],
['Failed (API error)', $failed], ['Failed (API error)', $failed],
['Skipped (no date)', $skipped], ['Skipped (no booking number)', $skipped],
] ]
); );
@@ -93,14 +93,14 @@ class Step4Controller extends AbstractController
} }
// Success: Store booking number in flash and clear session // Success: Store booking number in flash and clear session
$this->addFlash('booking_number', $bookingResponse->transactionNumber); $this->addFlash('booking_number', $bookingResponse->bookingNumber);
$this->clearTravelDataCache($bookingCreateDto); $this->clearTravelDataCache($bookingCreateDto);
$this->bookingService->clearBookingDto($request, BookingDto::MODE_CREATE); $this->bookingService->clearBookingDto($request, BookingDto::MODE_CREATE);
$this->logger->info('Booking successfully created.', [ $this->logger->info('Booking successfully created.', [
'date_id' => $bookingCreateDto->travel->id, 'date_id' => $bookingCreateDto->travel->id,
'hotel_id' => $bookingCreateDto->hotelId, 'hotel_id' => $bookingCreateDto->hotelId,
'booking_number' => $bookingResponse->transactionNumber, 'booking_number' => $bookingResponse->bookingNumber,
]); ]);
return $this->redirectToRoute('app_booking_create_success'); return $this->redirectToRoute('app_booking_create_success');
+21
View File
@@ -22,9 +22,18 @@ class BookingEditDraft
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private User $user; private User $user;
/**
* Internal database ID (BPN XML: idbuchung).
*/
#[ORM\Column(type: 'integer')] #[ORM\Column(type: 'integer')]
private int $bookingId; private int $bookingId;
/**
* User-facing transaction number (BPN XML: vorgang).
*/
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $bookingNumber = null;
#[ORM\Column(type: 'date_immutable')] #[ORM\Column(type: 'date_immutable')]
private \DateTimeImmutable $travelDate; private \DateTimeImmutable $travelDate;
@@ -62,6 +71,18 @@ class BookingEditDraft
return $this->bookingId; return $this->bookingId;
} }
public function getBookingNumber(): ?int
{
return $this->bookingNumber;
}
public function setBookingNumber(?int $bookingNumber): static
{
$this->bookingNumber = $bookingNumber;
return $this;
}
public function getTravelDate(): \DateTimeImmutable public function getTravelDate(): \DateTimeImmutable
{ {
return $this->travelDate; return $this->travelDate;
@@ -38,7 +38,7 @@ class BookingResponseParserTest extends TestCase
$response = $this->parser->parse($crawler->filter('ergebnis')); $response = $this->parser->parse($crawler->filter('ergebnis'));
$this->assertTrue($response->isInquiryValid()); $this->assertTrue($response->isInquiryValid());
$this->assertEquals('321530', $response->transactionNumber); $this->assertEquals(321530, $response->bookingNumber);
$this->assertEquals(755.0, $response->totalPrice); $this->assertEquals(755.0, $response->totalPrice);
// Verify purchase voucher is parsed // Verify purchase voucher is parsed