Files
myep/docs/buspronet-schema/live-api-queries.md
T

8.3 KiB
Raw Blame History

Live socket-API endpoints — direct-MSSQL feasibility & queries

Authority: docs/buspro-database-reference.md. Schema semantics live there and win on any conflict. This file is retained for the per-endpoint migration verdicts and the frei discovery SQL.

This complements travel-queries.md. That doc replaced the XML export (static catalog). This one covers the live socket API methods on src/BusProNet/ApiClient.php and classifies how replicable each is by direct MSSQL query in the Go proxy.

Verdict in one line: the structural parts replicate cleanly; the live "free" counts are computed by BusPro's allocation logic (not a flat column) and should either stay on the socket API or be reproduced with an explicitly-approximated query; getMutableData() is pure business-rule logic — keep it on the socket API.

Endpoint (type) Tier Replicable in SQL?
getProducts() (PRODUKTE) 1 trivial
getAgencies() (AGENTUREN) 1 structural (confirm agency filter + address joins)
getTravelData() (PRODUKTDATEN) 1 = travel-queries.md Query B
getAvailabilities() (VERFUEGBARKEIT) 2 ⚠ structure yes; frei count is computed
getAvailabilitiesExtended() (VERFUEGBARKEIT2) 2 ⚠ structure yes (we have the extra fields); frei computed
getHotelAvailability() (VERFUEGBARKEITHOTEL) 2 frei = contingent active bookings
getMutableData() (MOEGLICHEAENDERUNGEN) 3 business rules — keep on socket API

Tier 1 — structural, replicate as-is

getProducts()Product{id, code, name}

Parser keeps only rows with a non-empty code (ProductsParser).

SELECT IDProdukt AS id, Code AS code, Bezeichnung AS name
FROM dbo.Produkt
WHERE Code IS NOT NULL AND Code <> ''
ORDER BY Bezeichnung;

getTravelData(idprodukt[, hotelId])Travel

This is the same payload as travel-queries.md Query B (header + services + pickups + hotels/rooms + tags). The static structure is fully covered there. Its embedded live preis/frei values overlap Tier 2 — source those the same way you decide to handle availability below. No new SQL needed; reuse Query B.

getAgencies()Agency{id, name, code, street, postCode, city, phone}

name/code map to Adresse.SuchName + a code column; street/postCode/city live in AdresseAnschrift, phone in AdresseKommunikation (1-to-N → pick the primary row). Two things to pin with the discovery queries below: (1) which Adresse rows are agencies, and (2) the exact AdresseAnschrift/AdresseKommunikation column + primary-row flag.

-- skeleton (fill the agency filter + verified column names from discovery):
SELECT a.IDAdresse        AS id,
       a.SuchName         AS name,
       a.Code             AS code,          -- ⚠ confirm code column on Adresse
       an.Strasse         AS street,        -- ⚠ from AdresseAnschrift
       an.Plz             AS postCode,
       an.Ort             AS city,
       ak.Wert            AS phone           -- ⚠ from AdresseKommunikation (primary tel)
FROM dbo.Adresse a
LEFT JOIN dbo.AdresseAnschrift   an ON an.IDAdresse_FS = a.IDAdresse /* AND an.IsPrimary */
LEFT JOIN dbo.AdresseKommunikation ak ON ak.IDAdresse_FS = a.IDAdresse /* AND ak.Art = 'TEL' */
WHERE /* a is an agency — see discovery */ 1 = 0;

Discovery:

-- (1) how agencies are flagged (inspect the org/role tables):
SELECT TOP 20 * FROM dbo.AdresseAgenturOrganisation;
SELECT IDArtAgenturOrganisation, * FROM dbo.ArtAgenturOrganisation;   -- the agency "types"
-- (2) the address + comm column names / primary-row flags:
SELECT TOP 5 * FROM dbo.AdresseAnschrift;
SELECT TOP 5 * FROM dbo.AdresseKommunikation;
SELECT TOP 5 * FROM dbo.Adresse;            -- find the 'code' column

Tier 2 — availability: structure is flat, the frei count is computed

The response models (Availability, ExtendedAvailability, Room) carry per-item status, preis, structural attrs (dates/ages/mandatory/board) and frei. The first set is a flat read; frei is not stored.

Why frei is computed (validated): Leistung.Kontingent is a fixed planning allotment, not the remaining free count — e.g. service 155234 has Kontingent=2500 but 2945 booking rows (bookings exceed the allotment). So:

