feat: add paginated travel export endpoint for the teamer app
GET /api/travels/export flattens the XML export into one row per travel
date x hotel with its pickups resolved, so the Teamer app can import the
disposition in one walk instead of fanning out over /api/travels.
Built from the XML file map, not travel_snapshot: the snapshot table sits
at this grain but refreshes on its own schedule, while the loaders share
the bpn.cache pool under the xml-sync tag. That is what makes /api/hotels,
/api/pickups and this export move together for free.
Each Ziel_*.xml is walked once and TravelParser::getPickups() does the
zeit parsing, which buys both zeit shapes and adjustOvernightTimes() --
a time-only 02:15 listed after a 23:59 rolls onto the next calendar day.
Pickups join against PickupLoader::loadAll() rather than
patchPickupsDetails(), which sorts: the contract is source order.
The whole assembled export is cached, not a page, so skipped, total and
pickupsWithoutMasterData are whole-export figures identical on every page
and a consumer can abort after the first one. Errors stay real status
codes; the builder is deliberately not wrapped in a catch that would
answer 200 with an error body the way hotelAvailability() does.
The route is declared before api_travel_single_code and that route grows
a requirement, or /api/travels/export resolves to byCode('export') and
404s as though the endpoint existed with no data.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
0a5602fd41
commit
c5685e6d7c
9 files changed
+774
No files matched your search
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<reisen>
|
||||
<reise code="SBW">
|
||||
<!-- Two hotels on one date: the cross product must produce one row per pair. -->
|
||||
<termin id="1" idbuspro="900" idprodukt="1" termin="26.12.2026" bis="30.12.2026" code="SBW261226" reiseart="F">
|
||||
<text> Semesterende - Portes du Soleil </text>
|
||||
<hotel idbuspro="1" code="H1"/>
|
||||
<hotel idbuspro="2" code="H2"/>
|
||||
<zustiege>
|
||||
<zustieg id="1" idbuspro="10" zeit="26.12.2026 23:59:00"/>
|
||||
<!-- Time only, and earlier than the 23:59 above: rolls onto the next calendar day. -->
|
||||
<zustieg id="2" idbuspro="11" zeit="02:15"/>
|
||||
<!-- No zeit at all: the entry survives with both time fields null. -->
|
||||
<zustieg id="3" idbuspro="12"/>
|
||||
<!-- Referenced but absent from zustiege.xml. -->
|
||||
<zustieg id="4" idbuspro="99" zeit="26.12.2026 21:00:00"/>
|
||||
</zustiege>
|
||||
</termin>
|
||||
<!-- Summer date, to pin the +02:00 offset. -->
|
||||
<termin id="2" idbuspro="901" termin="15.07.2027" bis="22.07.2027" code="SBW270715">
|
||||
<text>Sommer</text>
|
||||
<hotel idbuspro="1" code="H1"/>
|
||||
<zustiege>
|
||||
<zustieg id="1" idbuspro="10" zeit="15.07.2027 08:00:00"/>
|
||||
<!-- Time only and after the full datetime above: stays on the departure date. -->
|
||||
<zustieg id="2" idbuspro="11" zeit="09:30"/>
|
||||
</zustiege>
|
||||
</termin>
|
||||
<!-- Hotel absent from hotel.xml: the row is dropped and counted. -->
|
||||
<termin id="3" idbuspro="902" termin="01.02.2027" bis="08.02.2027" code="SBW270201">
|
||||
<text>Ohne Hotel</text>
|
||||
<hotel idbuspro="404" code="GHOST"/>
|
||||
</termin>
|
||||
<!-- Unparseable date: both its hotel rows are dropped and counted. -->
|
||||
<termin id="4" idbuspro="903" termin="kaputt" bis="08.02.2027" code="SBW270301">
|
||||
<text>Kaputtes Datum</text>
|
||||
<hotel idbuspro="1" code="H1"/>
|
||||
<hotel idbuspro="2" code="H2"/>
|
||||
</termin>
|
||||
</reise>
|
||||
</reisen>
|
||||
Reference in new issue
Block a user