/** * Organization Entity * * Represents a business organization (freight forwarder, carrier, or shipper) * in the Xpeditis platform. * * Business Rules: * - SCAC code must be unique across all carrier organizations * - Name must be unique * - Type must be valid (FREIGHT_FORWARDER, CARRIER, SHIPPER) */ export enum OrganizationType { FREIGHT_FORWARDER = 'FREIGHT_FORWARDER', CARRIER = 'CARRIER', SHIPPER = 'SHIPPER', } export interface OrganizationAddress { street: string; city: string; state?: string; postalCode: string; country: string; } export interface OrganizationDocument { id: string; type: string; name: string; url: string; uploadedAt: Date; } export interface OrganizationProps { id: string; name: string; type: OrganizationType; scac?: string; // Standard Carrier Alpha Code (for carriers only) siren?: string; // French SIREN number (9 digits) eori?: string; // EU EORI number contact_phone?: string; // Contact phone number contact_email?: string; // Contact email address address: OrganizationAddress; logoUrl?: string; documents: OrganizationDocument[]; createdAt: Date; updatedAt: Date; isActive: boolean; } export class Organization { private readonly props: OrganizationProps; private constructor(props: OrganizationProps) { this.props = props; } /** * Factory method to create a new Organization */ static create(props: Omit): Organization { const now = new Date(); // Validate SCAC code if provided if (props.scac && !Organization.isValidSCAC(props.scac)) { throw new Error('Invalid SCAC code format. Must be 4 uppercase letters.'); } // Validate that carriers have SCAC codes if (props.type === OrganizationType.CARRIER && !props.scac) { throw new Error('Carrier organizations must have a SCAC code.'); } // Validate that non-carriers don't have SCAC codes if (props.type !== OrganizationType.CARRIER && props.scac) { throw new Error('Only carrier organizations can have SCAC codes.'); } return new Organization({ ...props, createdAt: now, updatedAt: now, }); } /** * Factory method to reconstitute from persistence */ static fromPersistence(props: OrganizationProps): Organization { return new Organization(props); } /** * Validate SCAC code format * SCAC = Standard Carrier Alpha Code (4 uppercase letters) */ private static isValidSCAC(scac: string): boolean { const scacPattern = /^[A-Z]{4}$/; return scacPattern.test(scac); } // Getters get id(): string { return this.props.id; } get name(): string { return this.props.name; } get type(): OrganizationType { return this.props.type; } get scac(): string | undefined { return this.props.scac; } get siren(): string | undefined { return this.props.siren; } get eori(): string | undefined { return this.props.eori; } get contactPhone(): string | undefined { return this.props.contact_phone; } get contactEmail(): string | undefined { return this.props.contact_email; } get address(): OrganizationAddress { return { ...this.props.address }; } get logoUrl(): string | undefined { return this.props.logoUrl; } get documents(): OrganizationDocument[] { return [...this.props.documents]; } get createdAt(): Date { return this.props.createdAt; } get updatedAt(): Date { return this.props.updatedAt; } get isActive(): boolean { return this.props.isActive; } // Business methods isCarrier(): boolean { return this.props.type === OrganizationType.CARRIER; } isFreightForwarder(): boolean { return this.props.type === OrganizationType.FREIGHT_FORWARDER; } isShipper(): boolean { return this.props.type === OrganizationType.SHIPPER; } updateName(name: string): void { if (!name || name.trim().length === 0) { throw new Error('Organization name cannot be empty.'); } this.props.name = name.trim(); this.props.updatedAt = new Date(); } updateAddress(address: OrganizationAddress): void { this.props.address = { ...address }; this.props.updatedAt = new Date(); } updateSiren(siren: string): void { this.props.siren = siren; this.props.updatedAt = new Date(); } updateEori(eori: string): void { this.props.eori = eori; this.props.updatedAt = new Date(); } updateContactPhone(phone: string): void { this.props.contact_phone = phone; this.props.updatedAt = new Date(); } updateContactEmail(email: string): void { this.props.contact_email = email; this.props.updatedAt = new Date(); } updateLogoUrl(logoUrl: string): void { this.props.logoUrl = logoUrl; this.props.updatedAt = new Date(); } addDocument(document: OrganizationDocument): void { this.props.documents.push(document); this.props.updatedAt = new Date(); } removeDocument(documentId: string): void { this.props.documents = this.props.documents.filter(doc => doc.id !== documentId); this.props.updatedAt = new Date(); } deactivate(): void { this.props.isActive = false; this.props.updatedAt = new Date(); } activate(): void { this.props.isActive = true; this.props.updatedAt = new Date(); } /** * Convert to plain object for persistence */ toObject(): OrganizationProps { return { ...this.props, address: { ...this.props.address }, documents: [...this.props.documents], }; } }