import { CsvRate } from '../entities/csv-rate.entity'; export interface PriceCalculationParams { volumeCBM: number; weightKG: number; palletCount: number; hasDangerousGoods: boolean; requiresSpecialHandling: boolean; requiresTailgate: boolean; requiresStraps: boolean; requiresThermalCover: boolean; hasRegulatedProducts: boolean; requiresAppointment: boolean; } export interface PriceBreakdown { basePrice: number; volumeCharge: number; weightCharge: number; palletCharge: number; surcharges: SurchargeItem[]; totalSurcharges: number; totalPrice: number; currency: string; } export interface SurchargeItem { code: string; description: string; amount: number; type: 'FIXED' | 'PER_UNIT' | 'PERCENTAGE'; } /** * Service de calcul de prix pour les tarifs CSV * Calcule le prix total basé sur le volume, poids, palettes et services additionnels */ export class CsvRatePriceCalculatorService { /** * Calcule le prix total pour un tarif CSV donné */ calculatePrice(rate: CsvRate, params: PriceCalculationParams): PriceBreakdown { // 1. Prix de base const basePrice = rate.pricing.basePriceUSD.getAmount(); // 2. Frais au volume (USD par CBM) const volumeCharge = rate.pricing.pricePerCBM * params.volumeCBM; // 3. Frais au poids (USD par KG) const weightCharge = rate.pricing.pricePerKG * params.weightKG; // 4. Frais de palettes (25 USD par palette) const palletCharge = params.palletCount * 25; // 5. Surcharges standard du CSV const standardSurcharges = this.parseStandardSurcharges(rate.getSurchargeDetails(), params); // 6. Surcharges additionnelles basées sur les services const additionalSurcharges = this.calculateAdditionalSurcharges(params); // 7. Total des surcharges const allSurcharges = [...standardSurcharges, ...additionalSurcharges]; const totalSurcharges = allSurcharges.reduce((sum, s) => sum + s.amount, 0); // 8. Prix total const totalPrice = basePrice + volumeCharge + weightCharge + palletCharge + totalSurcharges; return { basePrice, volumeCharge, weightCharge, palletCharge, surcharges: allSurcharges, totalSurcharges, totalPrice: Math.round(totalPrice * 100) / 100, // Arrondi à 2 décimales currency: rate.currency || 'USD', }; } /** * Parse les surcharges standard du format CSV * Format: "DOC:10 | ISPS:7 | HANDLING:20 W | DG_FEE:65" */ private parseStandardSurcharges( surchargeDetails: string | null, params: PriceCalculationParams ): SurchargeItem[] { if (!surchargeDetails) { return []; } const surcharges: SurchargeItem[] = []; const items = surchargeDetails.split('|').map(s => s.trim()); for (const item of items) { const match = item.match(/^([A-Z_]+):(\d+(?:\.\d+)?)\s*([WP%]?)$/); if (!match) continue; const [, code, amountStr, type] = match; let amount = parseFloat(amountStr); let surchargeType: 'FIXED' | 'PER_UNIT' | 'PERCENTAGE' = 'FIXED'; // Calcul selon le type if (type === 'W') { // Par poids (W = Weight) amount = amount * params.weightKG; surchargeType = 'PER_UNIT'; } else if (type === 'P') { // Par palette amount = amount * params.palletCount; surchargeType = 'PER_UNIT'; } else if (type === '%') { // Pourcentage (sera appliqué sur le total) surchargeType = 'PERCENTAGE'; } // Certaines surcharges ne s'appliquent que si certaines conditions sont remplies if (code === 'DG_FEE' && !params.hasDangerousGoods) { continue; // Skip DG fee si pas de marchandises dangereuses } surcharges.push({ code, description: this.getSurchargeDescription(code), amount: Math.round(amount * 100) / 100, type: surchargeType, }); } return surcharges; } /** * Calcule les surcharges additionnelles basées sur les services demandés */ private calculateAdditionalSurcharges(params: PriceCalculationParams): SurchargeItem[] { const surcharges: SurchargeItem[] = []; if (params.requiresSpecialHandling) { surcharges.push({ code: 'SPECIAL_HANDLING', description: 'Manutention particulière', amount: 75, type: 'FIXED', }); } if (params.requiresTailgate) { surcharges.push({ code: 'TAILGATE', description: 'Hayon élévateur', amount: 50, type: 'FIXED', }); } if (params.requiresStraps) { surcharges.push({ code: 'STRAPS', description: 'Sangles de sécurité', amount: 30, type: 'FIXED', }); } if (params.requiresThermalCover) { surcharges.push({ code: 'THERMAL_COVER', description: 'Couverture thermique', amount: 100, type: 'FIXED', }); } if (params.hasRegulatedProducts) { surcharges.push({ code: 'REGULATED_PRODUCTS', description: 'Produits réglementés', amount: 80, type: 'FIXED', }); } if (params.requiresAppointment) { surcharges.push({ code: 'APPOINTMENT', description: 'Livraison sur rendez-vous', amount: 40, type: 'FIXED', }); } return surcharges; } /** * Retourne la description d'un code de surcharge standard */ private getSurchargeDescription(code: string): string { const descriptions: Record = { DOC: 'Documentation fee', ISPS: 'ISPS Security', HANDLING: 'Handling charges', SOLAS: 'SOLAS VGM', CUSTOMS: 'Customs clearance', AMS_ACI: 'AMS/ACI filing', DG_FEE: 'Dangerous goods fee', BAF: 'Bunker Adjustment Factor', CAF: 'Currency Adjustment Factor', THC: 'Terminal Handling Charges', BL_FEE: 'Bill of Lading fee', TELEX_RELEASE: 'Telex release', ORIGIN_CHARGES: 'Origin charges', DEST_CHARGES: 'Destination charges', }; return descriptions[code] || code.replace(/_/g, ' '); } }