# Travel Snapshot Persistence (DB-Primary + Extended Availability) ## Scope Persist travel payloads in DB and use them as primary local source, with enrichment from BusPro `VERFUEGBARKEIT2` (internal naming: **extended/ext**). ## Current Implementation Status ### Completed - [x] Added snapshot persistence entity and repository: - `src/Entity/TravelSnapshot.php` - `src/Repository/TravelSnapshotRepository.php` - [x] Added snapshot application service: - `src/Service/TravelSnapshotService.php` - Uses Symfony Serializer JSON payloads for `Travel` - Supports upsert/hash comparison, load, mapping, product lookup, refresh, purge - [x] Added extended availability integration: - `ApiClient::TYPE_AVAILABILITY_EXTENDED = 'VERFUEGBARKEIT2'` - `ApiClient::getAvailabilitiesExtended()` - parser dispatch in `ApiResponseParser` - `ExtendedAvailabilitiesParser` - models `ExtendedAvailability` and `ExtendedServiceAvailabilityResponse` - [x] Added DB-primary read path in `TravelDataService`: - tries snapshot first - falls back to XML parse + enrichment + snapshot upsert - snapshot mapping merged into `generateFilesMap()` - product lookup includes snapshot metadata fallback - [x] Added refresh command: - `app:travel:snapshot-refresh` - supports batch processing, force mode, optional purge - [x] Added migration for snapshot table: - `migrations/Version20260321120000.php` - [x] Added serializer type metadata/docblocks in relevant model classes to support stable snapshot deserialization. - [x] Removed unified travel cache from `TravelDataService::getTravelData()`: - cache and snapshot-version-token logic removed; travel is now loaded directly from snapshot DB (fast indexed lookup) or XML fallback on every call - `TravelSnapshotService::getCacheVersionToken()` removed along with it - [x] Fixed hotel-specific lookup correctness: - when `hotelId` is provided, snapshot lookup no longer falls back to another hotel of same date. ### Confirmed Behaviors - [x] Snapshot data survives XML deletion and remains loadable. - [x] Extended refresh updates service-level fields including `uhrzeit_von` -> `Service::timeFrom`. - [x] Runtime availability overlay (`VERFUEGBARKEIT`) remains in place. ## Design Decisions (Final) - Internal naming uses `extended/ext`; external request type string stays `VERFUEGBARKEIT2`. - DB snapshots are the primary local source for travel loading. - Serializer format is JSON via Symfony Serializer, not PHP `serialize()`. - No application-level cache wraps `getTravelData()`; snapshot DB is the fast path, XML is the fallback. ## Operational Commands - Refresh snapshots: ```bash ddev php bin/console app:travel:snapshot-refresh ``` - Force refresh: ```bash ddev php bin/console app:travel:snapshot-refresh --force ``` - Force refresh with limit: ```bash ddev php bin/console app:travel:snapshot-refresh --force --limit=500 ``` - Refresh + purge: ```bash ddev php bin/console app:travel:snapshot-refresh --purge ``` ## Open TODOs - [ ] Revisit DB indexes on `travel_snapshot` and remove unused ones if desired (`date_code`, `product_id` currently appear non-critical for active query paths). - [x] Added automated tests: - `TravelSnapshotServiceTest` — upsert, load, exists, generateMapping - `TravelDataServiceTest` — DB-primary + XML fallback paths, insurance rehydration, exception propagation - [ ] Add/expand automated tests: - extended parser coverage - snapshot refresh command behavior - [ ] Define production cron schedule for refresh and cleanup cadence. ## Notes - If refreshed snapshot data is not visible immediately, ensure the request path is not serving an older in-memory/session DTO. There is no application-level cache on `getTravelData()`; each call reads the snapshot DB directly, so data is current on the next request after a refresh.