146 lines
4.6 KiB
TypeScript
146 lines
4.6 KiB
TypeScript
/**
|
|
* ONE (Ocean Network Express) 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 ONERequestMapper {
|
|
toONERequest(input: CarrierRateSearchInput): any {
|
|
return {
|
|
loading_port: input.origin,
|
|
discharge_port: input.destination,
|
|
equipment_type: this.mapContainerType(input.containerType),
|
|
cargo_cutoff_date: input.departureDate,
|
|
shipment_type: input.mode,
|
|
dangerous_cargo: input.isHazmat || false,
|
|
imo_class: input.imoClass,
|
|
cargo_weight_kg: input.weight,
|
|
cargo_volume_cbm: input.volume,
|
|
};
|
|
}
|
|
|
|
fromONEResponse(oneResponse: any, originalInput: CarrierRateSearchInput): RateQuote[] {
|
|
if (!oneResponse.instant_quotes || oneResponse.instant_quotes.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
return oneResponse.instant_quotes.map((quote: any) => {
|
|
const surcharges: Surcharge[] = [];
|
|
|
|
// Parse surcharges
|
|
if (quote.additional_charges) {
|
|
for (const [key, value] of Object.entries(quote.additional_charges)) {
|
|
if (typeof value === 'number' && value > 0) {
|
|
surcharges.push({
|
|
type: key.toUpperCase(),
|
|
description: this.formatChargeName(key),
|
|
amount: value,
|
|
currency: quote.currency || 'USD',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const baseFreight = quote.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: quote.loading_port_name || originalInput.origin,
|
|
departure: new Date(quote.departure_date),
|
|
vesselName: quote.vessel_details?.name,
|
|
voyageNumber: quote.vessel_details?.voyage,
|
|
});
|
|
|
|
// Transshipment ports
|
|
if (quote.via_ports && Array.isArray(quote.via_ports)) {
|
|
quote.via_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.discharge_port_name || originalInput.destination,
|
|
arrival: new Date(quote.arrival_date),
|
|
});
|
|
|
|
const transitDays =
|
|
quote.transit_days || this.calculateTransitDays(quote.departure_date, quote.arrival_date);
|
|
|
|
return RateQuote.create({
|
|
id: uuidv4(),
|
|
carrierId: 'one',
|
|
carrierName: 'ONE',
|
|
carrierCode: 'ONEY',
|
|
origin: {
|
|
code: originalInput.origin,
|
|
name: quote.loading_port_name || originalInput.origin,
|
|
country: quote.loading_country || 'Unknown',
|
|
},
|
|
destination: {
|
|
code: originalInput.destination,
|
|
name: quote.discharge_port_name || originalInput.destination,
|
|
country: quote.discharge_country || 'Unknown',
|
|
},
|
|
pricing: {
|
|
baseFreight,
|
|
surcharges,
|
|
totalAmount,
|
|
currency: quote.currency || 'USD',
|
|
},
|
|
containerType: originalInput.containerType,
|
|
mode: (originalInput.mode as 'FCL' | 'LCL') || 'FCL',
|
|
etd: new Date(quote.departure_date),
|
|
eta: new Date(quote.arrival_date),
|
|
transitDays,
|
|
route,
|
|
availability: quote.capacity_status?.available || 0,
|
|
frequency: quote.service_info?.frequency || 'Weekly',
|
|
vesselType: 'Container Ship',
|
|
co2EmissionsKg: quote.environmental_info?.co2_emissions,
|
|
});
|
|
});
|
|
}
|
|
|
|
private mapContainerType(type: string): string {
|
|
const mapping: Record<string, string> = {
|
|
'20GP': '20DV',
|
|
'40GP': '40DV',
|
|
'40HC': '40HC',
|
|
'45HC': '45HC',
|
|
'20RF': '20RF',
|
|
'40RF': '40RH',
|
|
};
|
|
return mapping[type] || type;
|
|
}
|
|
|
|
private formatChargeName(key: string): string {
|
|
return key
|
|
.split('_')
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|