242 lines
5.5 KiB
TypeScript
242 lines
5.5 KiB
TypeScript
/**
|
|
* 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<OrganizationProps, 'createdAt' | 'updatedAt'>): 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],
|
|
};
|
|
}
|
|
}
|