383 lines
20 KiB
Markdown
383 lines
20 KiB
Markdown
# BusProNet XML API — working reference
|
||
|
||
Condensed from the vendor PDF `docs/bpn_docs.pdf` (41 pages, German), keeping what this project
|
||
uses or plausibly could. The PDF stays authoritative; a few of its sentences are mangled by a
|
||
broken font encoding, so anything unclear there is flagged rather than guessed at.
|
||
|
||
> **The PDF lags the live API.** The vendor has shipped elements since it was written —
|
||
> `<ausstiege>` (return boarding points) is one confirmed example. So *absent from the PDF* means
|
||
> "not in this snapshot", never "unsupported". Treat the document as a floor on what the API
|
||
> accepts, not a ceiling, and prefer a live `buchungsart=Anfrage` probe over an argument from
|
||
> silence.
|
||
|
||
Read this alongside `buspro-database-reference.md` (what BusPro actually stores) — the two answer
|
||
different questions, and where they disagree the schema is the record of what happened.
|
||
|
||
**Use it for:** what an element is called and what it may contain; which codes a failure can
|
||
return; which documented capabilities we are not using; and which elements we send that the
|
||
vendor never documented — those are the ones that can break without warning.
|
||
|
||
## Envelope
|
||
|
||
Every request is `<anfrage>` with `<user>`, `<key>` and `<satz typ="…"/>`.
|
||
|
||
```
|
||
key = md5( user + password + date("YYYYMMDD") + satz-typ )
|
||
```
|
||
|
||
Per satz type and per day. `ApiClient::createKey()` implements it, and `sendRawXml()` regenerates
|
||
the key before sending, so a hand-built payload can carry a placeholder.
|
||
|
||
## Satz types
|
||
|
||
**Documented:** `ADRESSFELDER`, `ADRESSFELDERGUTSCHEIN`, `ADRESSFELDERKATALOGANFRAGE`,
|
||
`AGENTUREN`, `AGENTURKONTO`, `AKTIONSGUTSCHEIN`, `BUCHUNG`, `BUCHUNGAENDERUNG`, `CRM`,
|
||
`CRMKATALOGANFRAGE`, `GUTSCHEINBUCHUNG`, `GUTSCHEINDRUCK`, `GUTSCHEINPRUEFUNGEINLOESUNG`,
|
||
`GUTSCHEINVORLAGEN`, `GUTSCHEINVORSCHAU`, `GUTSCHEINZAHLUNGSARTEN`, `KATALOGANFRAGE`,
|
||
`KUNDENKONTO`, `KUNDENLISTE`, `MOEGLICHEAENDERUNGEN`, `PRODUKTDATEN`, `PRODUKTE`, `SITZPLATZ`,
|
||
`STAMMHOTELS`, `STAMMLAENDER`, `STAMMVERSICHERUNGEN`, `STAMMZUSTIEGE`, `VERFUEGBARKEIT`,
|
||
`VERFUEGBARKEITHOTEL`, `ZAHLUNG`, `ZAHLUNG2`, `ZAHLUNGSARTEN`, `ZAHLUNGSARTEN2`.
|
||
`SITZPLATZAUSWAHL` and `ZAHLUNG4` are named in prose without their own section.
|
||
|
||
**Used here but undocumented:** `VERFUEGBARKEIT2` (`ApiClient::TYPE_AVAILABILITY_EXTENDED`) — the
|
||
extended availability call returning `buchungstatusmoeglich` and per-service `pflicht`/`frei`.
|
||
Its contract is observed, not specified.
|
||
|
||
**Documented but unused here:** `SITZPLATZ`/`SITZPLATZAUSWAHL`, `KUNDENLISTE`, `ZAHLUNG2`,
|
||
`ADRESSFELDER`, `STAMMHOTELS`, `STAMMVERSICHERUNGEN`, `STAMMZUSTIEGE`, `KATALOGANFRAGE`.
|
||
|
||
## BUCHUNG — the create contract
|
||
|
||
`<buchungsart>` decides what the call does:
|
||
|
||
| Value | Effect |
|
||
|---|---|
|
||
| `Anfrage` | Enquiry. Validates and **returns the full price breakdown**. Nothing is stored. |
|
||
| `Buchung` | Stores the booking. |
|
||
|
||
`<status>` at the root: `F` Festbuchung · `O` Optionsbuchung · `A` Anfragebuchung. Gated by
|
||
error `962` ("Buchungsstatus #1# kann nicht verarbeitet werden, nur Status #2# möglich"), and by
|
||
`982`/`983` when the travel forbids options or enquiries outright.
|
||
|
||
### Two accommodation models
|
||
|
||
The base payload books accommodation as an ordinary service:
|
||
|
||
```xml
|
||
<unterbringungen>
|
||
<unterbringung idleistung="22341" anzahl="1" zuordnung="1,2"/>
|
||
</unterbringungen>
|
||
```
|
||
|
||
For the **Ferienziel** domain — what this app books — `unterbringungen` is *replaced* by
|
||
`ferienzielunterbringungen`, **and** a root `<idpartner>` added:
|
||
|
||
```xml
|
||
<ferienzielunterbringungen>
|
||
<ferienzielunterbringung idzimmer="2" kategorie="Ik" idverpflegung="2"
|
||
anreise="30.01.2018" abreise="06.02.2018"
|
||
anzahl="1" zuordnung="1,2"/>
|
||
</ferienzielunterbringungen>
|
||
…
|
||
<idpartner>78687</idpartner>
|
||
```
|
||
|
||
> `<idpartner>` is introduced as the element you add when switching to the Ferienziel model, and
|
||
> `914 Partner-ID fehlt` guards it. Note it is **not** confined to bookings that have a room: a
|
||
> teamer booking with zero `BuchungTNFerienziel` rows still returns `<idpartner>222925</idpartner>`
|
||
> from `Vorgang_Details`. Where that comes from is **unresolved** — `Buchung` has no partner FK, so
|
||
> either it is stored on `Buchung.IDAdresse_FS_ExtraFerienzielPartner`, or it is derived from the
|
||
> travel. That booking's product has exactly one hotel, so the response cannot distinguish the two;
|
||
> one read-only query settles it.
|
||
|
||
Accommodation is **mandatory** — `913 Unterbringungen fehlen` / `916 Ferienziel-Unterbringungen
|
||
fehlen`. Confirmed empirically on the test system 2026-09-02, not just documented: see
|
||
"Validation gate order" below. Room dates are cross-checked against the transport legs (`921`,
|
||
`923`).
|
||
|
||
### Full documented request shape
|
||
|
||
```xml
|
||
<anfrage>
|
||
<user/><key/><satz typ="BUCHUNG"/>
|
||
<buchungsart>Anfrage|Buchung</buchungsart>
|
||
<status>F|O|A</status>
|
||
<idagentur/> <idcrm/> <idreise/> <idpartner/>
|
||
<fremdvorgang/> <!-- your own reference, stored as Buchung.FremdVorgang -->
|
||
<datenschutztfmp/> <!-- ad consent, combinable: T Telefon, F Telefax, M EMail, P Post -->
|
||
<anmelder>
|
||
name, vorname, anrede, titel, geschlecht, nationalitaet, geburtsdatum,
|
||
<anschrift>strasse, plz, ort, ortsteil, land</anschrift>
|
||
<kommunikation>telefonmobil, email, telefonprivat</kommunikation>
|
||
<bemerkung/>
|
||
</anmelder>
|
||
<teilnehmerliste>
|
||
<teilnehmer id="1">
|
||
…same personal fields…
|
||
<wünsche><unterbringungswunsch/><beförderungswunsch/></wünsche>
|
||
<aktionscode/>
|
||
</teilnehmer>
|
||
</teilnehmerliste>
|
||
<beförderungen><beförderung idleistung anzahl zuordnung/></beförderungen>
|
||
<ferienzielunterbringungen>…</ferienzielunterbringungen>
|
||
<zusatzleistungen><zusatzleistung idleistung anzahl zuordnung/></zusatzleistungen>
|
||
<zustiege><zustieg idzustieg anzahl zuordnung/></zustiege>
|
||
<sitzpläne><sitzplan idleistung idsitzplan sitzplätze="1B;1A"/></sitzpläne>
|
||
<versicherungen><versicherung idversicherung anzahl zuordnung/></versicherungen>
|
||
<gutscheine><gutschein gutscheinnr einloesecode/></gutscheine>
|
||
<zuschlaege>
|
||
<zuschlag text betrag anzahl zuordnung
|
||
provision altersrabatt gruppenrabatt frühbucherrabatt bzberechnung/>
|
||
</zuschlaege>
|
||
<zahlung idzahlungsart bezeichnung art>
|
||
<bankverbindung kreditinstitut bic iban kontoinhaber/>
|
||
</zahlung>
|
||
</anfrage>
|
||
```
|
||
|
||
`zuordnung` is a comma-joined list of 1-based `teilnehmer/@id` values throughout.
|
||
|
||
### Responses
|
||
|
||
`buchungsart=Anfrage` → `<buchung>möglich</buchung>` plus `<preise>`: one `<preis>` per line with
|
||
`position`, `art`, `unterart`, `bezeichnung`, `terminvon`/`terminbis`, `anzahl`, `zuordnung`,
|
||
`preis`, `gesamtpreis`, `id`; then `<gesamtpreis>` and `<zahlungsbedingungen>` with `<anzahlung>`
|
||
and `<restzahlung>` (each `betrag` + `termin`). `art` values seen: `BEF` transport, `UNT`
|
||
accommodation, `SON` other, `ERM` discount, `VER` insurance.
|
||
|
||
`buchungsart=Buchung` → `<buchung>erfolgt</buchung>`, `<vorgang>`, `<rechnung>`, `<idbuchung>`,
|
||
`<email>`, `<pdfpasswort>`.
|
||
|
||
### Validation gate order (measured)
|
||
|
||
Established with `buchungsart=Anfrage` against the test system on 2026-09-02, by mirroring a real
|
||
room-less teamer booking (Vorgang 110628) element for element:
|
||
|
||
1. **Accommodation.** No `ferienzielunterbringungen` → `916`, regardless of everything else being
|
||
valid. Nothing further is evaluated.
|
||
2. **Personal data**, once a room is present → `650` with the field list *"Fehlende
|
||
Anmelder/Teilnehmer Daten … Nachname, Vorname, Strasse, Plz, Ort, Telefon-Mobil, Mail-Adresse,
|
||
Geburtsdatum, Nationalität"*, for the Anmelder **and every participant**.
|
||
|
||
So **sending `idadresse`/`idadresseperson` alone is not enough on create** — BusPro validates the
|
||
personal fields whether or not it could resolve them from the ids. Echoing the record back
|
||
(`idadresse` + `idadresseperson` + full data) narrows the complaint to whatever that record is
|
||
genuinely missing, which is a useful way to audit an address: the placeholder account 19465:41003
|
||
fails on `Telefon-Mobil` and `Nationalität` because its `Vorgang_Details` record has only
|
||
`telefonprivat` and no `nationalitaet`.
|
||
|
||
Note `650` again: both of those are hard rejections delivered under the code
|
||
`Notification::isError()` treats as success.
|
||
|
||
## BUCHUNGAENDERUNG — the edit contract
|
||
|
||
Structurally identical to `BUCHUNG` plus `<idbuchung>`, and the responses follow `buchungsart`
|
||
the same way. Ask `MOEGLICHEAENDERUNGEN` first (`<idreise>`), which answers:
|
||
|
||
```xml
|
||
<änderungen>
|
||
<änderung art="anzahl_teilnehmer" möglich="False"/>
|
||
<änderung art="beförderung" möglich="False"/>
|
||
<änderung art="zustieg" möglich="False"/>
|
||
<änderung art="sitztplatz" möglich="False"/> <!-- vendor's typo -->
|
||
<änderung art="unterbringung" möglich="False"/>
|
||
<änderung art="zusatzleistung" möglich="True" möglichbiszum="23.02.2020"/>
|
||
<änderung art="teilnehmerdaten" möglich="False"/>
|
||
</änderungen>
|
||
```
|
||
|
||
## How this app maps onto the contract
|
||
|
||
`BookingPayloadBuilder::buildCreatePayload()` builds create; `buildBasePayload()` + the
|
||
`addTo…Payload()` helpers build edit; `ApiClient::createBooking()`/`updateBooking()` wrap them.
|
||
|
||
| Documented element | Sent on create | Sent on edit | Notes |
|
||
|---|---|---|---|
|
||
| `buchungsart` | yes | yes | Edit **hardcodes `Buchung`** (`ApiClient.php:233`) |
|
||
| `status` | yes | yes | Edit replays the value read back from BusPro — see the caveat below |
|
||
| `idreise`, `idpartner`, `idagentur` | yes | yes | `idpartner` sent unconditionally, even with no room |
|
||
| `anmelder` | yes | yes | Create builds it from participant[0]; edit uses `PersonalData::toPayload()` |
|
||
| `anmelder/bemerkung` | — | yes | Via `PersonalData::toPayload()` |
|
||
| `teilnehmerliste` | yes | yes | |
|
||
| `wünsche` (`unterbringungswunsch`, `beförderungswunsch`) | yes | yes | |
|
||
| `aktionscode` | yes | yes | Promo code, paired with `AKTIONSGUTSCHEIN` |
|
||
| `beförderungen`, `zusatzleistungen`, `zustiege` | yes | yes | Built by `ServiceMappingCollector` |
|
||
| `ferienzielunterbringungen` | yes | yes | Create omits the node entirely when no room maps; edit always emits the empty skeleton |
|
||
| `versicherungen` | yes | yes | |
|
||
| `gutscheine` | yes | yes | Paired with `GUTSCHEINPRUEFUNGEINLOESUNG` |
|
||
| `zahlung` + `bankverbindung` | yes | yes | |
|
||
| `idcrm` | no | no | |
|
||
| `fremdvorgang` | no | no | Would give our own bookings a stable external reference |
|
||
| `datenschutztfmp` | no | no | Advertising consent — we track newsletter consent locally instead |
|
||
| `sitzpläne` | no | no | See Seats |
|
||
| `zuschlaege` | no | no | Free-text manual surcharge or discount with its own amount |
|
||
|
||
### Newer than the PDF
|
||
|
||
These are absent from the document but work against the live API — vendor additions that
|
||
post-date it. Listed so nobody mistakes the PDF's silence for a contract violation, and so the
|
||
gaps in this reference are known.
|
||
|
||
| Element | Where | Note |
|
||
|---|---|---|
|
||
| `<ausstiege><ausstieg idzustieg …/></ausstiege>` | create and edit | Return boarding points. Confirmed as a recent vendor addition; the PDF documents only `<zustiege>`. |
|
||
| `<status>` inside `<teilnehmer>` | create (hardcoded `F`) and edit | Not in the PDF in either direction, though `Vorgang_Details` returns `status_teilnehmer`. |
|
||
| `<login>J</login>` on `KUNDENKONTO` | probe command | Passwordless read escape hatch; see `bpn-login-j-passwordless-reads`. |
|
||
| `VERFUEGBARKEIT2` | availability path | Whole satz type post-dates the PDF. Returns `buchungstatusmoeglich` and per-service `pflicht`. |
|
||
|
||
When one of these needs pinning down, the authoritative move is a live `Anfrage` call, not a
|
||
reading of this file.
|
||
|
||
### Caveat: the edit flow replays a derived status
|
||
|
||
`buildBasePayload()` sends `'status' => $bookingData->status`, and `Booking::$status` is parsed
|
||
from the response's root `<status>` (`BookingParser.php:38`). But `Buchung` has **no status
|
||
column** — the root value is an aggregate over `BuchungTN.Status`, the only real store
|
||
(`<status>F</status>` beside `<status_teilnehmer>F/F/…/F</status_teilnehmer>` in
|
||
`tests/Resources/booking_data.xml`). So on a booking whose participants do *not* all share one
|
||
status, the edit flow sends an aggregate back as a directive. Worth confirming against a mixed
|
||
booking before relying on it.
|
||
|
||
### Gap: edit never dry-runs
|
||
|
||
Create is two-phase — `Anfrage` to validate and price, then `Buchung`. Edit sends
|
||
`buchungsart=Buchung` directly (`ApiClient.php:233`), even though `BUCHUNGAENDERUNG` supports
|
||
`Anfrage` identically. An enquiry pass would surface `913`/`924`/`962` and return the recalculated
|
||
price *before* mutating a live booking.
|
||
|
||
## Seats
|
||
|
||
`SITZPLATZ` asks BusPro to pick seats:
|
||
|
||
```xml
|
||
<satz typ="SITZPLATZ"/>
|
||
<idreise/><idleistung_hin/><idleistung_rueck/><anzahl_personen/>
|
||
<wunsch_vornemittehinten>V|M|H</wunsch_vornemittehinten>
|
||
<wunsch_linksrechts>L|R</wunsch_linksrechts>
|
||
<wunsch_fenstergang>F|G</wunsch_fenstergang>
|
||
```
|
||
|
||
Response: one `<sitzplan>` per direction with `sitzplaetze="0D;1D;1C"`, `idsitzplan`,
|
||
`richtung="H|R"`, `bezeichnung`, `idleistung`, and `grafik` (base64 PNG of the seat map). Where
|
||
seats carry surcharges the response adds `<zuschläge>` (`id`, `bezeichnung`, `preis`, colours) and
|
||
the request must then carry `idzuschlag`. `SITZPLATZAUSWAHL` returns the raw plan structure
|
||
instead of a picture.
|
||
|
||
Chosen seats go back into a booking via `<sitzpläne>`, landing in
|
||
`BuchungTNLeistungZusatz.Sitzplatz` / `IDSitzplan_FS`. Guarded by `933 Sitzplan mit ID #1# bei
|
||
Leistung #2# nicht freigegeben` and `304 Es konnte nicht für alle Teilnehmer ein Sitzplatz
|
||
ermittelt werden`.
|
||
|
||
## KUNDENKONTO
|
||
|
||
`<art>`: `Adressdaten`, `Adressdaten_Ändern`, `Adresse_Neu`, `SelektionCRM`,
|
||
`SelektionCRM_Ändern`, `Vorgänge`, `Vorgangdruck`, `Voucherdruck`, `Dokumentdruck`,
|
||
`Passwort_Anfrage`, `Newsletter`, `Adressdokument`, `Vorgang_Details`, `Prüfung`, `PrüfungEmail`,
|
||
`Ausweisdaten_Ändern`, `Stornoanfrage`, `Storno`, `Versicherungspolice`.
|
||
|
||
`Stornoanfrage` answers `stornierung_moeglich` + `stornopreis`; `Storno` answers `storniert` —
|
||
the documented way to cancel, including cleaning up a test booking. `Ausweisdaten_Ändern` takes a
|
||
`<personen>` list with `<ausweis>` data and answers `änderung`. `Versicherungspolice` returns
|
||
per-policy document URLs.
|
||
|
||
## AGENTURKONTO
|
||
|
||
`<art>`: `Vorgänge`, `Vorgangdruck`, `Rechnungdruck`, `Hotelgutscheine`, `Hotelgutscheindruck`,
|
||
`OptionInFest` — the last converts a held option into a firm booking, answering
|
||
`<optioninfestumgewandelt>true</optioninfestumgewandelt>`. `Vorgangdruck` takes `idbuchung`,
|
||
`Rechnungdruck` takes `idrechnung`, `Hotelgutscheindruck` takes `idgutschein`; print arts return
|
||
base64 PDFs.
|
||
|
||
## Availability and payment
|
||
|
||
`VERFUEGBARKEIT` takes `<idreise>` plus a `<leistungen><leistung id="…"/></leistungen>` filter and
|
||
answers `<reise id status>` plus per-service `status` ("buchbar"), `frei`, `preis`,
|
||
`preisfaellig`. `VERFUEGBARKEIT2`, which we actually use, additionally reports
|
||
`buchungstatusmoeglich` and `pflicht` but is undocumented.
|
||
|
||
`ZAHLUNGSARTEN2` lists payment methods; `ZAHLUNG2` records a payment against a booking
|
||
(`vorgang`, `idbuchung`, `datum`, `betrag`, `idzahlungsart`, `zahlungstext` — all mandatory).
|
||
`ZAHLUNG` reports a credit-card authorisation after booking; the amount must equal the confirmed
|
||
`anzahlung` or errors `1011`-`1018` follow.
|
||
|
||
## Error codes
|
||
|
||
Returned as `<satz typ="HINWEIS"><nr>…</nr><text>…</text></satz>`. `#1#`/`#2#` are substituted at
|
||
runtime.
|
||
|
||
> **`650` is not a success code.** Its documented text is a bare `#1#` — a generic message
|
||
> carrier whose entire content is substituted. `Notification::isError()`
|
||
> (`src/BusProNet/Model/Notification.php:33-36`) treats it as success, which happens to hold on
|
||
> the `KUNDENKONTO` paths but not on `BUCHUNG`/`BUCHUNGAENDERUNG`, where the same code delivers
|
||
> real rejections such as *"Es wurde nicht jedem Teilnehmer eine Pflichtleistung zugeordnet"*.
|
||
> `BookingEditSubmitter.php:81` therefore reports some genuine failures as INFO.
|
||
|
||
**Envelope / auth** — 100 Anfrageknoten fehlt · 101 Satzknoten fehlt · 102 Ungültiger Satztyp ·
|
||
103/104 User fehlt/fehlerhaft · 105/106 Key fehlt/fehlerhaft · 999 Systemfehler
|
||
|
||
**Lookup** — 200 Keine Einträge · 201/202 Partner · 203 Ungültiger Termin · 300 IDReise fehlt ·
|
||
301 Reise nicht gefunden · 302 IDLeistung (Hin & Rück) fehlt · 303 IDLeistung passt nicht zu
|
||
IDReise · 304 Kein Sitzplatz für alle Teilnehmer ermittelbar · 305 Anzahl Personen fehlt ·
|
||
306/307 Leistungen · 308 Keine Zahlungsarten · 400 IDPartner fehlt · 401 Partner nicht gefunden ·
|
||
402 Bis-Termin fehlt · 403 Keine Unterbringungsleistungen gefunden
|
||
|
||
**Customer / address** — 500 Art fehlt · 502 Keine Kunden · 840-842 Personen · 850 IDAdresse und
|
||
EMail fehlen · 851 Passwort fehlt · 852 Art fehlt · 853 ID, EMail oder Passwort falsch · 855 Keine
|
||
Daten für Adresse · 860 Adressdaten fehlen · 862-867 Selektionsmerkmale / CRM-Aktionen ·
|
||
870-875 Newsletter · 880-882 Dokumente · 885-891 Pflichtfelder (Name, Vorname, Geschlecht, EMail,
|
||
PLZ, Straße) · 889 Kunde bereits angelegt (ID #1#)
|
||
|
||
**Booking (create and edit)** — 900 Buchungsart fehlt · 901 Buchungsart falsch · 902 Status
|
||
fehlt · 903 Status falsch · 904/905 Agentur · 906 Reise-ID fehlt · 907 Reise nicht gefunden ·
|
||
908 Reise ist storniert · **909 Beförderungen fehlen** · 910 Leistung gehört nicht zur Reise ·
|
||
911 Leistung gehört nicht zum Produkt · 912 Beförderungsleistung fehlt · **913 Unterbringungen
|
||
fehlen** · **914 Partner-ID fehlt** · 915 Partner nicht gefunden · **916 Ferienziel-Unterbringungen
|
||
fehlen** · 917 Zimmer-IDZ fehlt · 918 Kategorie fehlt · 919 Verpflegungs-ID fehlt · 920 Anreise
|
||
fehlt · **921 Anreise passt nicht zur Beförderungsleistung (Hinfahrt)** · 922 Abreise fehlt ·
|
||
**923 Abreise passt nicht zur Beförderungsleistung (Rückfahrt)** · **924 Ferienziel: Keine Preise
|
||
zu den Daten gefunden** · 925 Preis zu den Daten nicht gefunden · 930 Zustiege fehlen ·
|
||
931 Zustieg nicht gefunden · 932 Zustieg bei Leistung nicht freigegeben · 933 Sitzplan bei
|
||
Leistung nicht freigegeben · 935-938 Versicherungen · 940-942 Zahlungsart · 950 Anmelder fehlt ·
|
||
951 Teilnehmerliste fehlt · 952 Teilnehmer-ID fehlt · 953 Keine Teilnehmer übergeben ·
|
||
960 Preisfehler · 961 Buchung konnte nicht gespeichert werden · **962 Buchungsstatus #1# kann
|
||
nicht verarbeitet werden, nur Status #2# möglich** · 970 CRM-Merkmal nicht gültig · 980 Reise ist
|
||
fürs Internet gesperrt · 981 Reise ist nicht mehr buchbar (#1#) · **982 Optionsbuchungen nicht
|
||
zugelassen** · 983 Anfragebuchung nicht zugelassen · 984 Leistung nicht fürs Internet buchbar ·
|
||
985 Zustieg nicht fürs Internet buchbar · 986/987 Rechnung · 990-994 Agenturkonto ·
|
||
995/996 Gutschein · 997/998 Buchung-ID · 1001 Reisedatum fehlerhaft · 1002 Buchungsdatum
|
||
fehlerhaft · 1003 Status im Vorgang nicht korrekt · 1011-1018 Zahlungsabgleich
|
||
|
||
**Vouchers** — 600-608 Gutscheine · 610/611 Buchungsart · 612-621 Gutscheinart / Stamm /
|
||
Kapazität · 622 Einlösecode fehlt · 630-632 Aktionsgutschein
|
||
|
||
## Applying this: the teamer placeholder booking
|
||
|
||
**Answered 2026-09-02: `BUCHUNG` cannot create it.** A payload mirroring the real teamer booking
|
||
(travel 12429, hotel 222925, agency 77876, both bus legs, ski pass, Ortstaxe, Keycard-Pfand,
|
||
pickup and drop-off, root and participant status `O`, ids-only identity, no room) returns
|
||
`916 Ferienziel-Unterbringungen fehlen`. Adding a room to the identical payload clears that gate
|
||
and moves on to `650`, which proves accommodation was the only structural objection.
|
||
|
||
Two blockers that were *not* the problem:
|
||
|
||
- **Status `O` is fine here.** Travel 12429 reports `buchungstatusmoeglich="F, O, A"`. The `982`
|
||
refusal seen earlier is per travel, not a global setting.
|
||
- **`NurFahrt` never came up** — the request never got far enough for it to matter.
|
||
|
||
What the real booking shows about how the office builds one, worth reproducing if the work moves
|
||
to a direct write:
|
||
|
||
- `<idpartner>` **is** present despite there being no accommodation — 222925 is the ordinary
|
||
hotel of the travel (`Adresse.IDAdresse`, "NEU Sportclub Weissfluh Midweeks und Wochenreisen",
|
||
the sole hotel of product 1429, with real bookable rooms), not a special no-accommodation
|
||
partner. Whether the booking stores it or the response derives it is still open.
|
||
- Each teamer seat is its **own `AdressePerson`** (407725, 407726) under the shared address 41003,
|
||
with an empty `<anschrift/>`.
|
||
- `<wünsche><beförderungswunsch>` carries the role label — "Reiseleitung + Busbegleitung".
|
||
- Per participant, `<optionsdatum>` sets the option expiry.
|
||
- The total is driven to zero with a **manual `<zuschlag>`**: Ortstaxe 44,00 + Keycard-Pfand 10,80
|
||
+ Abfahrtsortzuschlag 25,80 = 80,60, offset by an unnamed −80,60 line. `<zuschlaege>` is part of
|
||
the documented `BUCHUNG` payload, so this part *is* expressible over XML.
|
||
|
||
See `.ddev/plans/teamer-placeholder-booking-plan.md`.
|