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

6.9 KiB

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). 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).