import { PriceDTO, Price } from '@generated/swagger/checkout-api'; import { Availability as AvaAvailability } from '@isa/availability/data-access'; import { ensureCurrencyDefaults } from '@isa/common/data-access'; import { Availability, AvailabilityType } from '../schemas'; /** * Availability data from catalogue-api (raw response) */ export interface CatalogueAvailabilityResponse { availabilityType: AvailabilityType; ssc: number; sscText: string; supplier: { id: number }; isPrebooked: boolean; estimatedShippingDate: string; price: number; lastRequest: string; // Optional fields for shipping estimatedDelivery?: { start?: string; stop?: string }; logistician?: { id: number }; supplierProductNumber?: string; supplierInfo?: string; priceMaintained?: boolean; inStock?: number; orderDeadline?: string; } /** * Adapter for converting catalogue-api availability responses to checkout-api format. * * Handles: * - Structure mapping between different API representations * - Price object creation with VAT * - Supplier and logistician reference wrapping */ export class AvailabilityAdapter { private static readonly ADAPTER_NAME = 'AvailabilityAdapter'; /** * Converts catalogue-api availability to checkout-api AvailabilityDTO. * * @param catalogueAvailability - Raw availability from catalogue service * @param originalPrice - Original price to preserve (if different from new price) * @returns AvailabilityDTO compatible with checkout-api */ static toCheckoutFormat( catalogueAvailability: CatalogueAvailabilityResponse, originalPrice?: number, ): Availability { const availability: Availability = { availabilityType: catalogueAvailability.availabilityType, ssc: catalogueAvailability.ssc?.toString(), sscText: catalogueAvailability.sscText, supplier: { id: catalogueAvailability.supplier.id, data: { id: catalogueAvailability.supplier.id, }, // Explicitly omit externalReference to avoid TypeScript errors // (generated DTOs require externalStatus when externalReference is present) }, isPrebooked: catalogueAvailability.isPrebooked, estimatedShippingDate: catalogueAvailability.estimatedShippingDate, lastRequest: catalogueAvailability.lastRequest, // Use original price if provided, otherwise use new price price: { value: { value: originalPrice ?? catalogueAvailability.price, currency: 'EUR', currencySymbol: '€', }, }, }; // Add optional fields if present if (catalogueAvailability.estimatedDelivery) { availability.estimatedDelivery = catalogueAvailability.estimatedDelivery; } if (catalogueAvailability.logistician) { availability.logistician = { id: catalogueAvailability.logistician.id, data: { id: catalogueAvailability.logistician.id, }, // Explicitly omit externalReference to avoid TypeScript errors // (generated DTOs require externalStatus when externalReference is present) }; } if (catalogueAvailability.supplierProductNumber) { availability.supplierProductNumber = catalogueAvailability.supplierProductNumber; } if (catalogueAvailability.supplierInfo) { availability.supplierInfo = catalogueAvailability.supplierInfo; } if (catalogueAvailability.inStock !== undefined) { availability.inStock = catalogueAvailability.inStock; } return availability; } /** * Converts availability-api Availability to checkout-api AvailabilityDTO. * * Handles mapping between different API representations: * - status → availabilityType * - qty → inStock (preserves quantity information) * - Simple IDs → Entity containers for logistician/supplier * - Preserves common fields (price, ssc, dates, etc.) * * @param availability - Availability from availability-api service * @returns AvailabilityDTO compatible with checkout-api */ static fromAvailabilityApi(availability: AvaAvailability): Availability { const checkoutAvailability: Availability = { availabilityType: availability.status, ssc: availability.ssc, sscText: availability.sscText, isPrebooked: availability.isPrebooked, price: ensureCurrencyDefaults(availability.price), estimatedShippingDate: availability.at, lastRequest: availability.requested, }; // Map qty to inStock (preserve quantity information) if (availability.qty !== undefined) { checkoutAvailability.inStock = availability.qty; } // Convert logistician ID to entity container if (availability.logisticianId) { checkoutAvailability.logistician = { id: availability.logisticianId, data: { id: availability.logisticianId, }, // Explicitly omit externalReference to avoid TypeScript errors // (generated DTOs require externalStatus when externalReference is present) }; } // Convert supplier ID to entity container if (availability.supplierId) { checkoutAvailability.supplier = { id: availability.supplierId, data: { id: availability.supplierId, }, // Explicitly omit externalReference to avoid TypeScript errors // (generated DTOs require externalStatus when externalReference is present) }; } // Map supplier string to supplierInfo (alternative to supplierId) if (availability.supplier) { checkoutAvailability.supplierInfo = availability.supplier; } // Optional fields if (availability.estimatedDelivery) { checkoutAvailability.estimatedDelivery = availability.estimatedDelivery; } if (availability.supplierProductNumber) { checkoutAvailability.supplierProductNumber = availability.supplierProductNumber; } if (availability.requestReference) { checkoutAvailability.requestReference = availability.requestReference; } return checkoutAvailability; } /** * Converts PriceDTO to Price format for shopping cart operations. * * PriceDTO format (nested): * - value: { value?: number, currency?: string, ... } * - vat: { value?: number, inPercent?: number, vatType?: VATType, ... } * * Price format (flat): * - value: number (required) * - vatInPercent?: number * - vatType: VATType (required) * - vatValue?: number * - currency?: string * * @param priceDTO - PriceDTO from shopping cart item * @returns Price in flat format, or undefined if input is invalid */ static convertPriceDTOToPrice(priceDTO?: PriceDTO): Price | undefined { if (!priceDTO) { return undefined; } const value = priceDTO.value?.value; const vatType = priceDTO.vat?.vatType; // Both value and vatType are required for Price if (value === undefined || value === null || !vatType) { return undefined; } return { value, vatType, vatInPercent: priceDTO.vat?.inPercent, vatValue: priceDTO.vat?.value, currency: priceDTO.value?.currency, currencySymbol: priceDTO.value?.currencySymbol, }; } /** * Type guard for catalogue availability response */ static isValidCatalogueResponse( value: unknown, ): value is CatalogueAvailabilityResponse { if (typeof value !== 'object' || value === null) return false; return ( 'availabilityType' in value && typeof value.availabilityType === 'number' && 'ssc' in value && typeof value.ssc === 'number' && 'sscText' in value && typeof value.sscText === 'string' && 'supplier' in value && typeof value.supplier === 'object' && value.supplier !== null && 'id' in value.supplier && typeof value.supplier.id === 'number' ); } }