8.3 KiB
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 thefreidiscovery 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:
BuchungTNLeistunghas no own status; join up viaIDBuchungTN_FS→BuchungTN→Buchungand exclude Storno (theBuchungStatus*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 aPartnerKontingent), so itsfreidepends on a sibling, not just its own bookings.
Recommendation: keep the authoritative
freion 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
freicounts — socket API, or an explicitly-validated approximation. - Keep on socket:
getMutableData.