avicenna/service/data-hub/data-connector/src/modules/app-manager/car-entity/car.entity.ts

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];
}
}