diff --git a/service/data-hub/data-connector/prisma/migrations/20250725081454_add_cost_type_tragehilfe/migration.sql b/service/data-hub/data-connector/prisma/migrations/20250725081454_add_cost_type_tragehilfe/migration.sql new file mode 100644 index 0000000..0399202 --- /dev/null +++ b/service/data-hub/data-connector/prisma/migrations/20250725081454_add_cost_type_tragehilfe/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "CostTypeVariant" ADD VALUE 'TRAGEHILFE'; diff --git a/service/data-hub/data-connector/prisma/schema.prisma b/service/data-hub/data-connector/prisma/schema.prisma index abf8b7d..a84f49f 100644 --- a/service/data-hub/data-connector/prisma/schema.prisma +++ b/service/data-hub/data-connector/prisma/schema.prisma @@ -531,6 +531,7 @@ enum CostTypeVariant { GRUNDPREIS GRUNDPREIS_ZEIT PRIVATVERSICHERUNG + TRAGEHILFE } model AvicennaTenant { diff --git a/service/data-hub/data-connector/prisma/seed/tariffs/ktw-dak.ts b/service/data-hub/data-connector/prisma/seed/tariffs/ktw-dak.ts index 1e6779c..1f22019 100644 --- a/service/data-hub/data-connector/prisma/seed/tariffs/ktw-dak.ts +++ b/service/data-hub/data-connector/prisma/seed/tariffs/ktw-dak.ts @@ -289,6 +289,11 @@ export const KTW_DAK_TARIFFS = [ costAmount: 354, kmInclusive: 16, }, + { + description: 'Tragehilfe', + variant: CostTypeVariant.TRAGEHILFE, + costAmount: 5360, + }, { description: 'Nachtzuschlag 19:00-19:59', variant: CostTypeVariant.NACHTZUSCHLAG, @@ -375,6 +380,11 @@ export const KTW_DAK_TARIFFS = [ costAmount: 369, kmInclusive: 16, }, + { + description: 'Tragehilfe', + variant: CostTypeVariant.TRAGEHILFE, + costAmount: 5596, + }, { description: 'Nachtzuschlag 19:00-19:59', variant: CostTypeVariant.NACHTZUSCHLAG, @@ -461,6 +471,11 @@ export const KTW_DAK_TARIFFS = [ costAmount: 369, kmInclusive: 16, }, + { + description: 'Tragehilfe', + variant: CostTypeVariant.TRAGEHILFE, + costAmount: 5596, + }, { description: 'Nachtzuschlag 19:00-19:59', variant: CostTypeVariant.NACHTZUSCHLAG, diff --git a/service/data-hub/data-connector/prisma/seed/tariffs/ktw-vdek.ts b/service/data-hub/data-connector/prisma/seed/tariffs/ktw-vdek.ts index 28c6e5a..8aadd86 100644 --- a/service/data-hub/data-connector/prisma/seed/tariffs/ktw-vdek.ts +++ b/service/data-hub/data-connector/prisma/seed/tariffs/ktw-vdek.ts @@ -366,6 +366,11 @@ export const KTW_VDEK_TARIFFS = [ costAmount: 402, kmInclusive: 16, }, + { + description: 'Tragehilfe', + variant: CostTypeVariant.TRAGEHILFE, + costAmount: 6271, + }, { description: 'Nachtzuschlag 19:00-19:59', variant: CostTypeVariant.NACHTZUSCHLAG, diff --git a/service/data-hub/data-connector/prisma/seed/tariffs/tsw-aok.ts b/service/data-hub/data-connector/prisma/seed/tariffs/tsw-aok.ts index d6c35b1..0d65b8b 100644 --- a/service/data-hub/data-connector/prisma/seed/tariffs/tsw-aok.ts +++ b/service/data-hub/data-connector/prisma/seed/tariffs/tsw-aok.ts @@ -145,18 +145,19 @@ export const TSW_AOK_TARIFFS = [ costTypes: [ { description: 'Grundpauschale je Fahrt', - variant: CostTypeVariant.GRUNDPREIS, + variant: CostTypeVariant.GRUNDPREIS_ZEIT, costAmount: 6000, + timeRange: '00:00-14:29', }, { - description: 'ab dem 21. km', - variant: CostTypeVariant.KILOMETERZUSCHLAG, - costAmount: 200, - kmInclusive: 20, + description: 'Grundpauschale je Fahrt', + variant: CostTypeVariant.GRUNDPREIS_ZEIT, + costAmount: 6000, + timeRange: '18:01-23:59', }, { - description: 'Nachmittagszuschlag 14:30-18:00', - variant: CostTypeVariant.NACHTZUSCHLAG, + description: 'Nachmittagspauschale 14:30-18:00', + variant: CostTypeVariant.GRUNDPREIS_ZEIT, costAmount: 7000, timeRange: '14:30-18:00', }, @@ -166,6 +167,12 @@ export const TSW_AOK_TARIFFS = [ costAmount: 5000, timeRange: '18:00-07:30', }, + { + description: 'ab dem 21. km', + variant: CostTypeVariant.KILOMETERZUSCHLAG, + costAmount: 200, + kmInclusive: 20, + }, { description: 'Wochenendzuschlag', variant: CostTypeVariant.WOCHENENDZUSCHLAG, diff --git a/service/data-hub/data-connector/prisma/seed/tariffs/tsw-dak-vdek.ts b/service/data-hub/data-connector/prisma/seed/tariffs/tsw-dak-vdek.ts index d55c395..f62b8e6 100644 --- a/service/data-hub/data-connector/prisma/seed/tariffs/tsw-dak-vdek.ts +++ b/service/data-hub/data-connector/prisma/seed/tariffs/tsw-dak-vdek.ts @@ -38,7 +38,7 @@ export const TSW_DAK_VDEK_TARIFFS = [ { description: 'ab dem 22. km', variant: CostTypeVariant.KILOMETERZUSCHLAG, - costAmount: 159, + costAmount: 166, kmInclusive: 21, }, { @@ -90,13 +90,13 @@ export const TSW_DAK_VDEK_TARIFFS = [ { description: 'Abendzuschlag 19:00-23:00', variant: CostTypeVariant.NACHTZUSCHLAG, - costAmount: 407, + costAmount: 750, timeRange: '19:00-23:00', }, { description: 'ab dem 22. km', variant: CostTypeVariant.KILOMETERZUSCHLAG, - costAmount: 159, + costAmount: 180, kmInclusive: 21, }, ], diff --git a/service/data-hub/data-connector/src/core/database/types.ts b/service/data-hub/data-connector/src/core/database/types.ts index d2f12aa..a9c2d93 100644 --- a/service/data-hub/data-connector/src/core/database/types.ts +++ b/service/data-hub/data-connector/src/core/database/types.ts @@ -39,7 +39,8 @@ export const CostTypeVariant = { TSWDIFFERENZIERUNG: "TSWDIFFERENZIERUNG", GRUNDPREIS: "GRUNDPREIS", GRUNDPREIS_ZEIT: "GRUNDPREIS_ZEIT", - PRIVATVERSICHERUNG: "PRIVATVERSICHERUNG" + PRIVATVERSICHERUNG: "PRIVATVERSICHERUNG", + TRAGEHILFE: "TRAGEHILFE" } as const; export type CostTypeVariant = (typeof CostTypeVariant)[keyof typeof CostTypeVariant]; export type Anomaly = { diff --git a/service/data-hub/data-connector/src/modules/app-ticket-system/tickets.resolver.ts b/service/data-hub/data-connector/src/modules/app-ticket-system/tickets.resolver.ts index 972f485..8999d69 100644 --- a/service/data-hub/data-connector/src/modules/app-ticket-system/tickets.resolver.ts +++ b/service/data-hub/data-connector/src/modules/app-ticket-system/tickets.resolver.ts @@ -10,6 +10,7 @@ import { import { TicketValidationState } from '@prisma/client'; import { TourObjectType } from '../app-accounting/tour.object-type'; import { DocumentInfoArgs, TicketsArgs } from './ticket-arg.types'; +import { TicketSystemService } from './ticket-system.service'; import { PaginatedTicketObjectType, TicketObjectType, @@ -17,7 +18,6 @@ import { TicketStateMetaObjectType, TicketsUpdatedObjectType, } from './ticket.object-type'; -import { TicketSystemService } from './ticket-system.service'; @Resolver(() => TicketObjectType) export class TicketsResolver { @@ -43,6 +43,7 @@ export class TicketsResolver { return this.ticketsService.searchTickets(query); } + // TODO: obsolete @Query(() => TicketStateMetaObjectType) ticketStateMeta( @Args('state', { type: () => TicketValidationState }) @@ -51,6 +52,7 @@ export class TicketsResolver { return this.ticketsService.getTicketStateMeta(ticketState); } + // TODO: obsolete @Query(() => TicketStateKeyFiguresObjectType) ticketStateKeyFigures( @Args('state', { type: () => TicketValidationState }) diff --git a/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.repository.ts b/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.repository.ts index e306a82..3a09139 100644 --- a/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.repository.ts +++ b/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.repository.ts @@ -1,7 +1,10 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Prisma, Tour } from '@prisma/client'; import { endOfDay, Interval, startOfDay } from 'date-fns'; +import { Kysely } from 'kysely'; +import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module'; import { PrismaService } from 'src/core/database/prisma.service'; +import { DB } from 'src/core/database/types'; import { AccountingState } from 'src/modules/app-accounting/accounting-state'; import { createPrismaQueryFromAccountingFilters, @@ -72,7 +75,10 @@ function onlyRealTours(): Prisma.TourWhereInput[] { @Injectable() export class TourRepository { - constructor(private readonly prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + @Inject(KYSELY_CLIENT_PROVIDER_TOKEN) private readonly db: Kysely, + ) {} async findByOperationId(operationId: string): Promise { return await this.prisma.tour.findFirst({ @@ -170,6 +176,40 @@ export class TourRepository { }); } + async findTragehilfeTour(tour: Tour): Promise { + const { + patientId, + patientName, + patientSurname, + patientCity, + patientStreet, + patientZip, + } = tour; + return await this.prisma.tour.findFirst({ + where: { + patientId, + patientName, + patientSurname, + patientStreet, + patientCity, + patientZip, + ordinanceType: 'Tragehilfe', + AND: [ + { + startDate: { + gte: startOfDay(new Date(tour.startDate)), + }, + }, + { + startDate: { + lt: endOfDay(new Date(tour.startDate)), + }, + }, + ], + }, + }); + } + // -------------------- Ticket System ------------------- // async findFirstRecurring(tour: Tour): Promise { const institutionStreet = diff --git a/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.service.ts b/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.service.ts index 518072f..a0de83c 100644 --- a/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.service.ts +++ b/service/data-hub/data-connector/src/modules/feat-business-objects/entities/tour/tour.service.ts @@ -48,6 +48,10 @@ export class TourService { return this.repository.findOnSameDayWithOppositeDirection(tour); } + async findTragehilfeTour(tour: Tour): Promise { + return this.repository.findTragehilfeTour(tour) + } + async findToursWithTicket(interval: Interval): Promise { return this.repository.findWithTicket(interval); } diff --git a/service/data-hub/data-connector/src/modules/feat-calculation/calculation.service.ts b/service/data-hub/data-connector/src/modules/feat-calculation/calculation.service.ts index d71d51d..7f99b46 100644 --- a/service/data-hub/data-connector/src/modules/feat-calculation/calculation.service.ts +++ b/service/data-hub/data-connector/src/modules/feat-calculation/calculation.service.ts @@ -10,6 +10,7 @@ import { Interval, addDays, isSaturday, isWithinInterval } from 'date-fns'; import { generateIntervalForTimeRange } from 'src/utils'; import { TariffService } from '../feat-business-objects/entities/tariff/tariff.service'; +import { TourService } from '../feat-business-objects/entities/tour/tour.service'; import { BILLING_INFORMATION_BY_ORDINANCE_TYPE } from './billing-information-by-ordinance-type'; import { calculateDesinfektionszuschlag, @@ -38,6 +39,7 @@ export class CalculationService { constructor( private readonly personnelCostCalculationService: PersonnelCostCalculationService, private readonly tariffService: TariffService, + private readonly tourService: TourService, ) {} async calculateTourRevenue(tour: Tour): Promise { @@ -56,12 +58,11 @@ export class CalculationService { ); return 0; } - const revenue = - this.calculateProvidedServicePositions(tour, tariff).reduce( - (sum, [, value]) => (sum += value), - 0, - ) / 100; - return revenue; + const servicePositions = await this.calculateProvidedServicePositions( + tour, + tariff, + ); + return servicePositions.reduce((sum, [, value]) => (sum += value), 0) / 100; } async listProvidedServicePositions(tour: Tour): Promise<{ @@ -85,7 +86,10 @@ export class CalculationService { } return { tariff, - servicePositions: this.calculateProvidedServicePositions(tour, tariff), + servicePositions: await this.calculateProvidedServicePositions( + tour, + tariff, + ), }; } @@ -201,18 +205,19 @@ export class CalculationService { return billingInformation[1]; } - private calculateProvidedServicePositions( + private async calculateProvidedServicePositions( tour: Tour, tariff: Tariff & { costTypes: CostType[]; tariffGroup: TariffGroup }, - ): [CostType, number][] { + ): Promise<[CostType, number][]> { const highestZuschlag = this.getHighestZuschlag(tour, tariff); // if TariffGroup is VDEK KTW, sum up costTypes const sumZuschlaege = tariff.tariffGroup?.description?.indexOf('VDEK KTW') > -1 || + tariff.tariffGroup?.description?.indexOf('DAK KTW') > -1 || tariff.tariffGroup?.description?.indexOf('Privat') > -1; - return tariff.costTypes - .map((costType) => { + return Promise.all( + tariff.costTypes.map(async (costType) => { switch (costType.variant) { case CostTypeVariant.GRUNDPREIS: return [costType, costType.costAmount]; @@ -224,6 +229,13 @@ export class CalculationService { return [costType, calculateDesinfektionszuschlag(tour, costType)]; case CostTypeVariant.KILOMETERZUSCHLAG: return [costType, calculateKilometerzuschlag(tour, costType)]; + + // Currently the only async task in calculation + case CostTypeVariant.TRAGEHILFE: + const hasTragehilfe = await this.hasTragehilfe(tour); + if (hasTragehilfe) return [costType, costType.costAmount]; + return [costType, 0]; + case CostTypeVariant.FAHRZEUGZUSCHLAG: return [costType, calculateFahrzeugzuschlag(tour, costType)]; case CostTypeVariant.GRUNDPREIS_ZEIT: @@ -253,8 +265,14 @@ export class CalculationService { } return [costType, 0]; } - }) - .filter(([, value]) => value !== 0) as [CostType, number][]; + }), + ).then( + (servicePositions) => + servicePositions.filter(([, value]) => value !== 0) as [ + CostType, + number, + ][], + ); } private getHighestZuschlag( @@ -330,7 +348,8 @@ export class CalculationService { ); } - getHealthInsurancesWithNoTenant(): string[] { - return [...new Set(this.healthInsurancesWithNoTenant)]; + private async hasTragehilfe(tour: Tour): Promise { + const tragehilfeTour = await this.tourService.findTragehilfeTour(tour); + return !!tragehilfeTour; } } diff --git a/service/data-hub/data-connector/src/modules/feat-calculation/cost-type-calculations.ts b/service/data-hub/data-connector/src/modules/feat-calculation/cost-type-calculations.ts index fe9b5ca..43d7223 100644 --- a/service/data-hub/data-connector/src/modules/feat-calculation/cost-type-calculations.ts +++ b/service/data-hub/data-connector/src/modules/feat-calculation/cost-type-calculations.ts @@ -5,9 +5,15 @@ import { generateIntervalForTimeRange } from 'src/utils'; import { INFECTION_TYPES } from './infection-types'; import { PUBLIC_HOLIDAYS_HH } from './public-holidays'; -export function calculatePrivatVersicherung(tour: Tour, costType: CostType): number { - if(tour.category?.indexOf('Privatfahrt Rechnung') === -1 && tour.category?.indexOf('Barzahlung') === -1) { - if(costType.description === 'Hygienemaßnahmen' && tour.hasInfection) { +export function calculatePrivatVersicherung( + tour: Tour, + costType: CostType, +): number { + if ( + tour.category?.indexOf('Privatfahrt Rechnung') === -1 && + tour.category?.indexOf('Barzahlung') === -1 + ) { + if (costType.description === 'Hygienemaßnahmen' && tour.hasInfection) { return 0; } return costType.costAmount; @@ -38,13 +44,13 @@ export function calculateTSW(tour: Tour, costType: CostType): number { } else if ( (costType.description.includes('TS 1') || costType.description.includes('TSW 1')) && - tour.transportType === 'tsw' + (tour.transportType === 'tsw' || tour.transportType === 'TS1') ) { return costType.costAmount; } else if ( (costType.description.includes('TS 2') || costType.description.includes('TSW 2')) && - tour.transportType === 'TS+T' + (tour.transportType === 'TS+T' || tour.transportType === 'TS2') ) { return costType.costAmount; }