Files
frommeandClaude Opus 5.5 2bd663a9bf docs: CMS API and calendars
docs/cms-api.md describes the endpoints TYPO3 serves to MyE&P and the
chatbot (/api/hotel, /api/product, /api/calendar-hotels,
/api/content/*): key, parameters, responses, errors. It is the other
side of MyE&P's API consumer guide.

docs/calendars.md covers the price calendar, the Belegungskalender and
the iCal feed: which product gets which, the MyE&P endpoints behind
them, caching, troubleshooting, and the rule that a product's calendar
hotel must carry the BusPro code, not a group house's CMS code (which
broke three example trips).

The README replaces the TYPO3 base distribution boilerplate and links
both. The plugin renaming SQL in ep_products' README is obsolete since
the migration. The cmsApiKey description names /api/calendar-hotels.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 15:37:36 +01:00

117 lines
6.9 KiB
Markdown

# 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/<hotel>.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_<code>`. 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`).