(dc): data-connector enhancements
This commit is contained in:
parent
44f627499a
commit
08e3b3247c
|
|
@ -1,26 +1,28 @@
|
|||
FROM node:20.15.1-alpine AS builder
|
||||
|
||||
WORKDIR /app/builder
|
||||
COPY package.json .
|
||||
COPY package-lock.json .
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20.15.1-alpine
|
||||
|
||||
ENV OTEL_TRACES_EXPORTER="otlp"
|
||||
ENV OTEL_METRICS_EXPORTER="otlp"
|
||||
ENV OTEL_LOGS_EXPORTER="otlp"
|
||||
ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
|
||||
# ENV OTEL_TRACES_EXPORTER="otlp"
|
||||
# ENV OTEL_METRICS_EXPORTER="otlp"
|
||||
# ENV OTEL_LOGS_EXPORTER="otlp"
|
||||
# ENV NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
|
||||
|
||||
# To setup your own Service Instance ID
|
||||
ENV OTEL_RESOURCE_ATTRIBUTES="service.instance.id=avicenna.data-connector"
|
||||
# ENV OTEL_RESOURCE_ATTRIBUTES="service.instance.id=avicenna.data-connector"
|
||||
# ENV OTEL_NODE_RESOURCE_DETECTORS="env,host,os"
|
||||
|
||||
# To have a Service Instance ID generated using a random UUID
|
||||
# Requires auto-instrumentations-node version 0.46.0+
|
||||
ENV OTEL_NODE_RESOURCE_DETECTORS="env,host,os,serviceinstance,container"
|
||||
# ENV OTEL_NODE_RESOURCE_DETECTORS="env,host,os,serviceinstance,container"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
@ -31,4 +33,4 @@ COPY --from=builder /app/builder/docker-entrypoint.sh .
|
|||
|
||||
ENTRYPOINT [ "./docker-entrypoint.sh" ]
|
||||
|
||||
CMD [ "node", "./src/main.js" ]
|
||||
CMD ["node", "./src/main.js" ]
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -35,12 +35,9 @@
|
|||
"@nestjs/platform-express": "^11.0.15",
|
||||
"@nestjs/schedule": "^5.0.1",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.57.1",
|
||||
"@opentelemetry/exporter-metrics-otlp-proto": "^0.200.0",
|
||||
"@opentelemetry/exporter-trace-otlp-proto": "^0.200.0",
|
||||
"@opentelemetry/resources": "^2.0.0",
|
||||
"@opentelemetry/sdk-node": "^0.200.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.31.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.60.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
|
||||
"@opentelemetry/sdk-node": "^0.202.0",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const BTW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 1665,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
@ -53,7 +53,7 @@ export const BTW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 1665,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
@ -87,7 +87,7 @@ export const BTW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 1765,
|
||||
timeRange: '18:00-07:30',
|
||||
|
|
@ -107,7 +107,7 @@ export const BTW_AOK_TARIFFS = [
|
|||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft BTW',
|
||||
validFrom: startOfDay(new Date('2023-05-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2024-06-30')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -121,7 +121,7 @@ export const BTW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 1865,
|
||||
timeRange: '18:00-07:30',
|
||||
|
|
@ -138,4 +138,145 @@ export const BTW_AOK_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft BTW',
|
||||
validFrom: startOfDay(new Date('2024-07-01')),
|
||||
validTo: endOfDay(new Date('2024-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4000,
|
||||
timeRange: '00:00-14:29',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4000,
|
||||
timeRange: '18:01-23:59',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt (14:30-18:00)',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4500,
|
||||
timeRange: '14:30-18:00',
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 200,
|
||||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 2500,
|
||||
timeRange: '18:00-07:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 2500,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 2500,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft BTW',
|
||||
validFrom: startOfDay(new Date('2025-01-01')),
|
||||
validTo: endOfDay(new Date('2025-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4250,
|
||||
timeRange: '00:00-14:29',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4250,
|
||||
timeRange: '18:01-23:59',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt (14:30-18:00)',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5000,
|
||||
timeRange: '14:30-18:00',
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 200,
|
||||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 3000,
|
||||
timeRange: '18:00-07:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 3000,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 3000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft BTW',
|
||||
validFrom: startOfDay(new Date('2026-01-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4500,
|
||||
timeRange: '00:00-14:29',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4500,
|
||||
timeRange: '18:01-23:59',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt (14:30-18:00)',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5000,
|
||||
timeRange: '14:30-18:00',
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 200,
|
||||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 3000,
|
||||
timeRange: '18:00-07:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 3000,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 3000,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export const BTW_DAK_VDEK_TARIFFS = [
|
|||
{
|
||||
description: 'DAK + VDEK BTW',
|
||||
validFrom: startOfDay(new Date('2023-06-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2024-08-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -38,4 +38,22 @@ export const BTW_DAK_VDEK_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'DAK + VDEK BTW',
|
||||
validFrom: startOfDay(new Date('2024-09-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 4400,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 180,
|
||||
kmInclusive: 21,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { CostTypeVariant } from '@prisma/client';
|
||||
import { startOfDay } from 'date-fns';
|
||||
import { endOfDay, startOfDay } from 'date-fns';
|
||||
|
||||
export const BTW_PRIVAT_TARIFFS = [
|
||||
{
|
||||
description: 'Privat BTW',
|
||||
validFrom: startOfDay(new Date('2020-01-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2023-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -24,7 +24,46 @@ export const BTW_PRIVAT_TARIFFS = [
|
|||
kmInclusive: 15,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4000,
|
||||
timeRange: '19:00-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 4000,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 4000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'Privat BTW',
|
||||
validFrom: startOfDay(new Date('2024-01-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 8500,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2000,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 16. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 250,
|
||||
kmInclusive: 15,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4000,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
|
|||
|
|
@ -5,45 +5,6 @@ export const KTW_AOK_TARIFFS = [
|
|||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft KTW',
|
||||
validFrom: startOfDay(new Date('2019-04-01')),
|
||||
validTo: endOfDay(new Date('2021-04-22')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 8936,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 70,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 286,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4003,
|
||||
timeRange: '19:00-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 4003,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 4003,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft KTW',
|
||||
validFrom: startOfDay(new Date('2021-04-23')),
|
||||
validTo: endOfDay(new Date('2021-06-30')),
|
||||
costTypes: [
|
||||
{
|
||||
|
|
@ -51,18 +12,24 @@ export const KTW_AOK_TARIFFS = [
|
|||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 8936,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 177,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 70,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 177,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 286,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4003,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
@ -77,12 +44,6 @@ export const KTW_AOK_TARIFFS = [
|
|||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 4003,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 286,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -106,7 +67,13 @@ export const KTW_AOK_TARIFFS = [
|
|||
costAmount: 181,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 293,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4104,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
@ -121,12 +88,6 @@ export const KTW_AOK_TARIFFS = [
|
|||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 4104,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 293,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -174,7 +135,7 @@ export const KTW_AOK_TARIFFS = [
|
|||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6500,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -218,7 +179,7 @@ export const KTW_AOK_TARIFFS = [
|
|||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft KTW',
|
||||
validFrom: startOfDay(new Date('2024-01-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2024-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -260,7 +221,7 @@ export const KTW_AOK_TARIFFS = [
|
|||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6919,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -301,4 +262,128 @@ export const KTW_AOK_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft KTW',
|
||||
validFrom: startOfDay(new Date('2025-01-01')),
|
||||
validTo: endOfDay(new Date('2025-06-30')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 7500,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 3000,
|
||||
},
|
||||
{
|
||||
description: 'Modernes Einsatzfahrzeug',
|
||||
variant: CostTypeVariant.FAHRZEUGZUSCHLAG,
|
||||
costAmount: 1600,
|
||||
// excludeCarNames: [
|
||||
// '1/KTW-01',
|
||||
// '1/KTW-02',
|
||||
// '1/KTW-03',
|
||||
// '1/KTW-04',
|
||||
// '1/KTW-05-E',
|
||||
// '1/KTW-06',
|
||||
// ],
|
||||
},
|
||||
{
|
||||
description: 'Leitstellenmanagement',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 900,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag pauschal',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2500,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 385,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 8500,
|
||||
timeRange: '18:30-06:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 8500,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 8500,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft KTW',
|
||||
validFrom: startOfDay(new Date('2025-07-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 7831,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 3132,
|
||||
},
|
||||
{
|
||||
description: 'Modernes Einsatzfahrzeug',
|
||||
variant: CostTypeVariant.FAHRZEUGZUSCHLAG,
|
||||
costAmount: 1671,
|
||||
// excludeCarNames: [
|
||||
// '1/KTW-01',
|
||||
// '1/KTW-02',
|
||||
// '1/KTW-03',
|
||||
// '1/KTW-04',
|
||||
// '1/KTW-05-E',
|
||||
// '1/KTW-06',
|
||||
// ],
|
||||
},
|
||||
{
|
||||
description: 'Leitstellenmanagement',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 940,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag pauschal',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2610,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 19. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 402,
|
||||
kmInclusive: 18,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 8875,
|
||||
timeRange: '19:00-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 8875,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 8875,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,40 +5,6 @@ export const KTW_DAK_TARIFFS = [
|
|||
{
|
||||
description: 'DAK KTW',
|
||||
validFrom: startOfDay(new Date('2018-02-01')),
|
||||
validTo: endOfDay(new Date('2021-04-22')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 8507,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 284,
|
||||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4728,
|
||||
timeRange: '18:30-06:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 4728,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 4728,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'DAK KTW',
|
||||
validFrom: startOfDay(new Date('2021-04-23')),
|
||||
validTo: endOfDay(new Date('2021-09-30')),
|
||||
costTypes: [
|
||||
{
|
||||
|
|
@ -58,7 +24,7 @@ export const KTW_DAK_TARIFFS = [
|
|||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4728,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -102,7 +68,7 @@ export const KTW_DAK_TARIFFS = [
|
|||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5300,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -170,7 +136,7 @@ export const KTW_DAK_TARIFFS = [
|
|||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5485,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -214,7 +180,7 @@ export const KTW_DAK_TARIFFS = [
|
|||
{
|
||||
description: 'DAK KTW',
|
||||
validFrom: startOfDay(new Date('2024-01-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2024-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -238,25 +204,25 @@ export const KTW_DAK_TARIFFS = [
|
|||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5728,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6644,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7198,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5728,
|
||||
timeRange: '05:00-06:00',
|
||||
|
|
@ -297,4 +263,262 @@ export const KTW_DAK_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'DAK KTW',
|
||||
validFrom: startOfDay(new Date('2025-01-01')),
|
||||
validTo: endOfDay(new Date('2025-04-30')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 9648,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2144,
|
||||
},
|
||||
{
|
||||
description: 'Modernes Einsatzfahrzeug',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 1072,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 17. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 354,
|
||||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6140,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7122,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7716,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6140,
|
||||
timeRange: '05:00-06:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 5800,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 5800,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.A',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 3981,
|
||||
infectionType: '2.A',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.B',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 15914,
|
||||
infectionType: '2.B',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.1',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 18298,
|
||||
infectionType: '3.1',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.2',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 18298,
|
||||
infectionType: '3.2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'DAK KTW',
|
||||
validFrom: startOfDay(new Date('2025-05-01')),
|
||||
validTo: endOfDay(new Date('2025-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 10073,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2239,
|
||||
},
|
||||
{
|
||||
description: 'Modernes Einsatzfahrzeug',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 1119,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 17. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 369,
|
||||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6411,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7436,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 8057,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6411,
|
||||
timeRange: '05:00-06:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 6055,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 6055,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.A',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 4157,
|
||||
infectionType: '2.A',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.B',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 16616,
|
||||
infectionType: '2.B',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.1',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 19105,
|
||||
infectionType: '3.1',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.2',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 19105,
|
||||
infectionType: '3.2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'DAK KTW',
|
||||
validFrom: startOfDay(new Date('2026-01-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 10142,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2239,
|
||||
},
|
||||
{
|
||||
description: 'Modernes Einsatzfahrzeug',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 1119,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 17. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 369,
|
||||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6411,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7436,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 8057,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6411,
|
||||
timeRange: '05:00-06:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 6055,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 6055,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.A',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 4157,
|
||||
infectionType: '2.A',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.B',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 16616,
|
||||
infectionType: '2.B',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.1',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 19105,
|
||||
infectionType: '3.1',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.2',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 19105,
|
||||
infectionType: '3.2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { CostTypeVariant } from '@prisma/client';
|
||||
import { startOfDay } from 'date-fns';
|
||||
import { endOfDay, startOfDay } from 'date-fns';
|
||||
|
||||
export const KTW_PRIVAT_TARIFFS = [
|
||||
{
|
||||
description: 'Privat KTW',
|
||||
validFrom: startOfDay(new Date('2020-01-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2023-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -65,4 +65,72 @@ export const KTW_PRIVAT_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'Privat KTW',
|
||||
validFrom: startOfDay(new Date('2024-01-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 19000,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 3300,
|
||||
},
|
||||
{
|
||||
description: 'Hygienemaßnahmen',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 1500,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 16. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 390,
|
||||
kmInclusive: 15,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:30-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 8800,
|
||||
timeRange: '18:30-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 8800,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 8800,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.A',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 8800,
|
||||
infectionType: '2.A',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.B',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 17500,
|
||||
infectionType: '2.B',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.1',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 17500,
|
||||
infectionType: '3.1',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.2',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 17500,
|
||||
infectionType: '3.2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -5,40 +5,6 @@ export const KTW_VDEK_TARIFFS = [
|
|||
{
|
||||
description: 'VDEK KTW',
|
||||
validFrom: startOfDay(new Date('2017-09-01')),
|
||||
validTo: endOfDay(new Date('2021-04-22')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 8150,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 18. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 273,
|
||||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4550,
|
||||
timeRange: '18:30-06:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 4550,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 4550,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'VDEK KTW',
|
||||
validFrom: startOfDay(new Date('2021-04-23')),
|
||||
validTo: endOfDay(new Date('2021-06-30')),
|
||||
costTypes: [
|
||||
{
|
||||
|
|
@ -58,7 +24,7 @@ export const KTW_VDEK_TARIFFS = [
|
|||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 4550,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -97,7 +63,7 @@ export const KTW_VDEK_TARIFFS = [
|
|||
kmInclusive: 17,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5028,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -146,7 +112,7 @@ export const KTW_VDEK_TARIFFS = [
|
|||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:30-06:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5537,
|
||||
timeRange: '18:30-06:30',
|
||||
|
|
@ -190,7 +156,7 @@ export const KTW_VDEK_TARIFFS = [
|
|||
{
|
||||
description: 'VDEK KTW',
|
||||
validFrom: startOfDay(new Date('2023-05-01')),
|
||||
validTo: endOfDay(new Date('2024-04-31')),
|
||||
validTo: endOfDay(new Date('2024-04-30')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -219,25 +185,25 @@ export const KTW_VDEK_TARIFFS = [
|
|||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5728,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6644,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7198,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5728,
|
||||
timeRange: '05:00-06:00',
|
||||
|
|
@ -281,7 +247,7 @@ export const KTW_VDEK_TARIFFS = [
|
|||
{
|
||||
description: 'VDEK KTW',
|
||||
validFrom: startOfDay(new Date('2024-05-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2025-04-30')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -310,25 +276,25 @@ export const KTW_VDEK_TARIFFS = [
|
|||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5970,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6924,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7502,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5970,
|
||||
timeRange: '05:00-06:00',
|
||||
|
|
@ -369,4 +335,95 @@ export const KTW_VDEK_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'VDEK KTW',
|
||||
validFrom: startOfDay(new Date('2025-05-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 7971,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 3141,
|
||||
},
|
||||
{
|
||||
description: 'Modernes Einsatzfahrzeug',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 1885,
|
||||
},
|
||||
{
|
||||
description: 'Leitstellenmanagement',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 730,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 17. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 402,
|
||||
kmInclusive: 16,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 19:00-19:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6233,
|
||||
timeRange: '19:00-19:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 20:00-21:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7229,
|
||||
timeRange: '20:00-21:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 22:00-04:59',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7833,
|
||||
timeRange: '22:00-04:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 05:00-06:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6233,
|
||||
timeRange: '05:00-06:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 6992,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 6992,
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.A',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 4654,
|
||||
infectionType: '2.A',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 2.B',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 19070,
|
||||
infectionType: '2.B',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.1',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 22052,
|
||||
infectionType: '3.1',
|
||||
},
|
||||
{
|
||||
description: 'Desinfektionszuschlag 3.2',
|
||||
variant: CostTypeVariant.DESINFEKTIONSZUSCHLAG,
|
||||
costAmount: 22052,
|
||||
infectionType: '3.2',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const TSW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 2445,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
@ -53,7 +53,7 @@ export const TSW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag s19:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 2445,
|
||||
timeRange: '19:00-07:00',
|
||||
|
|
@ -87,7 +87,7 @@ export const TSW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 2600,
|
||||
timeRange: '18:00-07:30',
|
||||
|
|
@ -107,7 +107,7 @@ export const TSW_AOK_TARIFFS = [
|
|||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft TSW',
|
||||
validFrom: startOfDay(new Date('2023-05-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2024-06-30')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -121,7 +121,7 @@ export const TSW_AOK_TARIFFS = [
|
|||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 2700,
|
||||
timeRange: '18:00-07:30',
|
||||
|
|
@ -138,4 +138,138 @@ export const TSW_AOK_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft TSW',
|
||||
validFrom: startOfDay(new Date('2024-07-01')),
|
||||
validTo: endOfDay(new Date('2024-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 6000,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 200,
|
||||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachmittagszuschlag 14:30-18:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7000,
|
||||
timeRange: '14:30-18:00',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5000,
|
||||
timeRange: '18:00-07:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 5000,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 5000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft TSW',
|
||||
validFrom: startOfDay(new Date('2025-01-01')),
|
||||
validTo: endOfDay(new Date('2025-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6250,
|
||||
timeRange: '00:00-14:29',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6250,
|
||||
timeRange: '18:01-23:59',
|
||||
},
|
||||
{
|
||||
description: 'Nachmittagspauschale 14:30-18:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7500,
|
||||
timeRange: '14:30-18:00',
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 200,
|
||||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6000,
|
||||
timeRange: '18:00-07:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 6000,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 6000,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'AOK/BKK/IKK/Knappschaft TSW',
|
||||
validFrom: startOfDay(new Date('2026-01-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6500,
|
||||
timeRange: '00:00-14:29',
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6500,
|
||||
timeRange: '18:01-23:59',
|
||||
},
|
||||
{
|
||||
description: 'ab dem 21. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 200,
|
||||
kmInclusive: 20,
|
||||
},
|
||||
{
|
||||
description: 'Nachmittagszuschlag 14:30-18:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 7500,
|
||||
timeRange: '14:30-18:00',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag 18:00-07:30',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 6000,
|
||||
timeRange: '18:00-07:30',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 6000,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 6000,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -26,33 +26,33 @@ export const TSW_DAK_VDEK_TARIFFS = [
|
|||
validTo: endOfDay(new Date('2024-08-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt (TSW 2)',
|
||||
variant: CostTypeVariant.TSWDIFFERENZIERUNG,
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 5880,
|
||||
},
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt (TSW 1)',
|
||||
variant: CostTypeVariant.TSWDIFFERENZIERUNG,
|
||||
costAmount: 4380,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 407,
|
||||
timeRange: '18:30-20:00',
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 407,
|
||||
timeRange: '06:00-07:00',
|
||||
},
|
||||
// {
|
||||
// description: 'Grundpauschale je Fahrt (TSW 1)',
|
||||
// variant: CostTypeVariant.TSWDIFFERENZIERUNG,
|
||||
// costAmount: 4380,
|
||||
// },
|
||||
{
|
||||
description: 'ab dem 22. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 159,
|
||||
kmInclusive: 21,
|
||||
},
|
||||
{
|
||||
description: 'Morgenzuschlag 06:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 407,
|
||||
timeRange: '06:00-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Abendzuschlag 18:30-20:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 407,
|
||||
timeRange: '18:30-20:00',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -76,21 +76,26 @@ export const TSW_DAK_VDEK_TARIFFS = [
|
|||
costAmount: 5793,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
description: 'Fallmanagementzuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 750,
|
||||
},
|
||||
{
|
||||
description: 'Morgenzuschlag 05:00-07:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 407,
|
||||
costAmount: 450,
|
||||
timeRange: '05:00-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Fallmanagementzuschlag',
|
||||
description: 'Abendzuschlag 19:00-23:00',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 750,
|
||||
timeRange: '14:30-23:00',
|
||||
costAmount: 407,
|
||||
timeRange: '19:00-23:00',
|
||||
},
|
||||
{
|
||||
description: 'ab dem 22. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 180,
|
||||
costAmount: 159,
|
||||
kmInclusive: 21,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { CostTypeVariant } from '@prisma/client';
|
||||
import { startOfDay } from 'date-fns';
|
||||
import { endOfDay, startOfDay } from 'date-fns';
|
||||
|
||||
export const TSW_PRIVAT_TARIFFS = [
|
||||
{
|
||||
description: 'Privat TSW',
|
||||
validFrom: startOfDay(new Date('2020-01-01')),
|
||||
validTo: null,
|
||||
validTo: endOfDay(new Date('2023-12-31')),
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
|
|
@ -41,4 +41,43 @@ export const TSW_PRIVAT_TARIFFS = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
description: 'Privat TSW',
|
||||
validFrom: startOfDay(new Date('2024-01-01')),
|
||||
validTo: null,
|
||||
costTypes: [
|
||||
{
|
||||
description: 'Grundpauschale je Fahrt',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 13500,
|
||||
},
|
||||
{
|
||||
description: 'QMS Zuschlag',
|
||||
variant: CostTypeVariant.GRUNDPREIS,
|
||||
costAmount: 2000,
|
||||
},
|
||||
{
|
||||
description: 'ab dem 16. km',
|
||||
variant: CostTypeVariant.KILOMETERZUSCHLAG,
|
||||
costAmount: 250,
|
||||
kmInclusive: 15,
|
||||
},
|
||||
{
|
||||
description: 'Nachtzuschlag',
|
||||
variant: CostTypeVariant.NACHTZUSCHLAG,
|
||||
costAmount: 5500,
|
||||
timeRange: '19:00-07:00',
|
||||
},
|
||||
{
|
||||
description: 'Wochenendzuschlag',
|
||||
variant: CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
costAmount: 5500,
|
||||
},
|
||||
{
|
||||
description: 'Feiertagszuschlag',
|
||||
variant: CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
costAmount: 5500,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -13,18 +13,18 @@ import { DatabaseModule } from './core/database/database.module';
|
|||
import { PrismaModule } from './core/database/prisma.module';
|
||||
import { ApplicationAccountingModule } from './modules/app-accounting/app-accounting.module';
|
||||
import { ApplicationControlCenterModule } from './modules/app-control-center/app-control-center.module';
|
||||
import { ApplicationManagerModule } from './modules/app-manager/app-manager.module';
|
||||
import { ManagerModule } from './modules/app-manager/app-manager.module';
|
||||
import { ApplicationTicketSystemModule } from './modules/app-ticket-system/app-ticket-system.module';
|
||||
import { AnomalyDetectionModule } from './modules/feat-anomalies/anomaly-detection.module';
|
||||
import { FeatureEtlModule } from './modules/feat-etl/feat-etl.module';
|
||||
import { FeatReportsModule } from './modules/feat-reports/feat-reports.module';
|
||||
import { TicketSystemModule } from './modules/feat-tickets/ticket-system.module';
|
||||
import { FeatureCalculationModule } from './modules/feat-calculation/feat-calculation.module';
|
||||
import { FeatCalculationModule } from './modules/feat-calculation/feat-calculation.module';
|
||||
import { AppHomeModule } from './modules/app-home/app-home.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule,
|
||||
|
||||
// DatabaseModule.forRootAsync({
|
||||
// isGlobal: true,
|
||||
// inject: [ConfigService],
|
||||
|
|
@ -81,13 +81,14 @@ import { FeatureCalculationModule } from './modules/feat-calculation/feat-calcul
|
|||
}),
|
||||
FeatureEtlModule,
|
||||
FeatReportsModule,
|
||||
FeatureCalculationModule,
|
||||
FeatCalculationModule,
|
||||
TicketSystemModule,
|
||||
AnomalyDetectionModule,
|
||||
ApplicationManagerModule,
|
||||
ManagerModule,
|
||||
ApplicationAccountingModule,
|
||||
ApplicationControlCenterModule,
|
||||
ApplicationTicketSystemModule,
|
||||
AppHomeModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { Cron, SchedulerRegistry } from '@nestjs/schedule';
|
||||
import {
|
||||
Interval as TimeInterval,
|
||||
addDays,
|
||||
|
|
@ -13,12 +13,20 @@ import { concatMap, from, lastValueFrom, switchMap, take, tap } from 'rxjs';
|
|||
import { ImporterService } from './modules/feat-etl/feat-importers/importer.service';
|
||||
import { TicketService } from './modules/feat-tickets/tickets.service';
|
||||
import { chunk } from './utils';
|
||||
import { AnomaliesService } from './modules/feat-anomalies/anomalies.service';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
private readonly logger = new Logger(AppService.name);
|
||||
|
||||
// @Cron('0 */5 2-20 * * *')
|
||||
constructor(
|
||||
private readonly importer: ImporterService,
|
||||
private readonly ticketService: TicketService,
|
||||
private readonly anomalieService: AnomaliesService,
|
||||
private readonly schedulerRegistry: SchedulerRegistry,
|
||||
) {}
|
||||
|
||||
@Cron('0 */5 2-20 * * *')
|
||||
async fetchDataContinously() {
|
||||
const now = new Date();
|
||||
const fetchInterval = {
|
||||
|
|
@ -35,6 +43,13 @@ export class AppService {
|
|||
),
|
||||
),
|
||||
switchMap(() => from(this.ticketService.createTickets(fetchInterval))),
|
||||
switchMap(() =>
|
||||
from(
|
||||
this.anomalieService
|
||||
.detectAnomalies(fetchInterval)
|
||||
.then(() => this.anomalieService.validateAnomalies()),
|
||||
),
|
||||
),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
|
@ -142,12 +157,6 @@ export class AppService {
|
|||
.subscribe();
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly importer: ImporterService,
|
||||
private readonly ticketService: TicketService,
|
||||
private readonly schedulerRegistry: SchedulerRegistry,
|
||||
) {}
|
||||
|
||||
private splitIntervalIntoPeriods(
|
||||
interval: TimeInterval,
|
||||
chunkSize = 30,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,18 @@ type DocumentInfo {
|
|||
tdLocation: String
|
||||
}
|
||||
|
||||
type HomeDashboardKPIs {
|
||||
anomalyCount: Int!
|
||||
billableTourCount: Int!
|
||||
billableTourRevenueSum: Float!
|
||||
billedTourCount: Int!
|
||||
billedTourNegativeRevenueDeviation: Float!
|
||||
billedTourPositiveRevenueDeviation: Float!
|
||||
employeesWithoutTourCount: Int!
|
||||
revenueTrend: [RevenueTrend!]!
|
||||
toursWithoutApprovalCount: Int!
|
||||
}
|
||||
|
||||
type Interval {
|
||||
end: DateTime!
|
||||
start: DateTime!
|
||||
|
|
@ -88,6 +100,17 @@ type KPIMetadata {
|
|||
tourAmount: Float!
|
||||
}
|
||||
|
||||
type KpiInfo {
|
||||
additional: [Metric!]!
|
||||
main: Metric!
|
||||
secondary: Metric!
|
||||
}
|
||||
|
||||
type Metric {
|
||||
label: String!
|
||||
value: Float!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
acceptAnomaly(id: String!): Anomaly!
|
||||
setAnomalyToSleep(id: String!): Anomaly!
|
||||
|
|
@ -133,14 +156,31 @@ type Query {
|
|||
anomaliesProto: [Anomaly!]!
|
||||
anomaly(id: String!): Anomaly!
|
||||
groupedAnomalies(cursor: String, take: Int! = 10): PaginatedAnomalyGroup!
|
||||
homeDashboardKpis: HomeDashboardKPIs!
|
||||
managerKpi(ordinanceType: String!): KpiInfo!
|
||||
searchTickets(query: String!): [Ticket!]!
|
||||
ticket(id: String!): Ticket!
|
||||
ticketStateKeyFigures(state: TicketValidationState!): TicketStateKeyFigures!
|
||||
ticketStateKpi(state: TicketValidationState!): KpiInfo!
|
||||
ticketStateMeta(state: TicketValidationState!): TicketStateMeta!
|
||||
tickets(cursor: String, filters: TicketFilterArgs, state: TicketValidationState!, take: Int! = 10): PaginatedTicket!
|
||||
timePerTourMetricForDay(day: DateTime!, ordinanceType: String!): [TimePerTourMetric!]!
|
||||
timePerTourMetricForMonth(day: DateTime!, ordinanceType: String!): [TimePerTourMetric!]!
|
||||
tourCostBreakdown(operationId: String!): TourCostBreakdown!
|
||||
toursPerTimeKPI(ordinanceTypes: String!): [TimePerTourKPISummary!]!
|
||||
upcomingTours: [Tour!]!
|
||||
}
|
||||
|
||||
type RevenueTrend {
|
||||
date: DateTime!
|
||||
ordinanceType: String!
|
||||
revenueSum: Float!
|
||||
}
|
||||
|
||||
type ServicePosition {
|
||||
description: String!
|
||||
value: Float!
|
||||
variant: String!
|
||||
}
|
||||
|
||||
type SolutionTarget {
|
||||
|
|
@ -287,7 +327,15 @@ type Tour {
|
|||
type: String!
|
||||
}
|
||||
|
||||
type TourCostBreakdown {
|
||||
servicePositions: [ServicePosition!]!
|
||||
tariffDescription: String!
|
||||
tariffValidityPeriod: String!
|
||||
}
|
||||
|
||||
type TourStateMeta {
|
||||
count: Int!
|
||||
negativeRevenueDeviation: Float
|
||||
positiveRevenueDeviation: Float
|
||||
revenueSum: Float
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
// const tracer = require('./tracer');
|
||||
import tracer from './tracer'
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
await tracer.start();
|
||||
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: new ConsoleLogger({ json: true }),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -34,4 +34,8 @@ export class TourStateMetaObjectType {
|
|||
count!: number;
|
||||
@Field(() => Float, { nullable: true })
|
||||
revenueSum?: number;
|
||||
@Field(() => Float, { nullable: true })
|
||||
positiveRevenueDeviation?: number;
|
||||
@Field(() => Float, { nullable: true })
|
||||
negativeRevenueDeviation?: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,7 +170,12 @@ export class AccountingValidationRepository {
|
|||
],
|
||||
};
|
||||
|
||||
const [count, revenueSum] = await this.prisma.$transaction([
|
||||
const [
|
||||
count,
|
||||
revenueSum,
|
||||
positiveRevenueDeviation,
|
||||
negativeRevenueDeviation,
|
||||
] = await this.prisma.$transaction([
|
||||
this.prisma.tour.count({
|
||||
where: { ...ticketWithCurrentStateAndTours },
|
||||
}),
|
||||
|
|
@ -182,11 +187,37 @@ export class AccountingValidationRepository {
|
|||
...ticketWithCurrentStateAndTours,
|
||||
},
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenueDeviation: true,
|
||||
},
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours,
|
||||
revenueDeviation: {
|
||||
gt: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenueDeviation: true,
|
||||
},
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours,
|
||||
revenueDeviation: {
|
||||
lt: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
count,
|
||||
revenueSum: revenueSum._sum.revenue ?? 0,
|
||||
positiveRevenueDeviation:
|
||||
positiveRevenueDeviation._sum.revenueDeviation ?? 0,
|
||||
negativeRevenueDeviation:
|
||||
negativeRevenueDeviation._sum.revenueDeviation ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from './accounting-arg.types';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { AccountingValidationService } from './accounting-validation.service';
|
||||
import { PaginatedTourObjectType } from './accounting.object-type';
|
||||
import { PaginatedTourObjectType, TourCostBreakdownObjectType } from './accounting.object-type';
|
||||
import { TourObjectType } from './tour.object-type';
|
||||
|
||||
@Resolver(() => TourObjectType)
|
||||
|
|
@ -28,6 +28,11 @@ export class AccountingValidationResolver {
|
|||
return this.service.getAccountingStateMeta(accountingState);
|
||||
}
|
||||
|
||||
@Query(() => TourCostBreakdownObjectType)
|
||||
tourCostBreakdown(@Args('operationId') operationId: string) {
|
||||
return this.service.getTourCostBreakdown(operationId)
|
||||
}
|
||||
|
||||
@ResolveField(() => TicketObjectType, { nullable: true })
|
||||
ticket(@Parent() tour: TourObjectType) {
|
||||
return this.service.findTicketByTicketId(tour.ticketId);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
decodeOffsetCursor,
|
||||
encodeCursor,
|
||||
} from 'src/core/base/pagination.util';
|
||||
import { TourStateMetaObjectType } from './accounting-arg.types';
|
||||
import { AccountingFilter } from './accounting-filter';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { AccountingValidationRepository } from './accounting-validation.repository';
|
||||
import { PaginatedTourObjectType } from './accounting.object-type';
|
||||
import {
|
||||
decodeOffsetCursor,
|
||||
encodeCursor,
|
||||
} from 'src/core/base/pagination.util';
|
||||
import { AccountingService } from './accounting.service';
|
||||
|
||||
@Injectable()
|
||||
export class AccountingValidationService {
|
||||
constructor(private readonly repository: AccountingValidationRepository) {}
|
||||
constructor(
|
||||
private readonly repository: AccountingValidationRepository,
|
||||
private readonly accounting: AccountingService,
|
||||
) {}
|
||||
|
||||
async paginateToursByState(
|
||||
state: AccountingState,
|
||||
|
|
@ -49,4 +53,8 @@ export class AccountingValidationService {
|
|||
if (!ticketId) return;
|
||||
return this.repository.findTicketByTicketId(ticketId);
|
||||
}
|
||||
|
||||
async getTourCostBreakdown(operationId: string) {
|
||||
return this.accounting.tourCostBreakdown(operationId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,33 @@
|
|||
import { ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import { Field, Float, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { Paginated } from 'src/core/base/pagination.input';
|
||||
import { TourObjectType } from './tour.object-type';
|
||||
|
||||
@ObjectType('PaginatedTour')
|
||||
export class PaginatedTourObjectType extends Paginated(TourObjectType) {}
|
||||
import { CostTypeVariant } from '@prisma/client';
|
||||
|
||||
registerEnumType(AccountingState, {
|
||||
name: 'AccountingState',
|
||||
});
|
||||
|
||||
@ObjectType('PaginatedTour')
|
||||
export class PaginatedTourObjectType extends Paginated(TourObjectType) {}
|
||||
|
||||
@ObjectType('ServicePosition')
|
||||
export class ServicePositionObjectType {
|
||||
@Field()
|
||||
variant: CostTypeVariant;
|
||||
@Field()
|
||||
description: string;
|
||||
@Field(() => Float)
|
||||
value: number;
|
||||
}
|
||||
|
||||
@ObjectType('TourCostBreakdown')
|
||||
export class TourCostBreakdownObjectType {
|
||||
@Field()
|
||||
tariffDescription: string;
|
||||
@Field()
|
||||
tariffValidityPeriod: string;
|
||||
|
||||
@Field(() => [ServicePositionObjectType])
|
||||
servicePositions: ServicePositionObjectType[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { format } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { CalculationService } from '../feat-calculation/calculation.service';
|
||||
import { TourCostBreakdownObjectType } from './accounting.object-type';
|
||||
|
||||
@Injectable()
|
||||
export class AccountingService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly calculation: CalculationService,
|
||||
) {}
|
||||
|
||||
async tourCostBreakdown(
|
||||
operationId: string,
|
||||
): Promise<TourCostBreakdownObjectType> {
|
||||
const tour = await this.prisma.tour.findFirst({
|
||||
where: { operationId },
|
||||
});
|
||||
|
||||
const { tariff, servicePositions } =
|
||||
await this.calculation.listProvidedServicePositions(tour);
|
||||
|
||||
if (!tariff || servicePositions.length === 0) {
|
||||
return {
|
||||
tariffDescription: '!-- Tarif kann nicht ermittelt werden --!',
|
||||
tariffValidityPeriod: '--',
|
||||
servicePositions: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
tariffDescription: tariff.tariffGroup?.description,
|
||||
tariffValidityPeriod: this.formatTariffValidityPeriod(
|
||||
tariff.validFrom,
|
||||
tariff.validTo,
|
||||
),
|
||||
servicePositions: servicePositions.map(([costType, value]) => ({
|
||||
value,
|
||||
variant: costType.variant,
|
||||
description: costType.description,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
private formatTariffValidityPeriod(from: Date, to?: Date): string {
|
||||
if (!to) return `ab ${format(new Date(from), 'yyyy-MM-dd')}`;
|
||||
return `${format(new Date(from), 'yyyy-MM-dd')} - ${format(new Date(to), 'yyyy-MM-dd')}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { FeatCalculationModule } from '../feat-calculation/feat-calculation.module';
|
||||
import { AccountingValidationRepository } from './accounting-validation.repository';
|
||||
import { AccountingValidationResolver } from './accounting-validation.resolver';
|
||||
import { AccountingValidationService } from './accounting-validation.service';
|
||||
import { AccountingService } from './accounting.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
imports: [PrismaModule, FeatCalculationModule],
|
||||
providers: [
|
||||
AccountingValidationRepository,
|
||||
AccountingValidationResolver,
|
||||
AccountingValidationService,
|
||||
AccountingService,
|
||||
],
|
||||
exports: [],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||
import { ControlCenterResolver } from './control-center.resolver';
|
||||
import { SolutionTargetsResolver } from './solution-targets.resolver';
|
||||
import { AnomalyDetectionModule } from '../feat-anomalies/anomaly-detection.module';
|
||||
import { FeatKpiModule } from '../feat-kpis/feat-kpis.module';
|
||||
|
||||
@Module({
|
||||
imports: [AnomalyDetectionModule],
|
||||
imports: [AnomalyDetectionModule, FeatKpiModule],
|
||||
providers: [ControlCenterResolver, SolutionTargetsResolver],
|
||||
})
|
||||
export class ApplicationControlCenterModule {}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,16 @@ import {
|
|||
PaginatedAnomalyObjectType,
|
||||
SolutionTargetObjectType,
|
||||
} from './anomaly.object-type';
|
||||
import { KpiService } from '../feat-kpis/kpi.service';
|
||||
|
||||
@Resolver(() => AnomalyObjectType)
|
||||
export class ControlCenterResolver {
|
||||
constructor(private readonly anomaliesService: AnomaliesService) {}
|
||||
constructor(private readonly anomaliesService: AnomaliesService, private readonly kpiService: KpiService) {}
|
||||
|
||||
@Query(() => [TourObjectType])
|
||||
async upcomingTours() {
|
||||
return this.kpiService.upcomingTours();
|
||||
}
|
||||
|
||||
@Query(() => AnomalyObjectType)
|
||||
async anomaly(@Args('id') id: string) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { HomeResolver } from './home.resolver';
|
||||
import { HomeService } from './home.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [HomeResolver, HomeService],
|
||||
})
|
||||
export class AppHomeModule {}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { Field, Float, Int, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('RevenueTrend')
|
||||
export class RevenueTrendObjectType {
|
||||
@Field(() => Date)
|
||||
date: Date;
|
||||
@Field()
|
||||
ordinanceType: string;
|
||||
@Field(() => Float)
|
||||
revenueSum: number;
|
||||
}
|
||||
|
||||
@ObjectType('HomeDashboardKPIs')
|
||||
export class HomeDashboardKPIsObjectType {
|
||||
@Field(() => Int)
|
||||
anomalyCount: number;
|
||||
@Field(() => Int)
|
||||
employeesWithoutTourCount: number;
|
||||
@Field(() => Int)
|
||||
toursWithoutApprovalCount: number;
|
||||
@Field(() => Int)
|
||||
billableTourCount: number;
|
||||
@Field(() => Float)
|
||||
billableTourRevenueSum: number;
|
||||
@Field(() => Int)
|
||||
billedTourCount: number;
|
||||
@Field(() => Float)
|
||||
billedTourPositiveRevenueDeviation: number;
|
||||
@Field(() => Float)
|
||||
billedTourNegativeRevenueDeviation: number;
|
||||
@Field(() => [RevenueTrendObjectType])
|
||||
revenueTrend: RevenueTrendObjectType[];
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { Query, Resolver } from '@nestjs/graphql';
|
||||
import { HomeDashboardKPIsObjectType } from './home.object-type';
|
||||
import { HomeService } from './home.service';
|
||||
|
||||
@Resolver()
|
||||
export class HomeResolver {
|
||||
constructor(private readonly service: HomeService) {}
|
||||
|
||||
@Query(() => HomeDashboardKPIsObjectType)
|
||||
homeDashboardKpis() {
|
||||
return this.service.homeDashboardKpis();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,444 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ApprovalState, Prisma, Tour } from '@prisma/client';
|
||||
import { addDays, endOfDay, format, startOfDay, subDays } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { AnomalyTypes } from '../feat-anomalies/anomaly-detectors/anomaly-types.enum';
|
||||
import {
|
||||
HomeDashboardKPIsObjectType,
|
||||
RevenueTrendObjectType,
|
||||
} from './home.object-type';
|
||||
|
||||
@Injectable()
|
||||
export class HomeService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async homeDashboardKpis(): Promise<HomeDashboardKPIsObjectType> {
|
||||
const anomalyCount = await this.getAnomalyCount();
|
||||
const employeesWithoutTourCount = await this.getEmployeesWithoutTourCount();
|
||||
const toursWithoutApprovalCount = await this.getToursWithoutApprovalCount();
|
||||
const billableTourCount = await this.getBillableTourCount();
|
||||
const billableTourRevenueSum = await this.getBillableTourRevenueSum();
|
||||
const {
|
||||
tourCount: billedTourCount,
|
||||
positiveRevenueDeviation: billedTourPositiveRevenueDeviation,
|
||||
negativeRevenueDeviation: billedTourNegativeRevenueDeviation,
|
||||
} = await this.getBilledTourKPIs();
|
||||
const revenueTrend = await this.getRevenueTrend();
|
||||
|
||||
return {
|
||||
anomalyCount,
|
||||
employeesWithoutTourCount,
|
||||
toursWithoutApprovalCount,
|
||||
billableTourCount,
|
||||
billableTourRevenueSum,
|
||||
billedTourCount,
|
||||
billedTourPositiveRevenueDeviation,
|
||||
billedTourNegativeRevenueDeviation,
|
||||
revenueTrend,
|
||||
};
|
||||
}
|
||||
|
||||
private async getAnomalyCount(): Promise<number> {
|
||||
return await this.prisma.anomaly.count({
|
||||
where: {
|
||||
resolvedAt: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getEmployeesWithoutTourCount(): Promise<number> {
|
||||
return await this.prisma.anomaly.count({
|
||||
where: {
|
||||
type: AnomalyTypes.WorkingEmployeeNotInDistributionSystem,
|
||||
resolvedAt: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getToursWithoutApprovalCount(): Promise<number> {
|
||||
return await this.prisma.tour.count({
|
||||
where: {
|
||||
startDate: {
|
||||
gte: startOfDay(addDays(new Date(), 3)),
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
Ticket: {
|
||||
approvalState: ApprovalState.UNKNOWN,
|
||||
},
|
||||
},
|
||||
{
|
||||
Ticket: {
|
||||
approvalState: ApprovalState.REQUIRED,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getBillableTourCount(): Promise<number> {
|
||||
return await this.prisma.tour.count({
|
||||
where: {
|
||||
done: true,
|
||||
check: 0,
|
||||
empty: { not: null },
|
||||
ticketId: { not: null },
|
||||
occupiedKm: {
|
||||
lt: 9000,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ patientId: null },
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async getBillableTourRevenueSum(): Promise<number> {
|
||||
const aggr = await this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenue: true,
|
||||
},
|
||||
where: {
|
||||
done: true,
|
||||
check: 0,
|
||||
empty: { not: null },
|
||||
ticketId: { not: null },
|
||||
occupiedKm: {
|
||||
lt: 9000,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ patientId: null },
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return aggr._sum.revenue;
|
||||
}
|
||||
|
||||
private async getBilledTourKPIs(): Promise<{
|
||||
tourCount: number;
|
||||
positiveRevenueDeviation: number;
|
||||
negativeRevenueDeviation: number;
|
||||
}> {
|
||||
const toursWithCurrentState: Prisma.TourWhereInput = {
|
||||
check: 2,
|
||||
revenueDeviation: { not: 0 },
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
occupiedKm: {
|
||||
lt: 9000,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{ patientId: null },
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const [tourCount, positiveRevenueDeviation, negativeRevenueDeviation] =
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.tour.count({
|
||||
where: { ...toursWithCurrentState },
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenueDeviation: true,
|
||||
},
|
||||
where: {
|
||||
...toursWithCurrentState,
|
||||
revenueDeviation: {
|
||||
gt: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenueDeviation: true,
|
||||
},
|
||||
where: {
|
||||
...toursWithCurrentState,
|
||||
revenueDeviation: {
|
||||
lt: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
tourCount,
|
||||
positiveRevenueDeviation:
|
||||
positiveRevenueDeviation._sum.revenueDeviation ?? 0,
|
||||
negativeRevenueDeviation:
|
||||
negativeRevenueDeviation._sum.revenueDeviation ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
private async getRevenueTrend(): Promise<RevenueTrendObjectType[]> {
|
||||
const tours = await this.getTours();
|
||||
|
||||
const toursPerDay = tours.reduce(
|
||||
(acc, tour) => {
|
||||
const tourDay = format(new Date(tour.startDate), 'yyyy-MM-dd');
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[tourDay]: [...(acc[tourDay]?.length ? acc[tourDay] : []), tour],
|
||||
};
|
||||
},
|
||||
{} as Record<string, Tour[]>,
|
||||
) as Record<string, Tour[]>;
|
||||
|
||||
const revenueTrend = Object.entries(toursPerDay).flatMap(([day, tours]) => {
|
||||
const toursByOrdinanceType = tours.reduce(
|
||||
(acc, tour) => {
|
||||
return {
|
||||
...acc,
|
||||
[tour.ordinanceType]: [
|
||||
...(acc[tour.ordinanceType] ? acc[tour.ordinanceType] : []),
|
||||
tour,
|
||||
],
|
||||
};
|
||||
},
|
||||
{} as Record<string, Tour[]>,
|
||||
);
|
||||
return Object.entries(toursByOrdinanceType).map(([ot, tours]) => {
|
||||
const revenueSum = tours.reduce(
|
||||
(sum, { revenue }) => (sum += revenue),
|
||||
0,
|
||||
);
|
||||
return {
|
||||
revenueSum,
|
||||
date: new Date(day),
|
||||
ordinanceType: ot,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return revenueTrend;
|
||||
}
|
||||
|
||||
private getTours(): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: startOfDay(subDays(new Date(), 10)),
|
||||
lte: endOfDay(new Date()),
|
||||
},
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ordinanceType: {not: null}},
|
||||
{ordinanceType: {not: 'Tragehilfe'}},
|
||||
]
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
occupiedKm: {
|
||||
lt: 9999,
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,19 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { FeatureCalculationModule } from '../feat-calculation/feat-calculation.module';
|
||||
import { TourAmountForecastService } from './tour-amount-forecast.service';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
import { FeatCalculationModule } from '../feat-calculation/feat-calculation.module';
|
||||
import { ManagerResolver } from './manager.resolver';
|
||||
import { TimePerTourKpiService } from './time-per-tour-kpi.service';
|
||||
import { TimePerTourMetricsService } from './time-per-tour-metrics.service';
|
||||
import { ManagerResolver } from './manager.resolver';
|
||||
import { CarEntityService } from './car-entity/car-entity.service';
|
||||
import { CarEntityRepository } from './car-entity/car-entity.repository';
|
||||
import { TourAmountForecastService } from './tour-amount-forecast.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, FeatureCalculationModule],
|
||||
imports: [PrismaModule, FeatCalculationModule, FeatBusinessObjectsModule],
|
||||
providers: [
|
||||
TourAmountForecastService,
|
||||
TimePerTourKpiService,
|
||||
TimePerTourMetricsService,
|
||||
CarEntityService,
|
||||
CarEntityRepository,
|
||||
ManagerResolver,
|
||||
],
|
||||
})
|
||||
export class ApplicationManagerModule {}
|
||||
export class ManagerModule {}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@ import { OnEvent } from '@nestjs/event-emitter';
|
|||
import { addMilliseconds } from 'date-fns';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
import { CarEntityService } from './car-entity/car-entity.service';
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ import {
|
|||
startOfDay,
|
||||
startOfMonth,
|
||||
} from 'date-fns';
|
||||
import { CarEntityService } from './car-entity/car-entity.service';
|
||||
import { TourAmountForecastService } from './tour-amount-forecast.service';
|
||||
import { TimePerTourMetricObjectType } from './manager.object-type';
|
||||
import { CalculationService } from '../feat-calculation/calculation.service';
|
||||
import { generateIntervalForTimeRange, groupBy } from 'src/utils';
|
||||
import { CarEntityService } from '../feat-business-objects/car-entity/car-entity.service';
|
||||
|
||||
@Injectable()
|
||||
export class TimePerTourMetricsService {
|
||||
|
|
|
|||
|
|
@ -112,12 +112,28 @@ export class TicketLifecycleService {
|
|||
: ApprovalState.REQUIRED,
|
||||
});
|
||||
} else {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
// NOTE: Condition required to get these tickets out of USER_INTERACTION_REQUIRED
|
||||
if (
|
||||
ticket.tours.every(
|
||||
({ ordinanceType }) =>
|
||||
ordinanceType.indexOf('BTW') > -1 ||
|
||||
ordinanceType.indexOf('TSW') > -1,
|
||||
)
|
||||
) {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.TD_UNCERTAIN,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
} else {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await this.repository.setActionablesOnTicket(ticket.id, errors, notes);
|
||||
|
|
@ -186,6 +202,9 @@ export class TicketLifecycleService {
|
|||
const approvalRequirements = await Promise.all(
|
||||
ticket.tours.map((tour) => this.checkTourApprovalRequirement(tour)),
|
||||
);
|
||||
if(ticket.tours.some(({ordinanceType}) => ordinanceType.indexOf('TSW') > -1)) {
|
||||
console.log(approvalRequirements)
|
||||
}
|
||||
const isApprovalRequired = approvalRequirements.reduce(
|
||||
(result, requirement) => result || requirement,
|
||||
false,
|
||||
|
|
@ -204,6 +223,14 @@ export class TicketLifecycleService {
|
|||
}
|
||||
|
||||
private async checkTourApprovalRequirement(tour: Tour): Promise<boolean> {
|
||||
// NOTE: Requirement by Avicenna
|
||||
if (
|
||||
tour.ordinanceType.indexOf('BTW') > -1 ||
|
||||
tour.ordinanceType.indexOf('TSW') > -1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
'Einweisung',
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@ export function validatePatient(patient: Patient): ValidationResult {
|
|||
isValid = false;
|
||||
errors.push('Patient: Krankenkasse ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.careDegree) {
|
||||
notes.push('Patient: Pflegegrad ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.disabilityMark1) {
|
||||
notes.push('Patient: Merkzeichen 1 ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.disabilityMark2) {
|
||||
notes.push('Patient: Merkzeichen 2 ist nicht gefüllt');
|
||||
}
|
||||
// if (!patient?.careDegree) {
|
||||
// notes.push('Patient: Pflegegrad ist nicht gefüllt');
|
||||
// }
|
||||
// if (!patient?.disabilityMark1) {
|
||||
// notes.push('Patient: Merkzeichen 1 ist nicht gefüllt');
|
||||
// }
|
||||
// if (!patient?.disabilityMark2) {
|
||||
// notes.push('Patient: Merkzeichen 2 ist nicht gefüllt');
|
||||
// }
|
||||
|
||||
return {
|
||||
result: isValid,
|
||||
|
|
|
|||
|
|
@ -200,11 +200,11 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
|||
return false;
|
||||
}
|
||||
if (!employee.personnelNumber) {
|
||||
this.logger.warn(`Employee has no personnelNumber`, {
|
||||
this.logger.warn(`Employee has no personnelNumber ${JSON.stringify({
|
||||
id: employee.id,
|
||||
surname: employee.surname,
|
||||
name: employee.name,
|
||||
});
|
||||
})}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -86,9 +86,16 @@ export class WorkingEmployeeNotInDistributionSystemDetector extends AnomalyDetec
|
|||
const employeeId = employeeIdParts.join('-');
|
||||
const employee = await this.dataService.findEmployeeById(employeeId);
|
||||
|
||||
if (!employee || !employee?.personnelNumber) {
|
||||
this.logger.error(
|
||||
`Cannot verify anomaly ${anomaly.id}. Employee (${employeeId}) does not exist or cannot be synced ${JSON.stringify(employee)}`,
|
||||
);
|
||||
return anomaly;
|
||||
}
|
||||
|
||||
const plannedTime =
|
||||
await this.dataService.findPlannedTimeByPersonnelNumberForDay(
|
||||
`${employee.personnelNumber}`,
|
||||
`${employee?.personnelNumber}`,
|
||||
attendanceRegistration.dateTime,
|
||||
);
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -98,11 +98,15 @@ export class CarEntityRepository {
|
|||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
department: {
|
||||
contains: ordinanceType,
|
||||
},
|
||||
},
|
||||
...(ordinanceType !== 'ALL'
|
||||
? [
|
||||
{
|
||||
department: {
|
||||
contains: ordinanceType,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
department: {
|
||||
not: 'Leitstelle',
|
||||
|
|
@ -113,6 +117,11 @@ export class CarEntityRepository {
|
|||
not: 'Büro',
|
||||
},
|
||||
},
|
||||
{
|
||||
department: {
|
||||
not: 'Fortbildung',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -138,7 +138,7 @@ export class CarEntity {
|
|||
}`;
|
||||
}
|
||||
|
||||
private plannedTimeIntervalInInterval(interval: Interval): Interval {
|
||||
public plannedTimeIntervalInInterval(interval: Interval): Interval {
|
||||
return this.plannedTimes
|
||||
.map(({ startDate, endDate }) => ({
|
||||
start: new Date(startDate),
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { PrismaModule } from "src/core/database/prisma.module";
|
||||
import { CarEntityRepository } from "./car-entity/car-entity.repository";
|
||||
import { CarEntityService } from "./car-entity/car-entity.service";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
exports: [CarEntityService],
|
||||
providers: [CarEntityRepository, CarEntityService]
|
||||
})
|
||||
export class FeatBusinessObjectsModule {}
|
||||
|
|
@ -6,22 +6,21 @@ import {
|
|||
TariffGroup,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import {
|
||||
Interval,
|
||||
addDays,
|
||||
isSameDay,
|
||||
isSaturday,
|
||||
isWeekend,
|
||||
isWithinInterval,
|
||||
} from 'date-fns';
|
||||
import { toZonedTime } from 'date-fns-tz';
|
||||
import { Interval, addDays, isSaturday, isWithinInterval } from 'date-fns';
|
||||
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
import { BILLING_INFORMATION_BY_ORDINANCE_TYPE } from './billing-information-by-ordinance-type';
|
||||
import { INFECTION_TYPES } from './infection-types';
|
||||
import {
|
||||
calculateDesinfektionszuschlag,
|
||||
calculateFahrzeugzuschlag,
|
||||
calculateFeiertagszuschlag,
|
||||
calculateKilometerzuschlag,
|
||||
calculateNachtzuschlag,
|
||||
calculateTSW,
|
||||
calculateWochenendzuschlag,
|
||||
} from './cost-type-calculations';
|
||||
import { PersonnelCostCalculationService } from './personnel-cost-calculation.service';
|
||||
import { PUBLIC_HOLIDAYS_HH } from './public-holidays';
|
||||
|
||||
enum OrdinanceTypes {
|
||||
BTW = 'BTW',
|
||||
|
|
@ -56,10 +55,39 @@ export class CalculationService {
|
|||
);
|
||||
return 0;
|
||||
}
|
||||
const revenue = this.calculateRevenue(tour, tariff) / 100;
|
||||
const revenue =
|
||||
this.calculateProvidedServicePositions(tour, tariff).reduce(
|
||||
(sum, [, value]) => (sum += value),
|
||||
0,
|
||||
) / 100;
|
||||
return revenue;
|
||||
}
|
||||
|
||||
async listProvidedServicePositions(tour: Tour): Promise<{
|
||||
tariff: Tariff & { costTypes: CostType[]; tariffGroup: TariffGroup };
|
||||
servicePositions: [CostType, number][];
|
||||
}> {
|
||||
const { healthInsurance, ordinanceType, startDate } = tour;
|
||||
if (!ordinanceType) {
|
||||
return { tariff: undefined, servicePositions: [] };
|
||||
}
|
||||
const tariff = await this.findTariff(
|
||||
healthInsurance,
|
||||
ordinanceType.match(/([A-Z])\w+/).at(0),
|
||||
new Date(startDate),
|
||||
);
|
||||
if (!tariff) {
|
||||
this.logger.warn(
|
||||
`No tariff found for tour ${tour.operationId} (${healthInsurance}, ${ordinanceType}, ${startDate})`,
|
||||
);
|
||||
return { tariff: undefined, servicePositions: [] };
|
||||
}
|
||||
return {
|
||||
tariff,
|
||||
servicePositions: this.calculateProvidedServicePositions(tour, tariff),
|
||||
};
|
||||
}
|
||||
|
||||
public calculatePersonnelCostInInterval(
|
||||
interval: Interval,
|
||||
employeeAmount: number,
|
||||
|
|
@ -172,50 +200,58 @@ export class CalculationService {
|
|||
return billingInformation[1];
|
||||
}
|
||||
|
||||
private calculateRevenue(
|
||||
private calculateProvidedServicePositions(
|
||||
tour: Tour,
|
||||
tariff: Tariff & { costTypes: CostType[]; tariffGroup: TariffGroup },
|
||||
): number {
|
||||
): [CostType, number][] {
|
||||
const highestZuschlag = this.getHighestZuschlag(tour, tariff);
|
||||
// if TariffGroup is VDEK KTW, sum up costTypes
|
||||
const isVdek = tariff.tariffGroup?.description?.indexOf('VDEK KTW') > -1;
|
||||
|
||||
return tariff.costTypes.reduce((sum, costType) => {
|
||||
switch (costType.variant) {
|
||||
case CostTypeVariant.GRUNDPREIS:
|
||||
// TODO: decide if you need a CostTypeVariant for QMS Zuschlag and exclude the category there
|
||||
if (
|
||||
costType.description.indexOf('QMS') > -1 &&
|
||||
tour.category === 'Privatfahrt Rechnung'
|
||||
) {
|
||||
return sum;
|
||||
}
|
||||
return sum + costType.costAmount;
|
||||
case CostTypeVariant.TSWDIFFERENZIERUNG:
|
||||
return sum + this.calculateTSW(tour, costType);
|
||||
case CostTypeVariant.DESINFEKTIONSZUSCHLAG:
|
||||
return sum + this.calculateDesinfektionszuschlag(tour, costType);
|
||||
case CostTypeVariant.KILOMETERZUSCHLAG:
|
||||
return sum + this.calculateKilometerzuschlag(tour, costType);
|
||||
case CostTypeVariant.FAHRZEUGZUSCHLAG:
|
||||
return sum + this.calculateFahrzeugzuschlag(tour, costType);
|
||||
case CostTypeVariant.NACHTZUSCHLAG:
|
||||
if (highestZuschlag === CostTypeVariant.NACHTZUSCHLAG || isVdek) {
|
||||
return sum + this.calculateNachtzuschlag(tour, costType);
|
||||
}
|
||||
return sum;
|
||||
case CostTypeVariant.WOCHENENDZUSCHLAG:
|
||||
if (highestZuschlag === CostTypeVariant.WOCHENENDZUSCHLAG || isVdek) {
|
||||
return sum + this.calculateWochenendzuschlag(tour, costType);
|
||||
}
|
||||
return sum;
|
||||
case CostTypeVariant.FEIERTAGSZUSCHLAG:
|
||||
if (highestZuschlag === CostTypeVariant.FEIERTAGSZUSCHLAG || isVdek) {
|
||||
return sum + this.calculateFeiertagszuschlag(tour, costType);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}, 0);
|
||||
return tariff.costTypes
|
||||
.map((costType) => {
|
||||
switch (costType.variant) {
|
||||
case CostTypeVariant.GRUNDPREIS:
|
||||
// TODO: decide if you need a CostTypeVariant for QMS Zuschlag and exclude the category there
|
||||
if (
|
||||
costType.description.indexOf('QMS') > -1 &&
|
||||
tour.category === 'Privatfahrt Rechnung'
|
||||
) {
|
||||
return [costType, 0];
|
||||
}
|
||||
return [costType, costType.costAmount];
|
||||
case CostTypeVariant.TSWDIFFERENZIERUNG:
|
||||
return [costType, calculateTSW(tour, costType)];
|
||||
case CostTypeVariant.DESINFEKTIONSZUSCHLAG:
|
||||
return [costType, calculateDesinfektionszuschlag(tour, costType)];
|
||||
case CostTypeVariant.KILOMETERZUSCHLAG:
|
||||
return [costType, calculateKilometerzuschlag(tour, costType)];
|
||||
case CostTypeVariant.FAHRZEUGZUSCHLAG:
|
||||
return [costType, calculateFahrzeugzuschlag(tour, costType)];
|
||||
case CostTypeVariant.NACHTZUSCHLAG:
|
||||
if (highestZuschlag === CostTypeVariant.NACHTZUSCHLAG || isVdek) {
|
||||
return [costType, calculateNachtzuschlag(tour, costType)];
|
||||
}
|
||||
return [costType, 0];
|
||||
case CostTypeVariant.WOCHENENDZUSCHLAG:
|
||||
if (
|
||||
highestZuschlag === CostTypeVariant.WOCHENENDZUSCHLAG ||
|
||||
isVdek
|
||||
) {
|
||||
return [costType, calculateWochenendzuschlag(tour, costType)];
|
||||
}
|
||||
return [costType, 0];
|
||||
case CostTypeVariant.FEIERTAGSZUSCHLAG:
|
||||
if (
|
||||
highestZuschlag === CostTypeVariant.FEIERTAGSZUSCHLAG ||
|
||||
isVdek
|
||||
) {
|
||||
return [costType, calculateFeiertagszuschlag(tour, costType)];
|
||||
}
|
||||
return [costType, 0];
|
||||
}
|
||||
})
|
||||
.filter(([, value]) => value !== 0) as [CostType, number][];
|
||||
}
|
||||
|
||||
private getHighestZuschlag(
|
||||
|
|
@ -235,17 +271,17 @@ export class CalculationService {
|
|||
case CostTypeVariant.NACHTZUSCHLAG:
|
||||
return [
|
||||
CostTypeVariant.NACHTZUSCHLAG,
|
||||
this.calculateNachtzuschlag(tour, costType),
|
||||
calculateNachtzuschlag(tour, costType),
|
||||
];
|
||||
case CostTypeVariant.WOCHENENDZUSCHLAG:
|
||||
return [
|
||||
CostTypeVariant.WOCHENENDZUSCHLAG,
|
||||
this.calculateWochenendzuschlag(tour, costType),
|
||||
calculateWochenendzuschlag(tour, costType),
|
||||
];
|
||||
case CostTypeVariant.FEIERTAGSZUSCHLAG:
|
||||
return [
|
||||
CostTypeVariant.FEIERTAGSZUSCHLAG,
|
||||
this.calculateFeiertagszuschlag(tour, costType),
|
||||
calculateFeiertagszuschlag(tour, costType),
|
||||
];
|
||||
}
|
||||
})
|
||||
|
|
@ -260,99 +296,6 @@ export class CalculationService {
|
|||
return zuschlaegeHighestToLowest[0][0];
|
||||
}
|
||||
|
||||
private calculateDesinfektionszuschlag(
|
||||
tour: Tour,
|
||||
costType: CostType,
|
||||
): number {
|
||||
if (tour.hasInfection && !tour.infectionName) {
|
||||
return 0;
|
||||
}
|
||||
if (
|
||||
tour.hasInfection &&
|
||||
(tour.infectionName.indexOf(costType.infectionType) > -1 ||
|
||||
INFECTION_TYPES[tour.infectionName] === costType.infectionType)
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private calculateTSW(tour: Tour, costType: CostType): number {
|
||||
if (
|
||||
costType.description.includes('TS+R') &&
|
||||
tour.transportType === 'TS+R'
|
||||
) {
|
||||
return costType.costAmount;
|
||||
} else if (
|
||||
(costType.description.includes('TS 1') ||
|
||||
costType.description.includes('TSW 1')) &&
|
||||
tour.driverId === tour.codriverId
|
||||
) {
|
||||
return costType.costAmount;
|
||||
} else if (
|
||||
(costType.description.includes('TS 2') ||
|
||||
costType.description.includes('TSW 2')) &&
|
||||
tour.driverId !== tour.codriverId
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private calculateFeiertagszuschlag(tour: Tour, costType: CostType): number {
|
||||
if (
|
||||
PUBLIC_HOLIDAYS_HH.some((day) =>
|
||||
isSameDay(
|
||||
new Date(day),
|
||||
new Date(toZonedTime(tour.startDate, 'Europe/Berlin')),
|
||||
),
|
||||
)
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private calculateNachtzuschlag(tour: Tour, costType: CostType): number {
|
||||
const firstInterval = generateIntervalForTimeRange(
|
||||
costType.timeRange,
|
||||
'Europe/Berlin',
|
||||
addDays(tour.startDate, -1),
|
||||
);
|
||||
const secondInterval = generateIntervalForTimeRange(
|
||||
costType.timeRange,
|
||||
'Europe/Berlin',
|
||||
tour.startDate,
|
||||
);
|
||||
if (
|
||||
isWithinInterval(tour.startDate, firstInterval) ||
|
||||
isWithinInterval(tour.startDate, secondInterval)
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private calculateKilometerzuschlag(tour: Tour, costType: CostType): number {
|
||||
const kilometerOverLimit = Math.ceil(
|
||||
tour.occupiedKm - costType.kmInclusive,
|
||||
);
|
||||
if (kilometerOverLimit <= 0) return 0;
|
||||
return costType.costAmount * kilometerOverLimit;
|
||||
}
|
||||
|
||||
private calculateFahrzeugzuschlag(tour: Tour, costType: CostType): number {
|
||||
const isCarNameSupported = !costType.excludeCarNames.includes(tour.carName);
|
||||
if (!isCarNameSupported) return 0;
|
||||
return costType.costAmount;
|
||||
}
|
||||
|
||||
private calculateWochenendzuschlag(tour: Tour, costType: CostType): number {
|
||||
if (isWeekend(toZonedTime(tour.startDate, 'Europe/Berlin')))
|
||||
return costType.costAmount;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async findTariff(
|
||||
healthInsurance: string,
|
||||
ordinanceType: string,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
import { CostType, Tour } from '@prisma/client';
|
||||
import { addDays, isSameDay, isWeekend, isWithinInterval } from 'date-fns';
|
||||
import { toZonedTime } from 'date-fns-tz';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
import { INFECTION_TYPES } from './infection-types';
|
||||
import { PUBLIC_HOLIDAYS_HH } from './public-holidays';
|
||||
|
||||
export function calculateDesinfektionszuschlag(
|
||||
tour: Tour,
|
||||
costType: CostType,
|
||||
): number {
|
||||
if (tour.hasInfection && !tour.infectionName) {
|
||||
return 0;
|
||||
}
|
||||
if (
|
||||
tour.hasInfection &&
|
||||
(tour.infectionName.indexOf(costType.infectionType) > -1 ||
|
||||
INFECTION_TYPES[tour.infectionName] === costType.infectionType)
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function calculateTSW(tour: Tour, costType: CostType): number {
|
||||
if (costType.description.includes('TS+R') && tour.transportType === 'TS+R') {
|
||||
return costType.costAmount;
|
||||
} else if (
|
||||
(costType.description.includes('TS 1') ||
|
||||
costType.description.includes('TSW 1')) &&
|
||||
tour.transportType === 'tsw'
|
||||
) {
|
||||
return costType.costAmount;
|
||||
} else if (
|
||||
(costType.description.includes('TS 2') ||
|
||||
costType.description.includes('TSW 2')) &&
|
||||
tour.transportType === 'TS+T'
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function calculateFeiertagszuschlag(
|
||||
tour: Tour,
|
||||
costType: CostType,
|
||||
): number {
|
||||
if (
|
||||
PUBLIC_HOLIDAYS_HH.some((day) =>
|
||||
isSameDay(
|
||||
new Date(day),
|
||||
new Date(toZonedTime(tour.startDate, 'Europe/Berlin')),
|
||||
),
|
||||
)
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function calculateNachtzuschlag(tour: Tour, costType: CostType): number {
|
||||
const firstInterval = generateIntervalForTimeRange(
|
||||
costType.timeRange,
|
||||
'Europe/Berlin',
|
||||
addDays(tour.startDate, -1),
|
||||
);
|
||||
const secondInterval = generateIntervalForTimeRange(
|
||||
costType.timeRange,
|
||||
'Europe/Berlin',
|
||||
tour.startDate,
|
||||
);
|
||||
if (
|
||||
isWithinInterval(tour.startDate, firstInterval) ||
|
||||
isWithinInterval(tour.startDate, secondInterval)
|
||||
) {
|
||||
return costType.costAmount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function calculateKilometerzuschlag(
|
||||
tour: Tour,
|
||||
costType: CostType,
|
||||
): number {
|
||||
const kilometerOverLimit = Math.ceil(tour.occupiedKm - costType.kmInclusive);
|
||||
if (kilometerOverLimit <= 0) return 0;
|
||||
return costType.costAmount * kilometerOverLimit;
|
||||
}
|
||||
|
||||
export function calculateFahrzeugzuschlag(
|
||||
tour: Tour,
|
||||
costType: CostType,
|
||||
): number {
|
||||
const isCarNameSupported = !costType.excludeCarNames.includes(tour.carName);
|
||||
if (!isCarNameSupported) return 0;
|
||||
return costType.costAmount;
|
||||
}
|
||||
|
||||
export function calculateWochenendzuschlag(
|
||||
tour: Tour,
|
||||
costType: CostType,
|
||||
): number {
|
||||
if (isWeekend(toZonedTime(tour.startDate, 'Europe/Berlin')))
|
||||
return costType.costAmount;
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -9,4 +9,4 @@ import { PersonnelCostCalculationService } from './personnel-cost-calculation.se
|
|||
providers: [CalculationService, PersonnelCostCalculationService],
|
||||
exports: [CalculationService],
|
||||
})
|
||||
export class FeatureCalculationModule {}
|
||||
export class FeatCalculationModule {}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export class TourLoader {
|
|||
|
||||
private static tourStagingToFinal(tour: TourStaging, ticketId?: string) {
|
||||
const {
|
||||
id,
|
||||
done,
|
||||
direction,
|
||||
carName,
|
||||
|
|
@ -96,6 +97,7 @@ export class TourLoader {
|
|||
} = tour;
|
||||
|
||||
return {
|
||||
id,
|
||||
done,
|
||||
direction,
|
||||
carName,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { FeatureCalculationModule } from 'src/modules/feat-calculation/feat-calculation.module';
|
||||
import { FeatCalculationModule } from 'src/modules/feat-calculation/feat-calculation.module';
|
||||
import { DataAccessExtractorsModule } from '../feat-extractors/data-access-extractors.module';
|
||||
import { ToursCalculationAdapater } from './adapter/tours-calculation.adapter';
|
||||
import { StagingRepository } from './staging.repository';
|
||||
import { TransformersService } from './transformers.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, DataAccessExtractorsModule, FeatureCalculationModule],
|
||||
imports: [PrismaModule, DataAccessExtractorsModule, FeatCalculationModule],
|
||||
providers: [TransformersService, StagingRepository, ToursCalculationAdapater],
|
||||
exports: [TransformersService],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ export class PatientTransformer {
|
|||
public static transform(patientSource: PatientSource): PatientPreStaging {
|
||||
const patientParsed = {
|
||||
...patientSource,
|
||||
surname: faker.person.lastName(),
|
||||
name: faker.person.firstName(),
|
||||
street: faker.location.streetAddress(),
|
||||
// surname: faker.person.lastName(),
|
||||
// name: faker.person.firstName(),
|
||||
// street: faker.location.streetAddress(),
|
||||
...transformProperties(parseDateTime('yyyy-MM-dd'))(
|
||||
pick(['birthday'])(patientSource),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ export class PlannedTimeTransformer {
|
|||
): PlannedTimePreStaging {
|
||||
const plannedTimeParsed = {
|
||||
...plannedTimeSource,
|
||||
firstname: faker.person.firstName(),
|
||||
surname: faker.person.lastName(),
|
||||
// firstname: faker.person.firstName(),
|
||||
// surname: faker.person.lastName(),
|
||||
...transformProperties(parseInteger)(
|
||||
pick(['id', 'user_id'])(plannedTimeSource),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -16,16 +16,16 @@ export class TourTransformer {
|
|||
public static transform(tourSource: TourSource): TourPreStaging {
|
||||
const tourParsed = {
|
||||
...tourSource,
|
||||
operationId: faker.number.int({ min: 10000, max: 19999 }),
|
||||
driverName: faker.person.fullName(),
|
||||
codriverName: faker.person.fullName(),
|
||||
patientName: faker.person.firstName(),
|
||||
patientSurname: faker.person.lastName(),
|
||||
patientStreet: faker.location.streetAddress(),
|
||||
startInstitution: faker.company.name(),
|
||||
startStreet: faker.location.streetAddress(),
|
||||
targetInstitution: faker.company.name(),
|
||||
targetStreet: faker.location.streetAddress(),
|
||||
// operationId: faker.number.int({ min: 10000, max: 19999 }),
|
||||
// driverName: faker.person.fullName(),
|
||||
// codriverName: faker.person.fullName(),
|
||||
// patientName: faker.person.firstName(),
|
||||
// patientSurname: faker.person.lastName(),
|
||||
// patientStreet: faker.location.streetAddress(),
|
||||
// startInstitution: faker.company.name(),
|
||||
// startStreet: faker.location.streetAddress(),
|
||||
// targetInstitution: faker.company.name(),
|
||||
// targetStreet: faker.location.streetAddress(),
|
||||
...transformProperties(parseInteger)(pick(['check'])(tourSource)),
|
||||
...transformProperties(parseFloat)(
|
||||
pick(['besetztKm', 'gesamtPreis'])(tourSource),
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export class WorkTimeTransformer {
|
|||
public static transform(workTimeSource: WorkTimeSource): WorkTimePreStaging {
|
||||
const workTimeParsed = {
|
||||
...workTimeSource,
|
||||
firstname: faker.person.firstName(),
|
||||
surname: faker.person.lastName(),
|
||||
// firstname: faker.person.firstName(),
|
||||
// surname: faker.person.lastName(),
|
||||
...transformProperties(parseInteger)(
|
||||
pick(['id', 'user_id'])(workTimeSource),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
import { FeatCalculationModule } from '../feat-calculation/feat-calculation.module';
|
||||
import { KpiResolver } from './kpi.resolver';
|
||||
import { KpiService } from './kpi.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, FeatBusinessObjectsModule, FeatCalculationModule],
|
||||
providers: [KpiService, KpiResolver],
|
||||
exports: [KpiService],
|
||||
})
|
||||
export class FeatKpiModule {}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { Args, Query, Resolver } from '@nestjs/graphql';
|
||||
import { TicketValidationState } from '@prisma/client';
|
||||
import { KpiService } from './kpi.service';
|
||||
import { KpiInfoObjectType } from './object-types';
|
||||
|
||||
@Resolver()
|
||||
export class KpiResolver {
|
||||
constructor(private readonly kpiService: KpiService) {}
|
||||
|
||||
@Query(() => KpiInfoObjectType)
|
||||
ticketStateKpi(
|
||||
@Args('state', { type: () => TicketValidationState })
|
||||
ticketState: TicketValidationState,
|
||||
) {
|
||||
return this.kpiService.ticketStateKpi(ticketState);
|
||||
}
|
||||
|
||||
@Query(() => KpiInfoObjectType)
|
||||
managerKpi(
|
||||
@Args('ordinanceType', { type: () => String }) ordinanceType: string,
|
||||
) {
|
||||
return this.kpiService.managerKpi(ordinanceType);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,384 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
ApprovalState,
|
||||
Prisma,
|
||||
TicketValidationState,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import {
|
||||
add,
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
set,
|
||||
subDays,
|
||||
subHours,
|
||||
subMonths,
|
||||
} from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
import { CarEntityService } from '../feat-business-objects/car-entity/car-entity.service';
|
||||
import { CalculationService } from '../feat-calculation/calculation.service';
|
||||
import { KpiInfoObjectType, MetricObjectType } from './object-types';
|
||||
|
||||
function ticketWithCurrentStateAndTours(
|
||||
ticketState: TicketValidationState,
|
||||
): Prisma.TicketWhereInput {
|
||||
return { currentState: ticketState, tours: { some: { check: 0 } } };
|
||||
}
|
||||
|
||||
function dateFromTime(timeString: string): Date {
|
||||
const [hours, minutes] = timeString.split(':');
|
||||
return set(new Date(), {
|
||||
hours: parseInt(hours),
|
||||
minutes: parseInt(minutes),
|
||||
seconds: 0,
|
||||
milliseconds: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class KpiService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly carEntity: CarEntityService,
|
||||
private readonly calculationService: CalculationService,
|
||||
) {}
|
||||
|
||||
async calcCityTourAvgTime(ordinanceType: string) {
|
||||
const tourTimestamps = await this.prisma.tour.findMany({
|
||||
select: {
|
||||
startBegin: true,
|
||||
startEnd: true,
|
||||
target: true,
|
||||
targetBegin: true,
|
||||
empty: true,
|
||||
},
|
||||
where: {
|
||||
...(ordinanceType !== 'ALL'
|
||||
? {
|
||||
ordinanceType: {
|
||||
contains: ordinanceType,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
done: true,
|
||||
totalKm: {
|
||||
lte: 17,
|
||||
},
|
||||
},
|
||||
take: 5000,
|
||||
});
|
||||
const tourTimes = tourTimestamps
|
||||
.filter((timings) => Object.values(timings).filter(Boolean).length > 0)
|
||||
.map((timings) => {
|
||||
const times = Object.values(timings).filter(Boolean).sort();
|
||||
const startTime = times.at(0);
|
||||
const endTime = times.at(-1);
|
||||
return differenceInMinutes(
|
||||
dateFromTime(endTime),
|
||||
dateFromTime(startTime),
|
||||
);
|
||||
});
|
||||
return tourTimes.reduce((sum, cur) => (sum += cur), 0) / tourTimes.length;
|
||||
}
|
||||
|
||||
async calcCityTourAvgRevenue(ordinanceType: string) {
|
||||
const tourRevenues = await this.prisma.tour.findMany({
|
||||
select: {
|
||||
revenue: true,
|
||||
},
|
||||
where: {
|
||||
...(ordinanceType !== 'ALL'
|
||||
? {
|
||||
ordinanceType: {
|
||||
contains: ordinanceType,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
done: true,
|
||||
totalKm: {
|
||||
lte: 17,
|
||||
},
|
||||
},
|
||||
take: 5000,
|
||||
});
|
||||
return (
|
||||
tourRevenues.reduce((sum, { revenue }) => (sum += revenue), 0) /
|
||||
tourRevenues.length
|
||||
);
|
||||
}
|
||||
|
||||
upcomingTours(): Promise<Tour[]> {
|
||||
const now = new Date();
|
||||
const endUpcoming = add(now, { minutes: 20 });
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: subHours(now, 4),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: endUpcoming,
|
||||
},
|
||||
},
|
||||
{
|
||||
startBegin: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async managerKpi(ordinanceType: string): Promise<KpiInfoObjectType> {
|
||||
const avgRevenueCityTour = await this.calcCityTourAvgRevenue(ordinanceType);
|
||||
const avgCityTourTimeInMinutes =
|
||||
await this.calcCityTourAvgTime(ordinanceType);
|
||||
const forDay = subDays(new Date(), 0);
|
||||
const cars = await this.carEntity.getCarsForOperationDay(
|
||||
forDay,
|
||||
ordinanceType,
|
||||
);
|
||||
const workdayInterval = generateIntervalForTimeRange(
|
||||
'05:00-04:59',
|
||||
'Europe/Berlin',
|
||||
forDay,
|
||||
);
|
||||
|
||||
// console.log(cars.map(car => ))
|
||||
|
||||
const workCapacity = cars
|
||||
.map((car) => car.availableTimeInInterval(workdayInterval))
|
||||
.reduce((sum, cur) => (sum += cur), 0);
|
||||
|
||||
const tourAmount = cars
|
||||
.map((car) => car.tourAmountInInterval(workdayInterval))
|
||||
.reduce((sum, cur) => (sum += cur), 0);
|
||||
|
||||
const possibleRevenue =
|
||||
((workCapacity * 60) / avgCityTourTimeInMinutes) * avgRevenueCityTour;
|
||||
const carRevenues = await Promise.all([
|
||||
...cars.map((car) =>
|
||||
car.breakEvenInInterval(workdayInterval, this.calculationService),
|
||||
),
|
||||
]);
|
||||
const totalRevenue = carRevenues.reduce(
|
||||
(sum, { toursRevenue }) => (sum += toursRevenue),
|
||||
0,
|
||||
);
|
||||
|
||||
return {
|
||||
main: {
|
||||
label: 'Realisierbarer Umsatz',
|
||||
value: Math.floor(
|
||||
(100 / (possibleRevenue == 0 ? 1 : possibleRevenue)) * totalRevenue,
|
||||
),
|
||||
},
|
||||
secondary: {
|
||||
label: 'Realisierbare Touren',
|
||||
value:
|
||||
possibleRevenue > totalRevenue
|
||||
? (possibleRevenue - totalRevenue) / avgRevenueCityTour
|
||||
: 0,
|
||||
},
|
||||
additional: [
|
||||
{
|
||||
label: 'Anzahl Touren',
|
||||
value: tourAmount,
|
||||
},
|
||||
{
|
||||
label: 'Kapazität (in h)',
|
||||
value: workCapacity,
|
||||
},
|
||||
{
|
||||
label: '⌀ Zeit Stadttour (in min)',
|
||||
value: avgCityTourTimeInMinutes,
|
||||
},
|
||||
{
|
||||
label: '⌀ Umsatz Stadttour',
|
||||
value: avgRevenueCityTour,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async ticketStateKpi(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<KpiInfoObjectType> {
|
||||
return {
|
||||
main: await this.fastMovingTicketPercentMetric(ticketState),
|
||||
secondary: await this.averageTimePerStateMetric(ticketState),
|
||||
additional: [
|
||||
await this.ticketCountPerStateMetric(ticketState),
|
||||
...(ticketState === TicketValidationState.USER_INTERACTION_REQUIRED
|
||||
? [await this.ticketApprovalRequiredCountMetric(ticketState)]
|
||||
: []),
|
||||
await this.tourCountPerTicketStateMetric(ticketState),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private async fastMovingTicketPercentMetric(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<MetricObjectType> {
|
||||
const ticketsGotState24HoursAgo = await this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
some: {
|
||||
state: ticketState,
|
||||
createdAt: {
|
||||
gte: subHours(new Date(), 24),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const ticketsMoved = ticketsGotState24HoursAgo.filter(
|
||||
({ currentState }) => currentState !== ticketState,
|
||||
);
|
||||
|
||||
return {
|
||||
label: 'Neue Tickets bearbeitet innerhalb von 24 Stunden',
|
||||
value:
|
||||
ticketsMoved.length === 0
|
||||
? 0
|
||||
: Math.floor(
|
||||
(100 / ticketsGotState24HoursAgo.length) * ticketsMoved.length,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
private async averageTimePerStateMetric(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<MetricObjectType> {
|
||||
const movedTickets = await this.prisma.ticket.findMany({
|
||||
where: {
|
||||
currentState: {
|
||||
not: ticketState,
|
||||
},
|
||||
state: {
|
||||
some: {
|
||||
state: ticketState,
|
||||
},
|
||||
},
|
||||
createdAt: {
|
||||
gte: subMonths(new Date(), 3),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
state: true,
|
||||
},
|
||||
});
|
||||
const timePerTickets = movedTickets
|
||||
.map(({ state }) => {
|
||||
const statesSorted = [...state].sort(
|
||||
(a, b) =>
|
||||
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
|
||||
);
|
||||
const currentStateIdx = statesSorted.findIndex(
|
||||
(s) => s.state === ticketState,
|
||||
);
|
||||
if (currentStateIdx === -1) return;
|
||||
// if (statesSorted.length - 1 === currentStateIdx) return;
|
||||
return differenceInHours(
|
||||
new Date(statesSorted.at(currentStateIdx + 1).createdAt),
|
||||
new Date(statesSorted.at(currentStateIdx).createdAt),
|
||||
);
|
||||
})
|
||||
.filter((v) => !!v);
|
||||
if (timePerTickets.length === 0) {
|
||||
return { label: '⌀ Verweildauer (in h)', value: 0 };
|
||||
}
|
||||
return {
|
||||
label: '⌀ Verweildauer (in h)',
|
||||
value:
|
||||
timePerTickets.reduce((sum, cur) => (sum += cur), 0) /
|
||||
timePerTickets.length,
|
||||
};
|
||||
}
|
||||
|
||||
private async ticketCountPerStateMetric(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<MetricObjectType> {
|
||||
const count = await this.prisma.ticket.count({
|
||||
where: { ...ticketWithCurrentStateAndTours(ticketState) },
|
||||
});
|
||||
return { label: 'Tickets', value: count };
|
||||
}
|
||||
|
||||
private async ticketApprovalRequiredCountMetric(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<MetricObjectType> {
|
||||
const count = await this.prisma.ticket.count({
|
||||
where: {
|
||||
...ticketWithCurrentStateAndTours(ticketState),
|
||||
approvalState: ApprovalState.REQUIRED,
|
||||
},
|
||||
});
|
||||
return { label: 'Genehmigungspflicht', value: count };
|
||||
}
|
||||
|
||||
private async tourCountPerTicketStateMetric(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<MetricObjectType> {
|
||||
const count = await this.prisma.tour.count({
|
||||
where: {
|
||||
Ticket: {
|
||||
...ticketWithCurrentStateAndTours(ticketState),
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
label: 'Touren',
|
||||
value: count,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Field, Float, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType('Metric')
|
||||
export class MetricObjectType {
|
||||
@Field()
|
||||
label: string;
|
||||
@Field(() => Float)
|
||||
value: number;
|
||||
}
|
||||
|
||||
@ObjectType('KpiInfo')
|
||||
export class KpiInfoObjectType {
|
||||
@Field(() => MetricObjectType)
|
||||
main: MetricObjectType;
|
||||
@Field(() => MetricObjectType)
|
||||
secondary: MetricObjectType;
|
||||
@Field(() => [MetricObjectType])
|
||||
additional: MetricObjectType[];
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import {
|
|||
TicketValidationState,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { DocumentInfoArgs } from './ticket-arg.types';
|
||||
import {
|
||||
ValidationResult,
|
||||
validatePatient,
|
||||
|
|
@ -12,8 +14,6 @@ import {
|
|||
validateTour,
|
||||
} from './ticket-validation.functions';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
import { DocumentInfoArgs } from './ticket-arg.types';
|
||||
import { Interval } from 'date-fns';
|
||||
|
||||
@Injectable()
|
||||
export class TicketLifecycleService {
|
||||
|
|
@ -118,12 +118,28 @@ export class TicketLifecycleService {
|
|||
: ApprovalState.REQUIRED,
|
||||
});
|
||||
} else {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
// NOTE: Condition required to get these tickets out of USER_INTERACTION_REQUIRED
|
||||
if (
|
||||
ticket.tours.every(
|
||||
({ ordinanceType }) =>
|
||||
ordinanceType.indexOf('BTW') > -1 ||
|
||||
ordinanceType.indexOf('TSW') > -1,
|
||||
)
|
||||
) {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.TD_UNCERTAIN,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
} else {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await this.repository.setActionablesOnTicket(ticket.id, errors, notes);
|
||||
|
|
@ -210,6 +226,14 @@ export class TicketLifecycleService {
|
|||
}
|
||||
|
||||
private async checkTourApprovalRequirement(tour: Tour): Promise<boolean> {
|
||||
// NOTE: Requirement by Avicenna
|
||||
if (
|
||||
tour.ordinanceType.indexOf('BTW') > -1 ||
|
||||
tour.ordinanceType.indexOf('TSW') > -1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
'Einweisung',
|
||||
|
|
|
|||
|
|
@ -27,15 +27,15 @@ export function validatePatient(patient: Patient): ValidationResult {
|
|||
isValid = false;
|
||||
errors.push('Patient: Krankenkasse ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.careDegree) {
|
||||
notes.push('Patient: Pflegegrad ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.disabilityMark1) {
|
||||
notes.push('Patient: Merkzeichen 1 ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.disabilityMark2) {
|
||||
notes.push('Patient: Merkzeichen 2 ist nicht gefüllt');
|
||||
}
|
||||
// if (!patient?.careDegree) {
|
||||
// notes.push('Patient: Pflegegrad ist nicht gefüllt');
|
||||
// }
|
||||
// if (!patient?.disabilityMark1) {
|
||||
// notes.push('Patient: Merkzeichen 1 ist nicht gefüllt');
|
||||
// }
|
||||
// if (!patient?.disabilityMark2) {
|
||||
// notes.push('Patient: Merkzeichen 2 ist nicht gefüllt');
|
||||
// }
|
||||
|
||||
return {
|
||||
result: isValid,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
'use strict';
|
||||
|
||||
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
import { resourceFromAttributes } from '@opentelemetry/resources';
|
||||
import * as opentelemetry from '@opentelemetry/sdk-node';
|
||||
import {
|
||||
ATTR_SERVICE_NAME,
|
||||
ATTR_SERVICE_VERSION,
|
||||
} from '@opentelemetry/semantic-conventions';
|
||||
|
||||
// Configure the SDK to export telemetry data to the console
|
||||
// Enable all auto-instrumentations from the meta package
|
||||
const exporterOptions = {
|
||||
url: process.env.TRACE_EXPORTER_URL ?? 'http://localhost:4318/v1/traces',
|
||||
};
|
||||
|
||||
const traceExporter = new OTLPTraceExporter(exporterOptions);
|
||||
const sdk = new opentelemetry.NodeSDK({
|
||||
traceExporter,
|
||||
instrumentations: [getNodeAutoInstrumentations()],
|
||||
resource: resourceFromAttributes({
|
||||
[ATTR_SERVICE_NAME]: process.env.OTLP_SERVICE_NAME ?? 'avicenna.data-connector',
|
||||
// [ATTR_SERVICE_NAMESPACE]: 'avicenna',
|
||||
[ATTR_SERVICE_VERSION]: '1.0',
|
||||
}),
|
||||
});
|
||||
|
||||
sdk.start();
|
||||
|
||||
// gracefully shut down the SDK on process exit
|
||||
process.on('SIGTERM', () => {
|
||||
sdk
|
||||
.shutdown()
|
||||
.then(() => console.log('Tracing terminated'))
|
||||
.catch((error) => console.log('Error terminating tracing', error))
|
||||
.finally(() => process.exit(0));
|
||||
});
|
||||
|
||||
export default sdk;
|
||||
Loading…
Reference in New Issue