/** * Hapag-Lloyd Request/Response Mapper */ import { Injectable } from '@nestjs/common'; import { CarrierRateSearchInput } from '@domain/ports/out/carrier-connector.port'; import { RateQuote, RouteSegment, Surcharge } from '@domain/entities/rate-quote.entity'; import { v4 as uuidv4 } from 'uuid'; @Injectable() export class HapagLloydRequestMapper { toHapagRequest(input: CarrierRateSearchInput): any { return { place_of_receipt: input.origin, place_of_delivery: input.destination, container_type: this.mapContainerType(input.containerType), cargo_cutoff_date: input.departureDate, service_type: input.mode === 'FCL' ? 'CY-CY' : 'CFS-CFS', hazardous: input.isHazmat || false, imo_class: input.imoClass, weight_metric_tons: input.weight ? input.weight / 1000 : undefined, volume_cubic_meters: input.volume, }; } fromHapagResponse(hapagResponse: any, originalInput: CarrierRateSearchInput): RateQuote[] { if (!hapagResponse.quotes || hapagResponse.quotes.length === 0) { return []; } return hapagResponse.quotes.map((quote: any) => { const surcharges: Surcharge[] = []; if (quote.bunker_charge) { surcharges.push({ type: 'BAF', description: 'Bunker Adjustment Factor', amount: quote.bunker_charge, currency: quote.currency || 'EUR', }); } if (quote.security_charge) { surcharges.push({ type: 'SEC', description: 'Security Charge', amount: quote.security_charge, currency: quote.currency || 'EUR', }); } if (quote.terminal_charge) { surcharges.push({ type: 'THC', description: 'Terminal Handling Charge', amount: quote.terminal_charge, currency: quote.currency || 'EUR', }); } const baseFreight = quote.ocean_freight_rate || 0; const totalSurcharges = surcharges.reduce((sum, s) => sum + s.amount, 0); const totalAmount = baseFreight + totalSurcharges; // Build route segments const route: RouteSegment[] = []; // Origin port route.push({ portCode: originalInput.origin, portName: quote.origin_port_name || originalInput.origin, departure: new Date(quote.estimated_time_of_departure), vesselName: quote.vessel_name, voyageNumber: quote.voyage_number, }); // Transshipment ports if (quote.transshipment_ports && Array.isArray(quote.transshipment_ports)) { quote.transshipment_ports.forEach((port: any) => { route.push({ portCode: port.code || port, portName: port.name || port.code || port, }); }); } // Destination port route.push({ portCode: originalInput.destination, portName: quote.destination_port_name || originalInput.destination, arrival: new Date(quote.estimated_time_of_arrival), }); const transitDays = quote.transit_time_days || this.calculateTransitDays( quote.estimated_time_of_departure, quote.estimated_time_of_arrival ); return RateQuote.create({ id: uuidv4(), carrierId: 'hapag-lloyd', carrierName: 'Hapag-Lloyd', carrierCode: 'HLCU', origin: { code: originalInput.origin, name: quote.origin_port_name || originalInput.origin, country: quote.origin_country || 'Unknown', }, destination: { code: originalInput.destination, name: quote.destination_port_name || originalInput.destination, country: quote.destination_country || 'Unknown', }, pricing: { baseFreight, surcharges, totalAmount, currency: quote.currency || 'EUR', }, containerType: originalInput.containerType, mode: (originalInput.mode as 'FCL' | 'LCL') || 'FCL', etd: new Date(quote.estimated_time_of_departure), eta: new Date(quote.estimated_time_of_arrival), transitDays, route, availability: quote.space_available || 0, frequency: quote.service_frequency || 'Weekly', vesselType: 'Container Ship', co2EmissionsKg: quote.carbon_footprint, }); }); } private mapContainerType(type: string): string { return type; // Hapag-Lloyd uses standard ISO codes } private calculateTransitDays(departure?: string, arrival?: string): number { if (!departure || !arrival) return 0; const depDate = new Date(departure); const arrDate = new Date(arrival); const diff = arrDate.getTime() - depDate.getTime(); return Math.ceil(diff / (1000 * 60 * 60 * 24)); } }