import { PlannedTime, Tour } from '@prisma/client'; import { areIntervalsOverlapping, endOfDay, format, Interval, isWithinInterval, startOfDay, } from 'date-fns'; import { CalculationService } from 'src/modules/feat-calculation/calculation.service'; import { clampInterval, getHoursInInterval } from 'src/utils'; const EMPLOYEE_AMOUNT_BY_ORDINANCE_TYPE = { BTW: 1, TSW: 2, KTW: 2, }; export class CarEntity { constructor( private readonly tours: Tour[], private readonly plannedTimes: PlannedTime[], public readonly carName: string, private readonly ordinanceType: string, ) {} availableTimeInInterval(interval: Interval): number { return ( this.plannedTimes .map(({ startDate, endDate }) => ({ start: new Date(startDate), end: new Date(endDate), })) .filter((plannedTimeInterval) => areIntervalsOverlapping(plannedTimeInterval, interval), ) .map((plannedTimeInterval) => getHoursInInterval(plannedTimeInterval, interval), ) .sort() .at(0) ?? 0 ); } tourAmountInInterval(interval: Interval): number { return this.toursInInterval(interval).reduce( (sum, { occupiedKm }) => (sum += occupiedKm > 30 ? occupiedKm / 30 : 1), 0, ); } toursInInterval(interval: Interval): Tour[] { return this.tours .filter(({ startDate }) => isWithinInterval(new Date(startDate), interval), ) .filter(({ occupiedKm }) => occupiedKm < 9999); } async breakEvenInInterval( interval: Interval, calculationService: CalculationService, ): Promise<{ toursRevenue; personnelCosts; consumptionCost; }> { const toursInInterval = this.toursInInterval(interval); const toursRevenue = toursInInterval.reduce( (sum, tour) => (sum += tour.revenue), 0, ); const consumptionCost = toursInInterval.reduce( (sum, tour) => (sum += tour.consumptionCosts), 0, ); const plannedTimeIntervalInInterval = this.plannedTimeIntervalInInterval(interval); if (!plannedTimeIntervalInInterval) { return { toursRevenue, personnelCosts: 0, consumptionCost, }; } const personnelCosts = calculationService.calculatePersonnelCostInInterval( clampInterval(plannedTimeIntervalInInterval, interval), this.employeeAmount, this.ordinanceType, ) / 100; return { toursRevenue, personnelCosts, consumptionCost, }; } getTourAmountAtDateCreatedToday(date: Date): number { const dateInterval: Interval = { start: startOfDay(date), end: endOfDay(date), }; const todayInterval: Interval = { start: startOfDay(new Date()), end: endOfDay(new Date()), }; return this.tours.filter( ({ startDate, createdAt }) => isWithinInterval(new Date(startDate), dateInterval) && isWithinInterval(new Date(createdAt), todayInterval), ).length; } toString(): string { return `${this.carName} ${this.tours.length} Touren ${this.plannedTimes.length} geplante Mitarbeiter ${ this.plannedTimes.length ? this.plannedTimes .map( ({ startDate, endDate }) => `${format(new Date(startDate), 'HH:mm')}-${format( new Date(endDate), 'HH:mm', )}`, ) .join(', ') : '' }`; } private plannedTimeIntervalInInterval(interval: Interval): Interval { return this.plannedTimes .map(({ startDate, endDate }) => ({ start: new Date(startDate), end: new Date(endDate), })) .filter((plannedTimeInterval) => areIntervalsOverlapping(plannedTimeInterval, interval), ) .at(0); } private hasPlannedTimeInInterval(interval: Interval): boolean { return !!this.plannedTimeIntervalInInterval(interval); } private get employeeAmount(): number { return EMPLOYEE_AMOUNT_BY_ORDINANCE_TYPE[this.ordinanceType]; } }