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

166 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.<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`.