fix: refuse the import while MyE&P reports no cost unit or country code

This commit is contained in:
fromme committed 2026-09-29 11:08:04 +02:00
1 parent 2e9328fc51
commit 6c0a512c05
2 files changed
+215 -18

No files matched your search

+68 -18
View File
@@ -4,6 +4,7 @@ namespace App\Command;
use App\BusProNet\DataProvider\HotelDataProvider;
use App\BusProNet\DataProvider\PickupDataProvider;
use App\BusProNet\Model\Hotel;
use Doctrine\DBAL\Connection;
use League\Flysystem\FilesystemException;
use League\Flysystem\FilesystemOperator;
@@ -35,6 +36,36 @@ class BpnImportCommand extends Command
{
$io = new SymfonyStyle($input, $output);
// Resolve the base data once, before any file is read. A destination cannot be
// written without its hotel, so an unavailable hotel list would otherwise skip every
// record and still report success. Holding both lists locally also keeps the loop off
// the providers, which would otherwise be asked per pickup of all 174 files.
$hotels = $this->hotelDataProvider->getAll();
$pickups = $this->pickupDataProvider->getAll();
if ([] === $hotels) {
$io->error('Hotel-Stammdaten konnten nicht von MyE&P geladen werden – Import abgebrochen.');
$this->logger->error('Import aborted: hotel base data unavailable');
return Command::FAILURE;
}
// Every destination this import touches gets its cost unit and its country written
// from the hotel, on update as well as on insert. If MyE&P does not report those two
// fields yet, a run would quietly blank them on rows that have them: the cost unit
// then drops off the invoices (App\Service\Pdf\InvoiceApprover) and a missing
// country code blanks the flag icons and takes the row out of the country filters.
// Refusing to run is the recoverable failure; overwriting good data is not.
if ([] !== $missingFields = $this->missingHotelFields($hotels)) {
$io->error(sprintf(
'Die MyE&P-API liefert keine %s – Import abgebrochen, damit bestehende Werte nicht überschrieben werden.',
implode(' und ', $missingFields),
));
$this->logger->error('Import aborted: the MyE&P hotel payload carries no '.implode(', ', $missingFields));
return Command::FAILURE;
}
// Get all xml files
try {
$xmlFiles = $this
@@ -56,28 +87,13 @@ class BpnImportCommand extends Command
return Command::FAILURE;
}
// Resolve the BusProNet base data once, before touching a single row. A destination
// cannot be written without its hotel, so an unavailable hotel list would otherwise
// skip every record and still report success. Holding both lists locally also keeps
// the loop off the providers: on a failed fetch nothing is cached, and a per-lookup
// ->get() would re-open the socket for every pickup of all 174 files.
$hotels = $this->hotelDataProvider->getAll();
$pickups = $this->pickupDataProvider->getAll();
if ([] === $hotels) {
$io->error('Hotel-Stammdaten konnten nicht von BusProNet geladen werden – Import abgebrochen.');
$this->logger->error('BPN import aborted: hotel base data unavailable');
return Command::FAILURE;
}
$io->info('Found '.$totalCount.' XML files');
$addedCount = 0;
$updatedCount = 0;
$warnings = [];
if ([] === $pickups) {
$warnings[] = 'Zustiegs-Stammdaten konnten nicht von BusProNet geladen werden';
$warnings[] = 'Zustiegs-Stammdaten konnten nicht von MyE&P geladen werden';
}
$progressBar = $io->createProgressBar($totalCount);
@@ -144,7 +160,7 @@ class BpnImportCommand extends Command
'product' => $product,
'pickups' => json_encode($datePickups),
'cost_unit' => $hotel->getCostUnit(),
'country' => $hotel->getCountry(),
'country' => $hotel->getCountryCode(),
'updated_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'),
], [
'id' => $id,
@@ -166,7 +182,7 @@ class BpnImportCommand extends Command
'hotel_bus_pro_id' => $hotelBusProId,
'pickups' => json_encode($datePickups),
'cost_unit' => $hotel->getCostUnit(),
'country' => $hotel->getCountry(),
'country' => $hotel->getCountryCode(),
'created_at' => (new \DateTimeImmutable())->format('Y-m-d H:i:s'),
])
;
@@ -192,4 +208,38 @@ class BpnImportCommand extends Command
return Command::SUCCESS;
}
/**
* Names the hotel fields MyE&P is not reporting, German labels for the operator.
*
* Asserted across the whole list rather than per hotel, because a single hotel
* legitimately has neither: only a minority of them carry a kostenstelle upstream, and
* some carry no country at all. A list where not one hotel has the field is a field
* missing from the response, not a gap in the data.
*
* @param array<int, Hotel> $hotels
*
* @return list<string>
*/
private function missingHotelFields(array $hotels): array
{
$required = [
'Kostenstellen' => static fn (Hotel $hotel): bool => '' !== (string) $hotel->getCostUnit(),
'Länderkennungen' => static fn (Hotel $hotel): bool => '' !== (string) $hotel->getCountryCode(),
];
$missing = [];
foreach ($required as $label => $isReported) {
foreach ($hotels as $hotel) {
if ($isReported($hotel)) {
continue 2;
}
}
$missing[] = $label;
}
return $missing;
}
}