687 lines
24 KiB
PHP
687 lines
24 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Service;
|
|
|
|
use App\BusProNet\ApiClient;
|
|
use App\BusProNet\Exception\ApiClientException;
|
|
use App\BusProNet\Model\BaseData;
|
|
use App\BusProNet\Model\Notification;
|
|
use App\BusProNet\Model\Travel;
|
|
use App\BusProNet\XmlLoader\HotelLoader;
|
|
use App\BusProNet\XmlLoader\InsuranceLoader;
|
|
use App\BusProNet\XmlLoader\PickupLoader;
|
|
use App\BusProNet\XmlLoader\TravelLoader;
|
|
use App\Exception\HotelNotFoundException;
|
|
use App\Exception\HotelNotInTravelException;
|
|
use App\Exception\TravelNotFoundException;
|
|
use Psr\Cache\InvalidArgumentException;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Contracts\Cache\CacheInterface;
|
|
use Symfony\Contracts\Cache\ItemInterface;
|
|
|
|
/**
|
|
* Unified service for retrieving travel data from both local XML files and remote API.
|
|
*
|
|
* This service provides a unified interface for accessing travel data regardless of source,
|
|
* supporting automatic fallback between local XML files and remote API calls. It handles caching,
|
|
* error recovery, and data enrichment for both data sources.
|
|
*/
|
|
class TravelDataService
|
|
{
|
|
public const SOURCE_LOCAL = 'local';
|
|
public const SOURCE_REMOTE = 'remote';
|
|
|
|
public function __construct(
|
|
private readonly TravelLoader $travelLoader,
|
|
private readonly HotelLoader $hotelLoader,
|
|
private readonly PickupLoader $pickupLoader,
|
|
private readonly InsuranceLoader $insuranceLoader,
|
|
private readonly ApiClient $apiClient,
|
|
private readonly CacheInterface $cache,
|
|
private readonly LoggerInterface $logger,
|
|
private readonly bool $preferRemote = false,
|
|
private readonly bool $enableFallback = true,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Retrieve travel data with automatic source selection and fallback.
|
|
*
|
|
* Attempts to load travel data from the preferred source first, then falls back
|
|
* to the alternative source if the primary fails. Handles caching and enrichment
|
|
* of data from both sources.
|
|
*
|
|
* @param int $dateId The travel date ID to retrieve
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
* @param bool $preferRemote Whether to prefer remote API over XML for this call
|
|
* @param bool $enableCache Whether to use caching for this request
|
|
*
|
|
* @return Travel|null The travel data or null if not found in any source
|
|
*/
|
|
public function getTravelData(
|
|
int $dateId,
|
|
?int $hotelId = null,
|
|
?bool $preferRemote = null,
|
|
bool $enableCache = true,
|
|
): ?Travel {
|
|
$preferRemote = $preferRemote ?? $this->preferRemote;
|
|
$cacheKey = sprintf('travel_unified_%d_%d_%s', $dateId, $hotelId ?? 0, $preferRemote ? 'remote' : 'local');
|
|
|
|
if (!$enableCache) {
|
|
return $this->loadTravelDataUncached($dateId, $hotelId, $preferRemote);
|
|
}
|
|
|
|
try {
|
|
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($dateId, $hotelId, $preferRemote) {
|
|
$item->expiresAfter(300); // 5 minutes cache
|
|
|
|
return $this->loadTravelDataUncached($dateId, $hotelId, $preferRemote);
|
|
});
|
|
} catch (InvalidArgumentException $e) {
|
|
$this->logger->error('Cache error in TravelDataService', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return $this->loadTravelDataUncached($dateId, $hotelId, $preferRemote);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve travel data specifically from XML files.
|
|
*
|
|
* Loads travel data from local XML files with full data enrichment including
|
|
* hotel details and pickup information.
|
|
*
|
|
* @param int $dateId The travel date ID to retrieve
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
*
|
|
* @return Travel|null The travel data or null if not found in XML
|
|
*/
|
|
public function getTravelDataFromXml(int $dateId, ?int $hotelId = null): ?Travel
|
|
{
|
|
try {
|
|
$travel = $this->travelLoader->loadById($dateId, $hotelId);
|
|
|
|
$this->enrichTravelData($travel);
|
|
$this->logger->debug('Travel data loaded from XML', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'travelId' => $travel->id,
|
|
]);
|
|
|
|
return $travel;
|
|
} catch (TravelNotFoundException $e) {
|
|
$this->logger->debug('Travel not found in XML', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
throw $e;
|
|
} catch (HotelNotFoundException|HotelNotInTravelException $e) {
|
|
$this->logger->debug('Hotel not found in XML', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
throw $e;
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Failed to load travel data from XML', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve travel data specifically from remote API.
|
|
*
|
|
* Loads travel data from the remote BusProNet API. Note that the API uses
|
|
* product IDs rather than date IDs, so mapping is performed internally.
|
|
*
|
|
* @param int $dateId The travel date ID to retrieve
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
*
|
|
* @return Travel|null The travel data or null if not found via API
|
|
*/
|
|
public function getTravelDataFromApi(int $dateId, ?int $hotelId = null): ?Travel
|
|
{
|
|
try {
|
|
// Map dateId to productId for API call
|
|
$productId = $this->mapDateIdToProductId($dateId);
|
|
if (null === $productId) {
|
|
$this->logger->debug('Cannot map dateId to productId for API call', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
$result = $this->apiClient->getTravelData($productId, $hotelId);
|
|
|
|
if (!$result instanceof Travel) {
|
|
$this->logger->debug('API returned non-travel result', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'productId' => $productId,
|
|
'resultType' => get_class($result),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
$this->logger->debug('Travel data loaded from API', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'productId' => $productId,
|
|
'travelId' => $result->id,
|
|
]);
|
|
|
|
return $result;
|
|
} catch (ApiClientException $e) {
|
|
$this->logger->error('Failed to load travel data from API', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if travel data exists in XML files.
|
|
*
|
|
* Performs a lightweight check to determine if travel data exists in XML
|
|
* files without loading the full travel object.
|
|
*
|
|
* @param int $dateId The travel date ID to check
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
*
|
|
* @return bool True if travel data exists in XML files
|
|
*/
|
|
public function existsInXml(int $dateId, ?int $hotelId = null): bool
|
|
{
|
|
$mapping = $this->generateFilesMap();
|
|
|
|
if (false === isset($mapping[$dateId])) {
|
|
return false;
|
|
}
|
|
|
|
// If hotelId is specified, check if it exists in the travel's hotels
|
|
if (null !== $hotelId && false === isset($mapping[$dateId]['hotels'][$hotelId])) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get information about available data sources for a travel.
|
|
*
|
|
* Returns information about which data sources (local XML, remote API, or both) have
|
|
* data available for the specified travel.
|
|
*
|
|
* @param int $dateId The travel date ID to check
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
*
|
|
* @return array<string, bool> Array with 'local' and 'remote' keys indicating availability
|
|
*/
|
|
public function getAvailableSources(int $dateId, ?int $hotelId = null): array
|
|
{
|
|
return [
|
|
static::SOURCE_LOCAL => $this->existsInXml($dateId, $hotelId),
|
|
static::SOURCE_REMOTE => null !== $this->mapDateIdToProductId($dateId),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Load travel data directly without caching.
|
|
*
|
|
* Internal method that handles the actual loading logic with fallback support.
|
|
* Tries the preferred source first, then falls back to the alternative if enabled.
|
|
*
|
|
* @param int $dateId The travel date ID to retrieve
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
* @param bool $preferRemote Whether to prefer remote API over XML
|
|
*
|
|
* @return Travel|null The travel data or null if not found
|
|
*/
|
|
private function loadTravelDataUncached(int $dateId, ?int $hotelId = null, bool $preferRemote = false): ?Travel
|
|
{
|
|
$primarySource = $preferRemote ? self::SOURCE_REMOTE : self::SOURCE_LOCAL;
|
|
$fallbackSource = $preferRemote ? self::SOURCE_LOCAL : self::SOURCE_REMOTE;
|
|
|
|
// Try primary source first
|
|
$travel = $this->loadFromSource($dateId, $hotelId, $primarySource);
|
|
|
|
if (null !== $travel) {
|
|
return $travel;
|
|
}
|
|
|
|
// Try fallback source if enabled
|
|
if (true === $this->enableFallback) {
|
|
$this->logger->debug('Fallback to alternative source', [
|
|
'dateId' => $dateId,
|
|
'hotelId' => $hotelId,
|
|
'primarySource' => $primarySource,
|
|
'fallbackSource' => $fallbackSource,
|
|
]);
|
|
|
|
$travel = $this->loadFromSource($dateId, $hotelId, $fallbackSource);
|
|
}
|
|
|
|
return $travel;
|
|
}
|
|
|
|
/**
|
|
* Load travel data from a specific source.
|
|
*
|
|
* Internal method that routes to the appropriate loader based on source type.
|
|
*
|
|
* @param int $dateId The travel date ID to retrieve
|
|
* @param int|null $hotelId Optional hotel ID for specific hotel data
|
|
* @param string $source The source type (SOURCE_LOCAL or SOURCE_REMOTE)
|
|
*
|
|
* @return Travel|null The travel data or null if not found
|
|
*/
|
|
private function loadFromSource(int $dateId, ?int $hotelId, string $source): ?Travel
|
|
{
|
|
return match ($source) {
|
|
self::SOURCE_LOCAL => $this->getTravelDataFromXml($dateId, $hotelId),
|
|
self::SOURCE_REMOTE => $this->getTravelDataFromApi($dateId, $hotelId),
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map date code to date ID.
|
|
*
|
|
* Converts a date code string to its corresponding date ID using the
|
|
* date loader's mapping functionality.
|
|
*
|
|
* @param string $dateCode The date code to map
|
|
*
|
|
* @return int|null The corresponding date ID or null if not found
|
|
*/
|
|
public function mapDateCodeToId(string $dateCode): ?int
|
|
{
|
|
try {
|
|
$dateId = $this->travelLoader->mapCodeToId($dateCode);
|
|
|
|
$this->logger->debug('Date code mapping', [
|
|
'dateCode' => $dateCode,
|
|
'dateId' => $dateId,
|
|
]);
|
|
|
|
return $dateId;
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Failed to map date code to ID', [
|
|
'dateCode' => $dateCode,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map hotel code to hotel ID.
|
|
*
|
|
* Converts a hotel code string to its corresponding hotel ID using the
|
|
* hotel loader's mapping functionality.
|
|
*
|
|
* @param string $hotelCode The hotel code to map
|
|
*
|
|
* @return int|null The corresponding hotel ID or null if not found
|
|
*/
|
|
public function mapHotelCodeToId(string $hotelCode): ?int
|
|
{
|
|
try {
|
|
$hotelId = $this->hotelLoader->mapCodeToId($hotelCode);
|
|
|
|
$this->logger->debug('Hotel code mapping', [
|
|
'hotelCode' => $hotelCode,
|
|
'hotelId' => $hotelId,
|
|
]);
|
|
|
|
return $hotelId;
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Failed to map hotel code to ID', [
|
|
'hotelCode' => $hotelCode,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map date ID to product ID for API calls.
|
|
*
|
|
* Converts a date ID to its corresponding product ID for use with the
|
|
* remote API. This method exposes the existing loader functionality
|
|
* through the service layer.
|
|
*
|
|
* @param int $dateId The date ID to map
|
|
*
|
|
* @return int|null The corresponding product ID or null if not found
|
|
*/
|
|
public function mapDateIdToProductId(int $dateId): ?int
|
|
{
|
|
try {
|
|
$productId = $this->travelLoader->mapDateIdToProductId($dateId);
|
|
|
|
$this->logger->debug('Date ID to product ID mapping', [
|
|
'dateId' => $dateId,
|
|
'productId' => $productId,
|
|
]);
|
|
|
|
return $productId;
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Failed to map date ID to product ID', [
|
|
'dateId' => $dateId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate files mapping for available travel data.
|
|
*
|
|
* Creates a mapping of all available travel data files with their
|
|
* corresponding travel and hotel information. This method exposes
|
|
* the existing loader functionality through the service layer.
|
|
*
|
|
* @return array<int, array<string, mixed>> Array mapping of travel data files
|
|
*/
|
|
public function generateFilesMap(): array
|
|
{
|
|
try {
|
|
$mapping = $this->travelLoader->generateFilesMap();
|
|
|
|
$this->logger->debug('Generated files mapping', [
|
|
'count' => count($mapping),
|
|
]);
|
|
|
|
return $mapping;
|
|
} catch (\Exception $e) {
|
|
$this->logger->error('Failed to generate files mapping', [
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch mutability data from API.
|
|
*
|
|
* Retrieves mutability configuration data from the API for a specific travel date.
|
|
* Handles API errors and notification responses gracefully.
|
|
*
|
|
* @param int $dateId The travel date ID for API call
|
|
*
|
|
* @return BaseData|null The mutability data or null if not available or error occurred
|
|
*/
|
|
/**
|
|
* Gets mutability data for a travel date.
|
|
*
|
|
* @param int $dateId The travel date ID for API call
|
|
* @param bool $cached Whether to use cached data (default: true, TTL: 12 hours)
|
|
*
|
|
* @return BaseData|null The mutability data or null if not available or error occurred
|
|
*/
|
|
public function getMutabilityData(int $dateId, bool $cached = true): ?BaseData
|
|
{
|
|
if ($cached) {
|
|
$cacheKey = sprintf('mutability_%d', $dateId);
|
|
|
|
try {
|
|
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($dateId) {
|
|
// 12 hours TTL - mutability dates have date-only granularity
|
|
$item->expiresAfter(43200);
|
|
|
|
return $this->fetchMutabilityData($dateId);
|
|
});
|
|
} catch (InvalidArgumentException $e) {
|
|
$this->logger->error('Cache error in getMutabilityData', [
|
|
'dateId' => $dateId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
// Fallback to direct API call
|
|
return $this->fetchMutabilityData($dateId);
|
|
}
|
|
}
|
|
|
|
return $this->fetchMutabilityData($dateId);
|
|
}
|
|
|
|
/**
|
|
* Fetches mutability data directly from the API without caching.
|
|
*/
|
|
private function fetchMutabilityData(int $dateId): ?BaseData
|
|
{
|
|
try {
|
|
$mutableData = $this->apiClient->getMutableData($dateId);
|
|
|
|
if ($mutableData instanceof Notification) {
|
|
$this->logger->warning('API returned notification for mutability data', [
|
|
'dateId' => $dateId,
|
|
'message' => $mutableData->message,
|
|
'isError' => $mutableData->isError(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
$this->logger->debug('Successfully fetched mutability data', [
|
|
'dateId' => $dateId,
|
|
]);
|
|
|
|
return $mutableData;
|
|
} catch (ApiClientException $e) {
|
|
$this->logger->error('Failed to fetch mutability data from API', [
|
|
'dateId' => $dateId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply mutability data to travel services.
|
|
*
|
|
* Updates the mutability status of various travel services based on the
|
|
* provided mutability configuration data.
|
|
*
|
|
* @param Travel $travel The travel object to update
|
|
* @param BaseData $mutableData The mutability configuration data
|
|
*/
|
|
public function patchMutability(Travel $travel, BaseData $mutableData): void
|
|
{
|
|
$this->travelLoader->patchMutability($travel, $mutableData);
|
|
|
|
$this->logger->debug('Successfully patched mutability data', [
|
|
'travelId' => $travel->id,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Gets availability data for a travel date.
|
|
*
|
|
* @param int $dateId The travel date ID for API call
|
|
* @param bool $cached Whether to use cached data (default: false, TTL when cached: 60 seconds)
|
|
* @param int $ttl Cache TTL in seconds when $cached is true (default: 60 seconds)
|
|
*
|
|
* @return BaseData|null The availability data or null if not available or error occurred
|
|
*/
|
|
public function getAvailabilityData(int $dateId, bool $cached = false, int $ttl = 60): ?BaseData
|
|
{
|
|
if ($cached) {
|
|
$cacheKey = sprintf('availability_%d', $dateId);
|
|
|
|
try {
|
|
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($dateId, $ttl) {
|
|
// Short TTL - availability is volatile and changes with bookings
|
|
$item->expiresAfter($ttl);
|
|
|
|
return $this->fetchAvailabilityData($dateId);
|
|
});
|
|
} catch (InvalidArgumentException $e) {
|
|
$this->logger->error('Cache error in getAvailabilityData', [
|
|
'dateId' => $dateId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
// Fallback to direct API call
|
|
return $this->fetchAvailabilityData($dateId);
|
|
}
|
|
}
|
|
|
|
return $this->fetchAvailabilityData($dateId);
|
|
}
|
|
|
|
/**
|
|
* Fetches availability data directly from the API without caching.
|
|
*/
|
|
private function fetchAvailabilityData(int $dateId): ?BaseData
|
|
{
|
|
try {
|
|
$availabilities = $this->apiClient->getAvailabilities($dateId);
|
|
|
|
if ($availabilities instanceof Notification) {
|
|
$this->logger->warning('API returned notification for availability data', [
|
|
'dateId' => $dateId,
|
|
'message' => $availabilities->message,
|
|
'isError' => $availabilities->isError(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
|
|
$this->logger->debug('Successfully fetched availability data', [
|
|
'dateId' => $dateId,
|
|
]);
|
|
|
|
return $availabilities;
|
|
} catch (ApiClientException $e) {
|
|
$this->logger->error('Failed to fetch availability data from API', [
|
|
'dateId' => $dateId,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply availability data to travel services.
|
|
*
|
|
* Updates the availability status of additional and transportation
|
|
* services based on the provided availability data.
|
|
*
|
|
* @param Travel $travel The travel object to update
|
|
* @param BaseData $availabilities The availability data for services
|
|
*/
|
|
public function patchAvailabilities(Travel $travel, BaseData $availabilities): void
|
|
{
|
|
$this->travelLoader->patchAvailabilities($travel, $availabilities);
|
|
|
|
$this->logger->debug('Successfully patched availability data', [
|
|
'travelId' => $travel->id,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Enrich travel data with additional information.
|
|
*
|
|
* Adds hotel details and pickup information to travel data loaded from XML.
|
|
* This enrichment is necessary for complete travel information.
|
|
*
|
|
* @param Travel $travel The travel object to enrich
|
|
*/
|
|
private function enrichTravelData(Travel $travel): void
|
|
{
|
|
try {
|
|
$this->pickupLoader->patchPickupsDetails($travel);
|
|
$this->hotelLoader->patchHotelDetails($travel);
|
|
$this->patchInsuranceData($travel);
|
|
} catch (\Exception $e) {
|
|
$this->logger->warning('Failed to enrich travel data', [
|
|
'travelId' => $travel->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds insurance data to travel object.
|
|
*
|
|
* Loads all available insurances and adds them to the travel object.
|
|
* This ensures insurance options are available for booking.
|
|
*
|
|
* @param Travel $travel The travel object to enrich with insurance data
|
|
*/
|
|
private function patchInsuranceData(Travel $travel): void
|
|
{
|
|
try {
|
|
$insurances = $this->insuranceLoader->loadAll();
|
|
// Keep insurances indexed by ID for efficient lookups
|
|
$travel->insurances = $insurances;
|
|
|
|
// Hydrate package relationships after loading
|
|
// Packages lose their containedInsurances during serialization, so rebuild them
|
|
$this->hydrateInsurancePackageRelationships($travel->insurances);
|
|
} catch (\Exception $e) {
|
|
$this->logger->warning('Failed to load insurance data', [
|
|
'travelId' => $travel->id,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reconstructs containedInsurances arrays for insurance packages.
|
|
*
|
|
* Insurance packages reference other insurances via containedInsuranceIds.
|
|
* After deserialization, the containedInsurances array is empty due to
|
|
* circular reference prevention. This method rebuilds those relationships.
|
|
*
|
|
* @param array<Insurance> $insurances All insurances including packages
|
|
*/
|
|
private function hydrateInsurancePackageRelationships(array $insurances): void
|
|
{
|
|
// Build lookup map of all insurances by ID (includes complementary insurances)
|
|
$insuranceById = [];
|
|
foreach ($insurances as $insurance) {
|
|
$insuranceById[$insurance->id] = $insurance;
|
|
}
|
|
|
|
// Reconstruct containedInsurances for each package
|
|
foreach ($insurances as $insurance) {
|
|
if (!$insurance->package || empty($insurance->containedInsuranceIds)) {
|
|
continue;
|
|
}
|
|
|
|
$insurance->containedInsurances = [];
|
|
foreach ($insurance->containedInsuranceIds as $containedId) {
|
|
if (isset($insuranceById[$containedId])) {
|
|
$insurance->containedInsurances[] = $insuranceById[$containedId];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|