161 lines
4.0 KiB
TypeScript
161 lines
4.0 KiB
TypeScript
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];
|
|
}
|
|
}
|