/** * Volume Value Object * Represents shipping volume in CBM (Cubic Meters) and weight in KG * * Business Rule: Price is calculated using freight class rule: * - Take the higher of: (volumeCBM * pricePerCBM) or (weightKG * pricePerKG) */ export class Volume { constructor( public readonly cbm: number, public readonly weightKG: number, ) { this.validate(); } private validate(): void { if (this.cbm < 0) { throw new Error('Volume in CBM cannot be negative'); } if (this.weightKG < 0) { throw new Error('Weight in KG cannot be negative'); } if (this.cbm === 0 && this.weightKG === 0) { throw new Error('Either volume or weight must be greater than zero'); } } /** * Check if this volume is within the specified range */ isWithinRange(minCBM: number, maxCBM: number, minKG: number, maxKG: number): boolean { const cbmInRange = this.cbm >= minCBM && this.cbm <= maxCBM; const weightInRange = this.weightKG >= minKG && this.weightKG <= maxKG; return cbmInRange && weightInRange; } /** * Calculate freight price using the freight class rule * Returns the higher value between volume-based and weight-based pricing */ calculateFreightPrice(pricePerCBM: number, pricePerKG: number): number { const volumePrice = this.cbm * pricePerCBM; const weightPrice = this.weightKG * pricePerKG; return Math.max(volumePrice, weightPrice); } equals(other: Volume): boolean { return this.cbm === other.cbm && this.weightKG === other.weightKG; } toString(): string { return `${this.cbm} CBM / ${this.weightKG} KG`; } }