feat: implement new tour calculation rules
This commit is contained in:
parent
41472ba17b
commit
aa29d58243
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterEnum
|
||||
ALTER TYPE "CostTypeVariant" ADD VALUE 'TRAGEHILFE';
|
||||
|
|
@ -531,6 +531,7 @@ enum CostTypeVariant {
|
|||
GRUNDPREIS
|
||||
GRUNDPREIS_ZEIT
|
||||
PRIVATVERSICHERUNG
|
||||
TRAGEHILFE
|
||||
}
|
||||
|
||||
model AvicennaTenant {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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<DB>,
|
||||
) {}
|
||||
|
||||
async findByOperationId(operationId: string): Promise<Tour> {
|
||||
return await this.prisma.tour.findFirst({
|
||||
|
|
@ -170,6 +176,40 @@ export class TourRepository {
|
|||
});
|
||||
}
|
||||
|
||||
async findTragehilfeTour(tour: Tour): Promise<Tour | null> {
|
||||
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<Tour | null> {
|
||||
const institutionStreet =
|
||||
|
|
|
|||
|
|
@ -48,6 +48,10 @@ export class TourService {
|
|||
return this.repository.findOnSameDayWithOppositeDirection(tour);
|
||||
}
|
||||
|
||||
async findTragehilfeTour(tour: Tour): Promise<Tour | null> {
|
||||
return this.repository.findTragehilfeTour(tour)
|
||||
}
|
||||
|
||||
async findToursWithTicket(interval: Interval): Promise<Tour[]> {
|
||||
return this.repository.findWithTicket(interval);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<number> {
|
||||
|
|
@ -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<boolean> {
|
||||
const tragehilfeTour = await this.tourService.findTragehilfeTour(tour);
|
||||
return !!tragehilfeTour;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@ 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) {
|
||||
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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue