# Live socket-API endpoints — direct-MSSQL feasibility & queries > **Authority: [`docs/buspro-database-reference.md`](../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`). ```sql 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. ```sql -- 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: ```sql -- (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_FS` → `BuchungTN` → `Buchung` 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 ```sql 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 ```sql -- 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. = 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`.