/** * CMA CGM 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 CMACGMRequestMapper { toCMACGMRequest(input: CarrierRateSearchInput): any { return { departure_port_locode: input.origin, arrival_port_locode: input.destination, equipment_type_code: this.mapContainerType(input.containerType), cargo_ready_date: input.departureDate, shipment_term: input.mode, cargo_type: input.isHazmat ? 'HAZARDOUS' : 'GENERAL', imo_class: input.imoClass, gross_weight_kg: input.weight, volume_cbm: input.volume, }; } fromCMACGMResponse(cgmResponse: any, originalInput: CarrierRateSearchInput): RateQuote[] { if (!cgmResponse.quotations || cgmResponse.quotations.length === 0) { return []; } return cgmResponse.quotations.map((quotation: any) => { const surcharges: Surcharge[] = [ { type: 'BAF', description: 'Bunker Surcharge', amount: quotation.charges?.bunker_surcharge || 0, currency: quotation.charges?.currency || 'USD', }, { type: 'CAF', description: 'Currency Surcharge', amount: quotation.charges?.currency_surcharge || 0, currency: quotation.charges?.currency || 'USD', }, { type: 'PSS', description: 'Peak Season', amount: quotation.charges?.peak_season || 0, currency: quotation.charges?.currency || 'USD', }, { type: 'THC', description: 'Terminal Handling', amount: quotation.charges?.thc || 0, currency: quotation.charges?.currency || 'USD', }, ].filter(s => s.amount > 0); const baseFreight = quotation.charges?.ocean_freight || 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: quotation.routing?.departure_port_name || originalInput.origin, departure: new Date(quotation.schedule?.departure_date), vesselName: quotation.vessel?.name, voyageNumber: quotation.vessel?.voyage, }); // Transshipment ports if ( quotation.routing?.transshipment_ports && Array.isArray(quotation.routing.transshipment_ports) ) { quotation.routing.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: quotation.routing?.arrival_port_name || originalInput.destination, arrival: new Date(quotation.schedule?.arrival_date), }); const transitDays = quotation.schedule?.transit_time_days || this.calculateTransitDays( quotation.schedule?.departure_date, quotation.schedule?.arrival_date ); return RateQuote.create({ id: uuidv4(), carrierId: 'cmacgm', carrierName: 'CMA CGM', carrierCode: 'CMDU', origin: { code: originalInput.origin, name: quotation.routing?.departure_port_name || originalInput.origin, country: quotation.routing?.departure_country || 'Unknown', }, destination: { code: originalInput.destination, name: quotation.routing?.arrival_port_name || originalInput.destination, country: quotation.routing?.arrival_country || 'Unknown', }, pricing: { baseFreight, surcharges, totalAmount, currency: quotation.charges?.currency || 'USD', }, containerType: originalInput.containerType, mode: (originalInput.mode as 'FCL' | 'LCL') || 'FCL', etd: new Date(quotation.schedule?.departure_date), eta: new Date(quotation.schedule?.arrival_date), transitDays, route, availability: quotation.capacity?.slots_available || 0, frequency: quotation.service?.frequency || 'Weekly', vesselType: 'Container Ship', co2EmissionsKg: quotation.environmental?.co2_kg, }); }); } private mapContainerType(type: string): string { const mapping: Record = { '20GP': '22G1', '40GP': '42G1', '40HC': '45G1', '45HC': '45G1', '20RF': '22R1', '40RF': '42R1', }; return mapping[type] || type; } 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)); } }