106 lines
2.6 KiB
TypeScript
106 lines
2.6 KiB
TypeScript
import { Money } from './money.vo';
|
|
|
|
/**
|
|
* Surcharge Type Enumeration
|
|
* Common maritime shipping surcharges
|
|
*/
|
|
export enum SurchargeType {
|
|
BAF = 'BAF', // Bunker Adjustment Factor
|
|
CAF = 'CAF', // Currency Adjustment Factor
|
|
PSS = 'PSS', // Peak Season Surcharge
|
|
THC = 'THC', // Terminal Handling Charge
|
|
OTHER = 'OTHER',
|
|
}
|
|
|
|
/**
|
|
* Surcharge Value Object
|
|
* Represents additional fees applied to base freight rates
|
|
*/
|
|
export class Surcharge {
|
|
constructor(
|
|
public readonly type: SurchargeType,
|
|
public readonly amount: Money,
|
|
public readonly description?: string
|
|
) {
|
|
this.validate();
|
|
}
|
|
|
|
private validate(): void {
|
|
if (!Object.values(SurchargeType).includes(this.type)) {
|
|
throw new Error(`Invalid surcharge type: ${this.type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get human-readable surcharge label
|
|
*/
|
|
getLabel(): string {
|
|
const labels: Record<SurchargeType, string> = {
|
|
[SurchargeType.BAF]: 'Bunker Adjustment Factor',
|
|
[SurchargeType.CAF]: 'Currency Adjustment Factor',
|
|
[SurchargeType.PSS]: 'Peak Season Surcharge',
|
|
[SurchargeType.THC]: 'Terminal Handling Charge',
|
|
[SurchargeType.OTHER]: 'Other Surcharge',
|
|
};
|
|
return labels[this.type];
|
|
}
|
|
|
|
equals(other: Surcharge): boolean {
|
|
return this.type === other.type && this.amount.isEqualTo(other.amount);
|
|
}
|
|
|
|
toString(): string {
|
|
const label = this.description || this.getLabel();
|
|
return `${label}: ${this.amount.toString()}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Collection of surcharges with utility methods
|
|
*/
|
|
export class SurchargeCollection {
|
|
constructor(public readonly surcharges: Surcharge[]) {}
|
|
|
|
/**
|
|
* Calculate total surcharge amount in a specific currency
|
|
* Note: This assumes all surcharges are in the same currency
|
|
* In production, currency conversion would be needed
|
|
*/
|
|
getTotalAmount(currency: string): Money {
|
|
const relevantSurcharges = this.surcharges.filter(s => s.amount.getCurrency() === currency);
|
|
|
|
if (relevantSurcharges.length === 0) {
|
|
return Money.zero(currency);
|
|
}
|
|
|
|
return relevantSurcharges.reduce(
|
|
(total, surcharge) => total.add(surcharge.amount),
|
|
Money.zero(currency)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if collection has any surcharges
|
|
*/
|
|
isEmpty(): boolean {
|
|
return this.surcharges.length === 0;
|
|
}
|
|
|
|
/**
|
|
* Get surcharges by type
|
|
*/
|
|
getByType(type: SurchargeType): Surcharge[] {
|
|
return this.surcharges.filter(s => s.type === type);
|
|
}
|
|
|
|
/**
|
|
* Get formatted surcharge details for display
|
|
*/
|
|
getDetails(): string {
|
|
if (this.isEmpty()) {
|
|
return 'All-in price (no separate surcharges)';
|
|
}
|
|
return this.surcharges.map(s => s.toString()).join(', ');
|
|
}
|
|
}
|