/** * PortCode Value Object * * Encapsulates UN/LOCODE port code validation and behavior * * Business Rules: * - Port code must follow UN/LOCODE format (2-letter country + 3-letter/digit location) * - Port code is always uppercase * - Port code is immutable * * Format: CCLLL * - CC: ISO 3166-1 alpha-2 country code * - LLL: 3-character location code (letters or digits) * * Examples: NLRTM (Rotterdam), USNYC (New York), SGSIN (Singapore) */ export class PortCode { private readonly value: string; private constructor(code: string) { this.value = code; } static create(code: string): PortCode { if (!code || code.trim().length === 0) { throw new Error('Port code cannot be empty.'); } const normalized = code.trim().toUpperCase(); if (!PortCode.isValid(normalized)) { throw new Error( `Invalid port code format: ${code}. Must follow UN/LOCODE format (e.g., NLRTM, USNYC).` ); } return new PortCode(normalized); } private static isValid(code: string): boolean { // UN/LOCODE format: 2-letter country code + 3-character location code const unlocodePattern = /^[A-Z]{2}[A-Z0-9]{3}$/; return unlocodePattern.test(code); } getValue(): string { return this.value; } getCountryCode(): string { return this.value.substring(0, 2); } getLocationCode(): string { return this.value.substring(2); } equals(other: PortCode): boolean { return this.value === other.value; } toString(): string { return this.value; } }