220 lines
6.0 KiB
TypeScript
220 lines
6.0 KiB
TypeScript
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<string, string> = {
|
|
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, ' ');
|
|
}
|
|
}
|