146 lines
4.8 KiB
TypeScript
146 lines
4.8 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { OnEvent } from '@nestjs/event-emitter';
|
|
import { addMilliseconds } from 'date-fns';
|
|
import { PubSub } from 'graphql-subscriptions';
|
|
import { generateIntervalForTimeRange } from 'src/utils';
|
|
import { CalculationService } from '../feat-calculation/calculation.service';
|
|
import { ETLEventTypes } from '../feat-etl/feat-importers/events/event-types.enum';
|
|
import { LoadingDoneEvent } from '../feat-etl/feat-importers/events/loading-done.event';
|
|
import { CarEntityService } from '../feat-business-objects/car-entity/car-entity.service';
|
|
|
|
@Injectable()
|
|
export class TimePerTourKpiService {
|
|
public readonly pubSub = new PubSub();
|
|
|
|
@OnEvent(ETLEventTypes.LoadingDone, { async: true })
|
|
notifyOnPlannedTimeUpdate(event: LoadingDoneEvent): void {
|
|
this.pubSub.publish('timePerTourUpdated', {
|
|
timePerTourUpdated: { job: event.jobId },
|
|
});
|
|
}
|
|
|
|
constructor(
|
|
private readonly calculationService: CalculationService,
|
|
private readonly carEntityService: CarEntityService,
|
|
) {}
|
|
|
|
async toursPerTimeKPI(ordinanceType: string, date = new Date()) {
|
|
const fullRangeInterval = generateIntervalForTimeRange(
|
|
'05:00-01:00',
|
|
'Europe/Berlin',
|
|
date,
|
|
);
|
|
|
|
const subIntervals = [
|
|
generateIntervalForTimeRange('05:00-12:00', 'Europe/Berlin'),
|
|
generateIntervalForTimeRange('12:00-18:30', 'Europe/Berlin'),
|
|
generateIntervalForTimeRange('18:30-01:00', 'Europe/Berlin'),
|
|
];
|
|
|
|
const carEntities = await this.carEntityService.getCarsInInterval(
|
|
fullRangeInterval,
|
|
ordinanceType,
|
|
);
|
|
|
|
return Promise.all(
|
|
subIntervals
|
|
.map(({ start, end }, index, array) => ({
|
|
start,
|
|
end: index + 1 !== array.length ? addMilliseconds(end, -1) : end,
|
|
}))
|
|
.map(async (interval) => {
|
|
const cars = await Promise.all(
|
|
carEntities.map(async (carEntity) => {
|
|
const tourAmount = carEntity.tourAmountInInterval(interval);
|
|
const availableTime = carEntity.availableTimeInInterval(interval);
|
|
|
|
const { personnelCosts, consumptionCost, toursRevenue } =
|
|
await carEntity.breakEvenInInterval(
|
|
interval,
|
|
this.calculationService,
|
|
);
|
|
|
|
const baseValue = this.calculateKPIBaseValue(
|
|
availableTime,
|
|
tourAmount,
|
|
);
|
|
return {
|
|
carName: carEntity.carName ?? 'Touren ohne Zuweisung',
|
|
kpi: {
|
|
value: !carEntity.carName ? tourAmount : baseValue,
|
|
meta: {
|
|
tourAmount: Math.round(tourAmount * 100) / 100,
|
|
availableTime: Math.round(availableTime * 100) / 100,
|
|
baseValue,
|
|
breakEven:
|
|
Math.round(
|
|
(toursRevenue - personnelCosts - consumptionCost) * 100,
|
|
) / 100,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
);
|
|
|
|
const tourAmount = carEntities.reduce(
|
|
(sum, carEntity) =>
|
|
(sum += carEntity.tourAmountInInterval(interval)),
|
|
0,
|
|
);
|
|
|
|
const availableTime = carEntities.reduce(
|
|
(sum, carEntity) =>
|
|
(sum += carEntity.availableTimeInInterval(interval)),
|
|
0,
|
|
);
|
|
|
|
let revenue = 0;
|
|
let personnelCost = 0;
|
|
let consumptionCost = 0;
|
|
for (const carEntity of carEntities) {
|
|
const {
|
|
personnelCosts,
|
|
consumptionCost: cc,
|
|
toursRevenue,
|
|
} = await carEntity.breakEvenInInterval(
|
|
interval,
|
|
this.calculationService,
|
|
);
|
|
revenue += toursRevenue;
|
|
personnelCost += personnelCosts;
|
|
consumptionCost += cc;
|
|
}
|
|
const baseValue = this.calculateKPIBaseValue(
|
|
availableTime,
|
|
tourAmount,
|
|
);
|
|
return {
|
|
interval,
|
|
kpi: {
|
|
value: baseValue,
|
|
meta: {
|
|
tourAmount: Math.round(tourAmount * 100) / 100,
|
|
availableTime: Math.round(availableTime * 100) / 100,
|
|
baseValue,
|
|
breakEven:
|
|
Math.round(
|
|
(revenue - personnelCost - consumptionCost) * 100,
|
|
) / 100,
|
|
},
|
|
},
|
|
cars,
|
|
};
|
|
}),
|
|
);
|
|
}
|
|
|
|
calculateKPIBaseValue(availableTime: number, tourAmount: number): number {
|
|
if (tourAmount === 0 && availableTime > 0) {
|
|
return Math.round((availableTime / 0.01) * 1000) / 1000;
|
|
} else if (tourAmount === 0) {
|
|
return -1;
|
|
}
|
|
return Math.round((availableTime / tourAmount) * 1000) / 1000;
|
|
}
|
|
}
|