xpeditis2.0/apps/backend/src/domain/value-objects/date-range.vo.ts
2025-10-27 20:54:01 +01:00

119 lines
2.8 KiB
TypeScript

/**
* DateRange Value Object
*
* Encapsulates ETD/ETA date range with validation
*
* Business Rules:
* - End date must be after start date
* - Dates cannot be in the past (for new shipments)
* - Date range is immutable
*/
export class DateRange {
private readonly startDate: Date;
private readonly endDate: Date;
private constructor(startDate: Date, endDate: Date) {
this.startDate = startDate;
this.endDate = endDate;
}
static create(startDate: Date, endDate: Date, allowPastDates = false): DateRange {
if (!startDate || !endDate) {
throw new Error('Start date and end date are required.');
}
if (endDate <= startDate) {
throw new Error('End date must be after start date.');
}
if (!allowPastDates) {
const now = new Date();
now.setHours(0, 0, 0, 0); // Reset time to start of day
if (startDate < now) {
throw new Error('Start date cannot be in the past.');
}
}
return new DateRange(new Date(startDate), new Date(endDate));
}
/**
* Create from ETD and transit days
*/
static fromTransitDays(etd: Date, transitDays: number): DateRange {
if (transitDays <= 0) {
throw new Error('Transit days must be positive.');
}
const eta = new Date(etd);
eta.setDate(eta.getDate() + transitDays);
return DateRange.create(etd, eta, true);
}
getStartDate(): Date {
return new Date(this.startDate);
}
getEndDate(): Date {
return new Date(this.endDate);
}
getDurationInDays(): number {
const diffTime = this.endDate.getTime() - this.startDate.getTime();
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
}
getDurationInHours(): number {
const diffTime = this.endDate.getTime() - this.startDate.getTime();
return Math.ceil(diffTime / (1000 * 60 * 60));
}
contains(date: Date): boolean {
return date >= this.startDate && date <= this.endDate;
}
overlaps(other: DateRange): boolean {
return this.startDate <= other.endDate && this.endDate >= other.startDate;
}
isFutureRange(): boolean {
const now = new Date();
return this.startDate > now;
}
isPastRange(): boolean {
const now = new Date();
return this.endDate < now;
}
isCurrentRange(): boolean {
const now = new Date();
return this.contains(now);
}
equals(other: DateRange): boolean {
return (
this.startDate.getTime() === other.startDate.getTime() &&
this.endDate.getTime() === other.endDate.getTime()
);
}
toString(): string {
return `${this.formatDate(this.startDate)} - ${this.formatDate(this.endDate)}`;
}
private formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
toObject(): { startDate: Date; endDate: Date } {
return {
startDate: new Date(this.startDate),
endDate: new Date(this.endDate),
};
}
}