# Availability and prices on product pages TYPO3 shows three things built from contingent data. None of them talks to bpn-connect directly: all go through **MyE&P**, which keeps a local snapshot of bpn-connect's availability and holds the group house prices. | What | Where | Shown for | MyE&P endpoint | |---|---|---|---| | **Price calendar** + price tables | product detail, tab "Termine/Preise" | products with a calendar hotel and season **summer** (`s`) | `contingents/calendar`, `contingents/prices` | | **Belegungskalender** (availability calendar) | product detail, sidebar | products with a calendar hotel and season **not summer** | `contingents/availability` (+ `contingents/calendar` for the months to leave out) | | **iCal feed** for booking portals | `https://www.ep-reisen.de/contingents/.ical` | every hotel | `contingents/availability` | So every product with a calendar hotel shows exactly one calendar. The MyE&P side is documented in its `docs/api-consumer-guide.md` (`EP/myep`). ## The calendar hotel field A product's **calendar hotel** (`calendar_hotel`) decides whose availability and prices it shows. TYPO3 sends that hotel's **code** to MyE&P, which only understands **BusPro calendar codes** (the codes bpn-connect knows, e.g. `DGS`, `LPJ`, `DKS`). Most hotel records carry the BusPro code. Group houses also have **content records** of their own (`DKSGRP`, `LPJGRP`, `SERASCSO`, …) whose code is the **CMS code** MyE&P uses to fetch content via `/api/hotel`. These must **not** be used as calendar hotel: MyE&P and bpn-connect do not know them, and the calendar shows its error message. This broke three example trips until 2026-10-29. | Use as calendar hotel | Not | |---|---| | `DKS` Sportclub Schweizerhaus (uid 193) | `DKSGRP` Davos Klosters - Gruppenhaus Schweizerhaus | | `LPJ` Sportclub Jenatsch (uid 37) | `LPJGRP` Lenzerheide - Gruppenhaus Jenatsch | `/api/calendar-hotels` lists all calendar hotels in use; every code in it should be a BusPro code. A hidden hotel record counts as no calendar hotel: the product shows no calendar and the code is not in the list (e.g. `ASB` Sportclub Bader). ## Price calendar (summer) - `Product/Detail.html`, section `Dates`, partial `Product/PriceCalendar`; `AjaxCalendarController::priceCalendarAction` → `contingents/calendar` (status and price per day), `priceTableAction` → `contingents/prices` for the current and the next year (loaded when scrolled into view). - Only works for hotels that are an **accommodation with prices** in MyE&P (summer group houses). ## Belegungskalender (winter) - `Product/Detail.html`, section `AvailabilityCalendar`, loaded by htmx when it scrolls into view (`intersect once`, so the hidden copy of the sidebar on the other breakpoint does not load); `AjaxCalendarController::availabilityAction`, template `AjaxCalendar/Availability.html`, month select `calendar_controller.js`. - All months in one response; the select only switches between them. 12 months from the current one, extended to the last month MyE&P has data for. - **Months with prices are left out**: they are summer, covered by the price calendar. Only summer months have prices today; that is the whole distinction. If `contingents/calendar` fails, all months are shown (not cached). - Colours as on TYPO3 10: green available (`OK`, and days without data), yellow on request (`ON_REQUEST`), red not available (`BLOCKED`), past days grey; a status change from the previous day is drawn as a diagonal. The `availability-*` classes are built in PHP, so they are safelisted in `tailwind.config.js`; without that Tailwind purges them. - Works for every hotel MyE&P syncs, with or without an accommodation (see below). ## iCal feed - `IcalController::contingentsAction`, `IcalService`; route `/contingents/{hotel}.ical` on the `ep-reisen` site (`config/sites/ep-reisen/config.yaml`, page 9, type 1710), `{hotel}` is the hotel's path segment. - One `Unavailable` event per run of `BLOCKED` or `ON_REQUEST` days with the same status, as on TYPO3 10. - Never cached. When MyE&P has no availability it answers `503` with `Retry-After: 900` instead of an empty calendar, which the portals would read as "every day free"; they keep their last import. ## Which hotels MyE&P has availability for MyE&P syncs its snapshot from bpn-connect every 15 minutes during the day and hourly at night, for the calendar codes of its accommodations **plus** every code in TYPO3's `/api/calendar-hotels` (see [cms-api.md](cms-api.md)). So a hotel offered only in winter needs nothing in MyE&P: setting it as calendar hotel of a visible product is enough; it is synced with the next run. `contingents/availability` answers: - `400` for a code that is not synced (not an accommodation, not in TYPO3's list), - `502` when the hotel's snapshot is missing or older than 6 hours (sync failing), - `200` with one entry per known day otherwise. `contingents/calendar` and `contingents/prices` additionally need an accommodation with prices. MyE&P's `GET /api/contingents/status` lists every synced code with its source, prices, last sync and last error: the first place to look when a calendar does not load. ## Caching - The AJAX responses (price calendar, price tables, Belegungskalender) are page-cached on the AJAX page for at most **1 hour** (`cache_period = 3600`) and never past midnight (`cache_clearAtMidnight`, past days must turn grey). Browsers do not cache them (`sendCacheHeaders = 0`). - **Errors are not cached** (`disablePageCache()`): the next visitor triggers a new attempt. - Every AJAX URL carries a cHash (required for `tx_epproducts_ajax`), so each hotel and year has its own cache entry. - Entries are tagged `ep_hotel_`. Nothing flushes them automatically; a deploy flushes all caches. To refresh by hand: `vendor/bin/typo3 cache:flush --group pages`, or flush the tag via the `CacheManager` for a single hotel. - On top of that comes MyE&P's sync interval, so a calendar is at most about 1¼ hours behind bpn-connect. ## When a calendar does not load The message is "Der Kalender konnte aktuell nicht geladen werden."; the iCal feed answers `503`. | Cause | How to tell | Fix | |---|---|---| | Calendar hotel has a CMS code (`…GRP`, `…SO`) | code not BusPro; MyE&P `400`, bpn-connect `404` | set the hotel record with the BusPro code as calendar hotel | | Code not synced by MyE&P | not in `contingents/status`; MyE&P `400` | check that the product and its calendar hotel are visible (then it is in `/api/calendar-hotels`) | | Sync failing for the hotel | `contingents/status`: `available: false`, `lastError` | MyE&P / bpn-connect | | MyE&P unreachable or its OAuth failing | all hotels fail; TYPO3 log `EP.EpProducts.MyEP.ApiClient` | MyE&P | | Price calendar only: no accommodation or no prices | MyE&P `400` / empty | prices are maintained in MyE&P's Groups admin | TYPO3 logs every failed MyE&P call in `var/log/typo3_*.log` (component `EP.EpProducts.MyEP.ApiClient`, for the iCal feed also `EP.EpProducts.Service.IcalService`).