frei = Kontingent  (active, non-cancelled, non-expired bookings)   [± pooled contingent]

with three real complications:

  • Cancellations: BuchungTNLeistung has no own status; join up via IDBuchungTN_FSBuchungTNBuchung and exclude Storno (the BuchungStatus* flags / Storno date).
  • Option expiry: BuchungTNLeistung.OptionsDatum + Leistung.KontingentVerfallTage — expired options free their slots back.
  • Pooled contingents: Leistung.IDLeistung_FS_KontingentAUS / KontingentAusPartner — a service may draw from another service's pool (or a PartnerKontingent), so its frei depends on a sibling, not just its own bookings.

Recommendation: keep the authoritative frei on the socket API (getAvailabilities/getHotelAvailability). If you want it in SQL, treat it as a documented approximation and validate row-by-row against the socket API before trusting it for booking decisions. The structural fields below are safe to serve from SQL.

getAvailabilities() / getAvailabilitiesExtended() — structural part

SELECT
    l.IDLeistung      AS serviceId,
    l.Status          AS status,
    lp.Preis          AS price,
    l.Kontingent      AS allotment,        -- NOT 'frei' (see calc above)
    -- extended-only fields (VERFUEGBARKEIT2):
    l.Termin_VON      AS dateFrom, l.Termin_BIS AS dateTo,
    l.Alter_VON       AS ageFrom,  l.Alter_BIS  AS ageTo,
    l.Pflichtmerkmal  AS mandatory
FROM dbo.ReiseLeistung rl
JOIN dbo.Leistung l            ON l.IDLeistung = rl.IDLeistung_FS
LEFT JOIN dbo.LeistungPreis lp ON lp.IDLeistung_FS = l.IDLeistung AND lp.IDArtPreisSchema_FS = 1
WHERE rl.IDReise_FS = @DateId
ORDER BY l.Art, l.Unterart;
-- travel-level 'buchungstatusmoeglich' / 'status' come from the Reise + the allowed
-- ArtBuchungsStatus set; the extended 'uhrzeit_von'/'hinweis' come from the pickup
-- time plan (ZustiegZeitplan) and a Leistung text — see travel-queries.md B2/B2b.

The live frei per serviceId = the calc above (see recommendation).

getHotelAvailability(idreise, idpartner, terminbis)Room{frei, status, preis, …}

Rooms/price/status are the travel-queries.md B3 chain; status = PartnerKontingent.BuchungsStatus (''/S/A). frei = PartnerKontingent.Anzahl minus active hotel bookings for that (IDAdresse, IDZimmer, date-span) — same cancellation/expiry caveats as above.

Discovery — quantify the frei calc against the socket API

-- active (non-storno) booking rows for a service, with option dates:
SELECT btl.IDLeistung_FS, COUNT(*) AS rows_total,
       SUM(CASE WHEN btl.OptionsDatum IS NULL OR btl.OptionsDatum >= GETDATE() THEN 1 ELSE 0 END) AS rows_live
FROM dbo.BuchungTNLeistung btl
JOIN dbo.BuchungTN  bt ON bt.IDBuchungTN = btl.IDBuchungTN_FS
JOIN dbo.Buchung    b  ON b.IDBuchung    = bt.IDBuchung_FS   -- ⚠ confirm FK name
WHERE btl.IDLeistung_FS = @ServiceId
  /* AND b.<storno flag> = 0 */                              -- ⚠ confirm Storno column
GROUP BY btl.IDLeistung_FS;
-- then compare Leistung.Kontingent  rows_live  vs  the socket-API 'frei' for the same service.

Tier 3 — getMutableData() — keep on the socket API

Returns per-aspect möglich (bool) + möglichbiszum (deadline) for: participant count, transportation, pickup, accommodation, additional services, participant data. These are derived from booking rules / change-cutoff config / departure timing (not a flat table). Reproducing them in SQL would mean re-implementing BusPro's change-deadline logic and risks silent divergence. Recommendation: leave this on the socket API; don't migrate it.


Summary for the Go proxy

  • Migrate now (SQL): getProducts, getAgencies (after the agency-filter discovery), getTravelData (reuse Query B), and the structural fields of the two availability endpoints.
  • Keep computed: the live frei counts — socket API, or an explicitly-validated approximation.
  • Keep on socket: getMutableData.