fix: map live service status to export wording
This commit is contained in:
1 parent
d8fe9a3875
commit
09610889f3
7 files changed
+129
-11
No files matched your search
@@ -307,10 +307,10 @@ class TravelLoader extends AbstractLoader
|
||||
$availability = $serviceAvailabilities[$service->id];
|
||||
$service->available = $availability->available;
|
||||
|
||||
// The live status is the only signal that a contingent has run out: BusPro moves a
|
||||
// Leistung to 'Anfrage' without necessarily reporting frei="0". Only overwrite when the
|
||||
// response actually carries one, so a response that omits the attribute does not wipe
|
||||
// the value parsed from the XML export. Mirrors TravelSnapshotManager::applyExtendedToService().
|
||||
// The live status is fresher than the nightly export's. The parser has already mapped it
|
||||
// onto the export vocabulary. Only overwrite when the response actually carries one, so a
|
||||
// response that omits the attribute does not wipe the value parsed from the XML export.
|
||||
// Mirrors TravelSnapshotManager::applyExtendedToService().
|
||||
if (null !== $availability->status && '' !== trim($availability->status)) {
|
||||
$service->status = $availability->status;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\Traits\TypeConversionTrait;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
@@ -9,6 +10,19 @@ abstract class AbstractParser
|
||||
{
|
||||
use TypeConversionTrait;
|
||||
|
||||
/**
|
||||
* Live availability wording for a Leistung, mapped onto the XML export's.
|
||||
*
|
||||
* The availability endpoints answer "buchbar" or "ausgebucht", while every availability rule
|
||||
* compares against the export's "Frei" / "Anfrage" / "Buchungsstop". "ausgebucht" also covers
|
||||
* a Leistung that would still take an inquiry (anfragemoeglich="J"); that is not offered
|
||||
* either, so it maps to the booking stop.
|
||||
*/
|
||||
private const LIVE_SERVICE_STATUS = [
|
||||
'buchbar' => Constants::STATUS_AVAILABLE,
|
||||
'ausgebucht' => Constants::STATUS_BLOCKED,
|
||||
];
|
||||
|
||||
protected function getStringOrNullValue(Crawler $node): ?string
|
||||
{
|
||||
return 0 < $node->count() ? $node->text() : null;
|
||||
@@ -87,4 +101,16 @@ abstract class AbstractParser
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a live Leistung status into the export vocabulary; anything else passes through.
|
||||
*/
|
||||
protected function toServiceStatus(?string $liveStatus): ?string
|
||||
{
|
||||
if (null === $liveStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::LIVE_SERVICE_STATUS[strtolower(trim($liveStatus))] ?? $liveStatus;
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ class AvailabilitiesParser extends AbstractParser
|
||||
->each(function (Crawler $node) use (&$availabilities) {
|
||||
$availability = new Availability();
|
||||
$availability->serviceId = (int) $node->attr('id');
|
||||
$availability->status = $node->attr('status');
|
||||
$availability->status = $this->toServiceStatus($node->attr('status'));
|
||||
$availability->available = (int) $node->attr('frei');
|
||||
$availability->price = $this->stringToFloat($node->attr('preis'));
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class ExtendedAvailabilitiesParser extends AbstractParser
|
||||
|
||||
$availability = new ExtendedAvailability();
|
||||
$availability->serviceId = $serviceId;
|
||||
$availability->status = $node->attr('status');
|
||||
$availability->status = $this->toServiceStatus($node->attr('status'));
|
||||
$availability->available = (int) $node->attr('frei');
|
||||
$availability->price = $this->stringToFloat($node->attr('preis'));
|
||||
$availability->dateFrom = $this->stringToDate($node->attr('termin'));
|
||||
|
||||
@@ -19,9 +19,9 @@ use Symfony\Contracts\Cache\CacheInterface;
|
||||
/**
|
||||
* Tests that patchAvailabilities() carries the live service status, not just the contingent.
|
||||
*
|
||||
* When a Leistung's contingent runs out BusPro moves it to 'Anfrage' without necessarily
|
||||
* reporting frei="0", so the status is the only reliable signal. Dropping it meant every
|
||||
* availability rule read the status from the nightly XML export instead.
|
||||
* Dropping the status meant every availability rule read it from the nightly XML export instead.
|
||||
* The statuses here are already in export vocabulary; the parsers translate BusPro's live
|
||||
* "buchbar" / "ausgebucht" (see AvailabilitiesParserTest).
|
||||
*/
|
||||
class TravelLoaderAvailabilityTest extends TestCase
|
||||
{
|
||||
@@ -45,10 +45,10 @@ class TravelLoaderAvailabilityTest extends TestCase
|
||||
|
||||
$this->loader->patchAvailabilities(
|
||||
$travel,
|
||||
$this->createResponse([$this->createAvailability(60, Constants::STATUS_ON_REQUEST, 3)])
|
||||
$this->createResponse([$this->createAvailability(60, Constants::STATUS_BLOCKED, 3)])
|
||||
);
|
||||
|
||||
$this->assertSame(Constants::STATUS_ON_REQUEST, $service->status);
|
||||
$this->assertSame(Constants::STATUS_BLOCKED, $service->status);
|
||||
$this->assertSame(3, $service->available);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\XmlCrawlerFactory;
|
||||
use App\BusProNet\XmlParser\AvailabilitiesParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* The service lines are taken from recorded VERFUEGBARKEIT responses (travels 12855 and 12833).
|
||||
*/
|
||||
class AvailabilitiesParserTest extends TestCase
|
||||
{
|
||||
private AvailabilitiesParser $parser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->parser = new AvailabilitiesParser();
|
||||
}
|
||||
|
||||
public function testMapsLiveServiceStatusOntoExportVocabulary(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?>
|
||||
<ergebnis>
|
||||
<satz typ="VERFUEGBARKEIT"/>
|
||||
<reise id="12855" status="frei" buchungstatusmoeglich="FOA"/>
|
||||
<leistungen>
|
||||
<leistung id="201200" bezeichnung="Begleitperson (gemäß Gruppenregelung)" art="SON" unterart="SON" status="buchbar" frei="3" preis="0,00" preisfaellig="Einmalig"/>
|
||||
<leistung id="201590" bezeichnung="Begleitperson (gemäß Gruppenregelung)" art="SON" unterart="SON" status="ausgebucht" anfragemoeglich="J" frei="0" preis="0,00" preisfaellig="Einmalig"/>
|
||||
</leistungen>
|
||||
</ergebnis>';
|
||||
|
||||
$services = $this->parser->parseServices(XmlCrawlerFactory::create($xml))->getServices();
|
||||
|
||||
$this->assertSame(Constants::STATUS_AVAILABLE, $services[201200]->status);
|
||||
$this->assertSame(3, $services[201200]->available);
|
||||
$this->assertSame(Constants::STATUS_BLOCKED, $services[201590]->status);
|
||||
$this->assertSame(0, $services[201590]->available);
|
||||
}
|
||||
|
||||
public function testPassesExportVocabularyAndUnknownStatusThrough(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?>
|
||||
<ergebnis>
|
||||
<leistungen>
|
||||
<leistung id="1" status="Anfrage" frei="2" preis="0,00"/>
|
||||
<leistung id="2" status="unbekannt" frei="2" preis="0,00"/>
|
||||
<leistung id="3" frei="2" preis="0,00"/>
|
||||
</leistungen>
|
||||
</ergebnis>';
|
||||
|
||||
$services = $this->parser->parseServices(XmlCrawlerFactory::create($xml))->getServices();
|
||||
|
||||
$this->assertSame(Constants::STATUS_ON_REQUEST, $services[1]->status);
|
||||
$this->assertSame('unbekannt', $services[2]->status);
|
||||
$this->assertNull($services[3]->status);
|
||||
}
|
||||
|
||||
public function testLeavesTravelLevelStatusUntouched(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?>
|
||||
<ergebnis>
|
||||
<reise id="12855" status="frei" buchungstatusmoeglich="FOA"/>
|
||||
<leistungen/>
|
||||
</ergebnis>';
|
||||
|
||||
$response = $this->parser->parseServices(XmlCrawlerFactory::create($xml));
|
||||
|
||||
$this->assertSame('frei', $response->travelStatus);
|
||||
$this->assertSame(['F', 'O', 'A'], $response->allowedBookingStatus);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\XmlCrawlerFactory;
|
||||
use App\BusProNet\XmlParser\ExtendedAvailabilitiesParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -48,6 +49,22 @@ class ExtendedAvailabilitiesParserTest extends TestCase
|
||||
$this->assertTrue($availability->mandatory);
|
||||
}
|
||||
|
||||
public function testMapsLiveServiceStatusOntoExportVocabulary(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?>
|
||||
<ergebnis>
|
||||
<leistungen>
|
||||
<leistung id="103" status="buchbar" frei="3" preis="0,00" />
|
||||
<leistung id="104" status="ausgebucht" anfragemoeglich="J" frei="0" preis="0,00" />
|
||||
</leistungen>
|
||||
</ergebnis>';
|
||||
|
||||
$services = $this->parser->parseServices(XmlCrawlerFactory::create($xml))->getServices();
|
||||
|
||||
$this->assertSame(Constants::STATUS_AVAILABLE, $services[103]->status);
|
||||
$this->assertSame(Constants::STATUS_BLOCKED, $services[104]->status);
|
||||
}
|
||||
|
||||
public function testIgnoresEmptyAgeAndMandatoryAttributes(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
Reference in new issue
Block a user