ref: add repository and service for all entities
This commit is contained in:
parent
5eb775523f
commit
51ba4a1f83
|
|
@ -63,6 +63,7 @@ export class AppController {
|
|||
.then(() => this.anomaliesService.validateAnomalies());
|
||||
}
|
||||
|
||||
//! Used to create check 2 report
|
||||
@Get('tour-calculation')
|
||||
async tourCalculation(@Query('from') from: string, @Query('to') to: string) {
|
||||
const interval = {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { ApplicationTicketSystemModule } from './modules/app-ticket-system/app-t
|
|||
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 { FeatTicketSystemModule } from './modules/feat-tickets/ticket-system.module';
|
||||
import { FeatCalculationModule } from './modules/feat-calculation/feat-calculation.module';
|
||||
import { AppHomeModule } from './modules/app-home/app-home.module';
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ import { AppHomeModule } from './modules/app-home/app-home.module';
|
|||
FeatureEtlModule,
|
||||
FeatReportsModule,
|
||||
FeatCalculationModule,
|
||||
TicketSystemModule,
|
||||
FeatTicketSystemModule,
|
||||
AnomalyDetectionModule,
|
||||
ManagerModule,
|
||||
ApplicationAccountingModule,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ type Anomaly {
|
|||
sleepSince: DateTime
|
||||
sleepTimer: Int!
|
||||
solution: [SolutionTarget!]!
|
||||
tour: Tour
|
||||
type: String!
|
||||
workedOnBy: String!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ import {
|
|||
ObjectType,
|
||||
} from '@nestjs/graphql';
|
||||
import { PaginationArgs } from 'src/core/base/pagination.input';
|
||||
import { AccountingFilter } from './accounting-filter';
|
||||
import { AccountingState } from './accounting-state';
|
||||
import { TourAccountingFilterDto } from '../feat-business-objects/entities/tour/tours.filter';
|
||||
|
||||
@InputType()
|
||||
export class AccountingFilterArgs implements AccountingFilter {
|
||||
export class AccountingFilterArgs implements TourAccountingFilterDto {
|
||||
@Field(() => [String], { nullable: true })
|
||||
deviation!: 'pos' | 'neg' | 'both'[];
|
||||
@Field(() => [String], { nullable: true })
|
||||
|
|
|
|||
|
|
@ -1,242 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { TourStateMetaObjectType } from './accounting-arg.types';
|
||||
import {
|
||||
AccountingFilter,
|
||||
createPrismaQueryFromAccountingFilters,
|
||||
} from './accounting-filter';
|
||||
import { AccountingState } from './accounting-state';
|
||||
|
||||
@Injectable()
|
||||
export class AccountingValidationRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findByState(
|
||||
state: AccountingState,
|
||||
filter: AccountingFilter,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
) {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const query: Prisma.TourFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 && !filter?.deviation
|
||||
? { revenueDeviation: { not: 0 } }
|
||||
: {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [
|
||||
...createPrismaQueryFromAccountingFilters(filter),
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
|
||||
const [tours, count] = await this.prisma.$transaction([
|
||||
this.prisma.tour.findMany({ ...query }),
|
||||
this.prisma.tour.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tours,
|
||||
total: count,
|
||||
};
|
||||
}
|
||||
|
||||
async calculateTourStateMeta(
|
||||
state: AccountingState,
|
||||
): Promise<TourStateMetaObjectType> {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const ticketWithCurrentStateAndTours: Prisma.TourWhereInput = {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 ? { revenueDeviation: { not: 0 } } : {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
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 [
|
||||
count,
|
||||
revenueSum,
|
||||
positiveRevenueDeviation,
|
||||
negativeRevenueDeviation,
|
||||
] = await this.prisma.$transaction([
|
||||
this.prisma.tour.count({
|
||||
where: { ...ticketWithCurrentStateAndTours },
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenue: true,
|
||||
},
|
||||
where: {
|
||||
...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,
|
||||
};
|
||||
}
|
||||
|
||||
findTicketByTicketId(ticketId: string) {
|
||||
return this.prisma.ticket.findFirst({
|
||||
where: {
|
||||
id: ticketId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private getCheckValueForAccountingState(state: AccountingState): number {
|
||||
switch (state) {
|
||||
case AccountingState.Billable:
|
||||
return 0;
|
||||
case AccountingState.PreparedForBilling:
|
||||
return 1;
|
||||
case AccountingState.Billed:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,27 +4,29 @@ import {
|
|||
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 { AccountingService } from './accounting.service';
|
||||
import { TicketService } from '../feat-business-objects/entities/ticket/ticket.service';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
|
||||
import { TourAccountingFilterDto } from '../feat-business-objects/entities/tour/tours.filter';
|
||||
|
||||
@Injectable()
|
||||
export class AccountingValidationService {
|
||||
constructor(
|
||||
private readonly repository: AccountingValidationRepository,
|
||||
private readonly accounting: AccountingService,
|
||||
private readonly ticketService: TicketService,
|
||||
private readonly tourService: TourService,
|
||||
) {}
|
||||
|
||||
async paginateToursByState(
|
||||
state: AccountingState,
|
||||
filter: AccountingFilter,
|
||||
filter: TourAccountingFilterDto,
|
||||
take: number = 10,
|
||||
cursor?: string,
|
||||
): Promise<PaginatedTourObjectType> {
|
||||
const skip = cursor ? decodeOffsetCursor(cursor) : 0;
|
||||
const { tours, total } = await this.repository.findByState(
|
||||
const { tours, total } = await this.tourService.findByAccountingState(
|
||||
state,
|
||||
filter,
|
||||
take,
|
||||
|
|
@ -46,12 +48,12 @@ export class AccountingValidationService {
|
|||
async getAccountingStateMeta(
|
||||
accountingState: AccountingState,
|
||||
): Promise<TourStateMetaObjectType> {
|
||||
return this.repository.calculateTourStateMeta(accountingState);
|
||||
return this.tourService.calculateAccountingStateMeta(accountingState);
|
||||
}
|
||||
|
||||
findTicketByTicketId(ticketId?: string | null) {
|
||||
if (!ticketId) return;
|
||||
return this.repository.findTicketByTicketId(ticketId);
|
||||
return this.ticketService.findById(ticketId);
|
||||
}
|
||||
|
||||
async getTourCostBreakdown(operationId: string) {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,20 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { format } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.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,
|
||||
private readonly tourService: TourService,
|
||||
) {}
|
||||
|
||||
async tourCostBreakdown(
|
||||
operationId: string,
|
||||
): Promise<TourCostBreakdownObjectType> {
|
||||
const tour = await this.prisma.tour.findFirst({
|
||||
where: { operationId },
|
||||
});
|
||||
const tour = await this.tourService.findByOperationId(operationId);
|
||||
|
||||
const { tariff, servicePositions } =
|
||||
await this.calculation.listProvidedServicePositions(tour);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
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 { 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, FeatCalculationModule],
|
||||
imports: [PrismaModule, FeatCalculationModule, FeatBusinessObjectsModule],
|
||||
providers: [
|
||||
AccountingValidationRepository,
|
||||
AccountingValidationResolver,
|
||||
AccountingValidationService,
|
||||
AccountingService,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
import { FeatKpiModule } from '../feat-kpis/feat-kpis.module';
|
||||
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, FeatKpiModule],
|
||||
imports: [FeatBusinessObjectsModule, FeatKpiModule],
|
||||
providers: [ControlCenterResolver, SolutionTargetsResolver],
|
||||
})
|
||||
export class ApplicationControlCenterModule {}
|
||||
|
|
|
|||
|
|
@ -6,26 +6,29 @@ import {
|
|||
ResolveField,
|
||||
Resolver,
|
||||
} from '@nestjs/graphql';
|
||||
|
||||
import { Anomaly } from '@prisma/client';
|
||||
import { PaginationArgs } from 'src/core/base/pagination.input';
|
||||
import {
|
||||
decodeOffsetCursor,
|
||||
encodeCursor,
|
||||
} from 'src/core/base/pagination.util';
|
||||
import { TourObjectType } from '../app-accounting/tour.object-type';
|
||||
import { AnomaliesService } from '../feat-anomalies/anomalies.service';
|
||||
import { AnomalyTypes } from '../feat-anomalies/anomaly-detectors/anomaly-types.enum';
|
||||
import { AnomalyTypes } from '../feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomaly.service';
|
||||
import { KpiService } from '../feat-kpis/kpi.service';
|
||||
import {
|
||||
AnomalyObjectType,
|
||||
PaginatedAnomalyGroupObjectType,
|
||||
PaginatedAnomalyObjectType,
|
||||
SolutionTargetObjectType,
|
||||
} from './anomaly.object-type';
|
||||
import { KpiService } from '../feat-kpis/kpi.service';
|
||||
|
||||
@Resolver(() => AnomalyObjectType)
|
||||
export class ControlCenterResolver {
|
||||
constructor(private readonly anomaliesService: AnomaliesService, private readonly kpiService: KpiService) {}
|
||||
constructor(
|
||||
private readonly kpiService: KpiService,
|
||||
private readonly anomalyService: AnomalyService,
|
||||
) {}
|
||||
|
||||
@Query(() => [TourObjectType])
|
||||
async upcomingTours() {
|
||||
|
|
@ -34,24 +37,24 @@ export class ControlCenterResolver {
|
|||
|
||||
@Query(() => AnomalyObjectType)
|
||||
async anomaly(@Args('id') id: string) {
|
||||
return this.anomaliesService.findById(id);
|
||||
return this.anomalyService.findById(id);
|
||||
}
|
||||
|
||||
@Query(() => [AnomalyObjectType])
|
||||
async anomaliesProto() {
|
||||
return this.anomaliesService.findActiveAnomalies();
|
||||
return this.anomalyService.findAllVisible();
|
||||
}
|
||||
|
||||
@Query(() => PaginatedAnomalyObjectType)
|
||||
async anomalies(@Args() { cursor, take }: PaginationArgs) {
|
||||
const skip = cursor ? decodeOffsetCursor(cursor) : 0;
|
||||
const activeAnomalies = await this.anomaliesService.findActiveAnomalies(
|
||||
const activeAnomalies = await this.anomalyService.findAllVisible(
|
||||
skip,
|
||||
take,
|
||||
[AnomalyTypes.WorkingEmployeeNotInDistributionSystem],
|
||||
);
|
||||
|
||||
const totalCount = await this.anomaliesService.countActiveAnomalies([
|
||||
const totalCount = await this.anomalyService.countVisibleAnomalies([
|
||||
AnomalyTypes.WorkingEmployeeNotInDistributionSystem,
|
||||
]);
|
||||
|
||||
|
|
@ -72,10 +75,7 @@ export class ControlCenterResolver {
|
|||
const skip = cursor ? decodeOffsetCursor(cursor) : 0;
|
||||
|
||||
const { totalCount, anomalies: groupedActiveAnomalies } =
|
||||
await this.anomaliesService.findActiveAnomaliesGroupedByGroupKey(
|
||||
skip,
|
||||
take,
|
||||
);
|
||||
await this.findAnomaliesVisibleGroupedByGroupKey(skip, take);
|
||||
|
||||
const prevPage = skip - take < 0 ? 0 : skip - take;
|
||||
const nextPage = skip + take < totalCount ? skip + take : skip;
|
||||
|
|
@ -91,24 +91,68 @@ export class ControlCenterResolver {
|
|||
|
||||
@Mutation(() => AnomalyObjectType)
|
||||
async setAnomalyToSleep(@Args({ name: 'id' }) anomalyId: string) {
|
||||
return this.anomaliesService.setAnomalyToSleep(anomalyId);
|
||||
return this.anomalyService.snoozeAnomaly(anomalyId);
|
||||
}
|
||||
|
||||
@Mutation(() => AnomalyObjectType)
|
||||
async acceptAnomaly(@Args({ name: 'id' }) anomalyId: string) {
|
||||
return this.anomaliesService.acceptAnomaly(anomalyId);
|
||||
return this.anomalyService.acceptAnomaly(anomalyId);
|
||||
}
|
||||
|
||||
@ResolveField(() => TourObjectType, { nullable: true })
|
||||
tour(@Parent() anomaly: AnomalyObjectType) {
|
||||
const [operationId] = anomaly.groupKey.split('-').reverse();
|
||||
return this.anomaliesService.findTourByOperationId(operationId);
|
||||
}
|
||||
//! Used when creating anomaly list
|
||||
// @ResolveField(() => TourObjectType, { nullable: true })
|
||||
// tour(@Parent() anomaly: AnomalyObjectType) {
|
||||
// const [operationId] = anomaly.groupKey.split('-').reverse();
|
||||
// return this.anomaliesService.findTourByOperationId(operationId);
|
||||
// }
|
||||
|
||||
@ResolveField(() => [SolutionTargetObjectType])
|
||||
solution(
|
||||
@Parent() anomaly: AnomalyObjectType,
|
||||
): Promise<SolutionTargetObjectType[]> {
|
||||
return this.anomaliesService.findSolutionTargetsByAnomalyId(anomaly.id);
|
||||
return this.anomalyService.findSolutionTargetsByAnomalyId(anomaly.id);
|
||||
}
|
||||
|
||||
private async findAnomaliesVisibleGroupedByGroupKey(
|
||||
skip = 0,
|
||||
take = 10,
|
||||
): Promise<{
|
||||
totalCount: number;
|
||||
anomalies: { key: string; anomalies: Anomaly[] }[];
|
||||
}> {
|
||||
const activeAnomalies = await this.anomalyService.findAllVisible(
|
||||
//! 2025-06-11: This is intended, we reduce the response at the end
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
AnomalyTypes.DriverCertificate,
|
||||
AnomalyTypes.EmployeesLoggedIn,
|
||||
AnomalyTypes.OneTourAtATime,
|
||||
],
|
||||
);
|
||||
|
||||
const groupedAnomalies = this.groupAnomaliesByGroupKey(activeAnomalies);
|
||||
|
||||
return {
|
||||
totalCount: Object.keys(groupedAnomalies).length,
|
||||
anomalies: Object.entries(groupedAnomalies)
|
||||
.map(([key, anomalies]) => ({
|
||||
key,
|
||||
anomalies,
|
||||
}))
|
||||
.slice(skip, skip + take),
|
||||
};
|
||||
}
|
||||
|
||||
private groupAnomaliesByGroupKey(
|
||||
anomalies: Anomaly[],
|
||||
): Record<string, Anomaly[]> {
|
||||
return anomalies.reduce(
|
||||
(grouped, anomaly) => ({
|
||||
...grouped,
|
||||
[anomaly.groupKey]: [...(grouped[anomaly.groupKey] ?? []), anomaly],
|
||||
}),
|
||||
{} as Record<string, Anomaly[]>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { AnomaliesService } from '../feat-anomalies/anomalies.service';
|
||||
import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomaly.service';
|
||||
import {
|
||||
SolutionTargetObjectType,
|
||||
SolutionTargetStepObjectType,
|
||||
|
|
@ -8,13 +8,13 @@ import {
|
|||
|
||||
@Resolver(() => SolutionTargetObjectType)
|
||||
export class SolutionTargetsResolver {
|
||||
constructor(private readonly anomaliesService: AnomaliesService) {}
|
||||
constructor(private readonly anomalyService: AnomalyService) {}
|
||||
|
||||
@ResolveField(() => [SolutionTargetStepObjectType!]!)
|
||||
steps(
|
||||
@Parent() solutionTarget: SolutionTargetObjectType,
|
||||
): Promise<SolutionTargetStepObjectType[]> {
|
||||
return this.anomaliesService.findSolutionTargetStepsBySolutionTargetId(
|
||||
return this.anomalyService.findSolutionTargetStepsBySolutionTargetId(
|
||||
solutionTarget.id,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { HomeResolver } from './home.resolver';
|
||||
import { HomeService } from './home.service';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
imports: [PrismaModule, FeatBusinessObjectsModule],
|
||||
providers: [HomeResolver, HomeService],
|
||||
})
|
||||
export class AppHomeModule {}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ 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 { AnomalyTypes } from '../feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomaly.service';
|
||||
import {
|
||||
HomeDashboardKPIsObjectType,
|
||||
RevenueTrendObjectType,
|
||||
|
|
@ -10,7 +11,10 @@ import {
|
|||
|
||||
@Injectable()
|
||||
export class HomeService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly anomalyService: AnomalyService,
|
||||
) {}
|
||||
|
||||
async homeDashboardKpis(): Promise<HomeDashboardKPIsObjectType> {
|
||||
const anomalyCount = await this.getAnomalyCount();
|
||||
|
|
@ -39,20 +43,13 @@ export class HomeService {
|
|||
}
|
||||
|
||||
private async getAnomalyCount(): Promise<number> {
|
||||
return await this.prisma.anomaly.count({
|
||||
where: {
|
||||
resolvedAt: null,
|
||||
},
|
||||
});
|
||||
return await this.anomalyService.countVisibleAnomalies();
|
||||
}
|
||||
|
||||
private async getEmployeesWithoutTourCount(): Promise<number> {
|
||||
return await this.prisma.anomaly.count({
|
||||
where: {
|
||||
type: AnomalyTypes.WorkingEmployeeNotInDistributionSystem,
|
||||
resolvedAt: null,
|
||||
},
|
||||
});
|
||||
return await this.anomalyService.countVisibleAnomalies([
|
||||
AnomalyTypes.WorkingEmployeeNotInDistributionSystem,
|
||||
]);
|
||||
}
|
||||
|
||||
private async getToursWithoutApprovalCount(): Promise<number> {
|
||||
|
|
@ -380,7 +377,7 @@ export class HomeService {
|
|||
AND: [
|
||||
{ ordinanceType: { not: null } },
|
||||
{ ordinanceType: { not: 'Tragehilfe' } },
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ import {
|
|||
isSameDay,
|
||||
startOfDay,
|
||||
} from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
|
||||
|
||||
@Injectable()
|
||||
export class TourAmountForecastService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(private readonly tourService: TourService) {}
|
||||
|
||||
calculateTourAmountForecastFourWeekPeriod(
|
||||
ordinanceType: string | undefined,
|
||||
|
|
@ -150,94 +150,9 @@ export class TourAmountForecastService {
|
|||
interval: Interval,
|
||||
ordinanceType: string | undefined,
|
||||
) {
|
||||
const ordinanceTypeToQuery = ordinanceType.split(' ')[0];
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
...(ordinanceType
|
||||
? [
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
ordinanceType: ordinanceTypeToQuery,
|
||||
},
|
||||
...(ordinanceType.indexOf('TSW') > -1
|
||||
? [
|
||||
{
|
||||
transportType:
|
||||
ordinanceType.indexOf('1') > -1 ? 'TS1' : 'TS2',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return this.tourService.findInInterval(
|
||||
interval,
|
||||
ordinanceType ? [ordinanceType] : [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,14 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { TicketLifecycleService } from './ticket-lifecycle.service';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
import { FeatTicketSystemModule } from '../feat-tickets/ticket-system.module';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
import { TicketsResolver } from './tickets.resolver';
|
||||
import { TicketService } from './tickets.service';
|
||||
import { TicketSystemService } from './ticket-system.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [
|
||||
TicketRepository,
|
||||
TicketService,
|
||||
TicketLifecycleService,
|
||||
TicketsResolver,
|
||||
],
|
||||
exports: [TicketService],
|
||||
imports: [PrismaModule, FeatTicketSystemModule, FeatBusinessObjectsModule],
|
||||
providers: [TicketRepository, TicketSystemService, TicketsResolver],
|
||||
exports: [TicketSystemService],
|
||||
})
|
||||
export class ApplicationTicketSystemModule {}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ArgsType, Field, InputType } from '@nestjs/graphql';
|
||||
import { ApprovalState, TicketValidationState } from '@prisma/client';
|
||||
import { PaginationArgs } from 'src/core/base/pagination.input';
|
||||
import { TicketFilter } from './ticket-filter';
|
||||
import { TicketFilter } from '../feat-business-objects/entities/ticket/ticket-filter';
|
||||
|
||||
@InputType()
|
||||
export class TicketFilterArgs implements TicketFilter {
|
||||
|
|
|
|||
|
|
@ -1,266 +0,0 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
ApprovalState,
|
||||
Ticket,
|
||||
TicketValidationState,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { DocumentInfoArgs } from './ticket-arg.types';
|
||||
import {
|
||||
ValidationResult,
|
||||
validatePatient,
|
||||
validatePatientDataOnTours,
|
||||
validateTour,
|
||||
} from './ticket-validation.functions';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
|
||||
@Injectable()
|
||||
export class TicketLifecycleService {
|
||||
private readonly logger = new Logger(TicketLifecycleService.name);
|
||||
|
||||
constructor(private readonly repository: TicketRepository) {}
|
||||
|
||||
async createTickets(interval: Interval): Promise<void> {
|
||||
const tours = await this.repository.findTours(interval);
|
||||
|
||||
for (const tour of tours) {
|
||||
if (tour.ticketId) {
|
||||
continue;
|
||||
}
|
||||
const ticketId = await this.repository.searchTicketIDForTour(tour);
|
||||
if (ticketId) {
|
||||
await this.repository.addToursToTicket(ticketId, [tour]);
|
||||
} else {
|
||||
await this.repository.createTicket([tour]);
|
||||
}
|
||||
}
|
||||
|
||||
await this.validateCreatedTickets();
|
||||
// await this.archiveClosedTickets();
|
||||
}
|
||||
|
||||
async validateDocumentInfoUpdate(
|
||||
ticketId: string,
|
||||
documentInfo: DocumentInfoArgs,
|
||||
) {
|
||||
const ticket = await this.repository.findById(ticketId);
|
||||
|
||||
switch (ticket.currentState) {
|
||||
case TicketValidationState.USER_INTERACTION_REQUIRED:
|
||||
const approvalStateUpdate = documentInfo.approval.isNeeded
|
||||
? ApprovalState.REQUESTED
|
||||
: ApprovalState.FREE;
|
||||
|
||||
let stateUpdate: TicketValidationState =
|
||||
TicketValidationState.USER_INTERACTION_REQUIRED;
|
||||
if (
|
||||
documentInfo.tdLocation === 'company' &&
|
||||
approvalStateUpdate === ApprovalState.FREE
|
||||
)
|
||||
stateUpdate = TicketValidationState.ARCHIVED;
|
||||
else if (documentInfo.tdLocation === 'uncertain')
|
||||
stateUpdate = TicketValidationState.TD_UNCERTAIN;
|
||||
else stateUpdate = TicketValidationState.DOCUMENTS_MISSING;
|
||||
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
documentInfo,
|
||||
approvalState: approvalStateUpdate,
|
||||
state: stateUpdate,
|
||||
});
|
||||
|
||||
case TicketValidationState.TD_UNCERTAIN:
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
documentInfo,
|
||||
state:
|
||||
ticket.approvalState === ApprovalState.FREE &&
|
||||
documentInfo.tdLocation === 'company'
|
||||
? TicketValidationState.ARCHIVED
|
||||
: TicketValidationState.DOCUMENTS_MISSING,
|
||||
});
|
||||
case TicketValidationState.DOCUMENTS_MISSING:
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
documentInfo,
|
||||
...((ticket.approvalState === ApprovalState.FREE ||
|
||||
documentInfo.approval.location === 'company') &&
|
||||
documentInfo.tdLocation === 'company'
|
||||
? { state: TicketValidationState.ARCHIVED }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async validateCreatedTickets(): Promise<void> {
|
||||
const tickets = await this.repository.findAllCreated();
|
||||
|
||||
for (const ticket of tickets) {
|
||||
const {
|
||||
result: isValid,
|
||||
errors,
|
||||
notes,
|
||||
} = await this.validateCreatedTicket(ticket);
|
||||
|
||||
if (isValid) {
|
||||
const { result: approvalResult } = await this.isApprovalNeeded(ticket);
|
||||
if (approvalResult) {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
approvalState: ticket.tours.every(({ patientId }) => !patientId)
|
||||
? ApprovalState.UNKNOWN
|
||||
: ApprovalState.REQUIRED,
|
||||
});
|
||||
} else {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async archiveClosedTickets() {
|
||||
const closedTickets = await this.repository.findAllClosed();
|
||||
for (const closedTicket of closedTickets) {
|
||||
await this.repository.setStateOfTicket(
|
||||
closedTicket.id,
|
||||
TicketValidationState.ARCHIVED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateCreatedTicket(
|
||||
ticket: Ticket & { tours: Tour[] },
|
||||
): Promise<ValidationResult> {
|
||||
const patientIds = [
|
||||
...new Set(ticket.tours.map(({ patientId }) => patientId)),
|
||||
];
|
||||
if (patientIds.length > 1) {
|
||||
return {
|
||||
result: false,
|
||||
errors: ['Ticket: Die Touren umfassen mehr als einen Patienten'],
|
||||
notes: [],
|
||||
};
|
||||
}
|
||||
const [patientId] = patientIds;
|
||||
if (!patientId && ticket.tours.at(0)?.type !== 'Spontan') {
|
||||
return {
|
||||
result: false,
|
||||
errors: [],
|
||||
notes: ['Patient: Der Patient ist nicht in DispoLive hinterlegt'],
|
||||
};
|
||||
}
|
||||
|
||||
let patientResult: ValidationResult;
|
||||
if (!patientId && ticket.tours.at(0)?.type === 'Spontan') {
|
||||
patientResult = validatePatientDataOnTours(ticket.tours);
|
||||
} else {
|
||||
try {
|
||||
const patient = await this.repository.findPatientById(patientId);
|
||||
patientResult = validatePatient(patient);
|
||||
} catch {
|
||||
patientResult = validatePatientDataOnTours(ticket.tours);
|
||||
}
|
||||
}
|
||||
const tourResults = ticket.tours.map((tour) => validateTour(tour));
|
||||
|
||||
return [patientResult, ...tourResults].reduce(
|
||||
(result, results) => ({
|
||||
result: result.result && results.result,
|
||||
errors: [...result.errors, ...results.errors],
|
||||
notes: [...result.notes, ...results.notes],
|
||||
}),
|
||||
{ result: true, errors: [], notes: [] } as ValidationResult,
|
||||
);
|
||||
}
|
||||
|
||||
private async isApprovalNeeded(
|
||||
ticket: Ticket & { tours: Tour[] },
|
||||
): Promise<ValidationResult> {
|
||||
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,
|
||||
);
|
||||
|
||||
return {
|
||||
result: isApprovalRequired,
|
||||
errors: [],
|
||||
notes:
|
||||
isApprovalRequired && ticket.tours.every(({ patientId }) => !patientId)
|
||||
? [
|
||||
'Patient nicht in Stammdaten: Prüfen Sie ob eine Genehmigung benötigt wird',
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
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',
|
||||
'Entlassung',
|
||||
'Entlassung Vorbestellung',
|
||||
'Konsil KH - Dia',
|
||||
'Privatfahrt Rechnung',
|
||||
'Tagesklinik',
|
||||
'Verlegung',
|
||||
].includes(tour.category) ||
|
||||
tour.healthInsurance === 'KOSTENTRÄGER PATIENT'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
tour.patientId &&
|
||||
['BTW', 'TSW'].includes(tour.ordinanceType) &&
|
||||
tour.category !== 'Dialyse'
|
||||
) {
|
||||
const patient = await this.repository.findPatientById(tour.patientId);
|
||||
if (
|
||||
['3', '4', '5'].includes(patient.careDegree) &&
|
||||
(['aG', 'Bi', 'H'].includes(patient.disabilityMark1) ||
|
||||
['aG', 'Bi', 'H'].includes(patient.disabilityMark2))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
export enum TicketSystemEventTypes {
|
||||
Validated = 'ticket-system.validated',
|
||||
}
|
||||
|
||||
export class TicketSystemValidatedEvent {
|
||||
constructor(public readonly count: number) {}
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { Ticket, TicketValidationState, Tour } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { PubSub } from 'graphql-subscriptions';
|
||||
import {
|
||||
decodeOffsetCursor,
|
||||
encodeCursor,
|
||||
} from 'src/core/base/pagination.util';
|
||||
import { DocumentInfoArgs, TicketFilterArgs } from './ticket-arg.types';
|
||||
import { TicketLifecycleService } from './ticket-lifecycle.service';
|
||||
import { TicketService } from '../feat-business-objects/entities/ticket/ticket.service';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
|
||||
import { TicketLifecycleService } from '../feat-tickets/ticket-lifecycle.service';
|
||||
import {
|
||||
TicketSystemEventTypes,
|
||||
TicketSystemValidatedEvent,
|
||||
} from './ticket-system.events';
|
||||
} from '../feat-tickets/ticket-system.events';
|
||||
import { DocumentInfoArgs, TicketFilterArgs } from './ticket-arg.types';
|
||||
import {
|
||||
PaginatedTicketObjectType,
|
||||
TicketStateKeyFiguresObjectType,
|
||||
|
|
@ -21,7 +22,7 @@ import {
|
|||
import { TicketRepository } from './ticket.repository';
|
||||
|
||||
@Injectable()
|
||||
export class TicketService {
|
||||
export class TicketSystemService {
|
||||
public readonly pubSub = new PubSub();
|
||||
|
||||
@OnEvent(TicketSystemEventTypes.Validated, { async: true })
|
||||
|
|
@ -33,33 +34,25 @@ export class TicketService {
|
|||
|
||||
constructor(
|
||||
private readonly repository: TicketRepository,
|
||||
private readonly ticketService: TicketService,
|
||||
private readonly tourService: TourService,
|
||||
private readonly lifecycle: TicketLifecycleService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
async createTickets(interval: Interval): Promise<void> {
|
||||
await this.lifecycle.createTickets(interval);
|
||||
|
||||
this.eventEmitter.emit(
|
||||
TicketSystemEventTypes.Validated,
|
||||
new TicketSystemValidatedEvent(0),
|
||||
);
|
||||
}
|
||||
|
||||
async updateDocumentInfo(ticketId: string, documentInfo: DocumentInfoArgs) {
|
||||
return this.lifecycle.validateDocumentInfoUpdate(ticketId, documentInfo);
|
||||
}
|
||||
|
||||
findById(id: string): Promise<Ticket> {
|
||||
return this.repository.findById(id);
|
||||
return this.ticketService.findById(id);
|
||||
}
|
||||
|
||||
findAll(): Promise<Ticket[]> {
|
||||
return this.repository.findAll() as Promise<Ticket[]>;
|
||||
return this.ticketService.findAll() as Promise<Ticket[]>;
|
||||
}
|
||||
|
||||
searchTickets(query: string): Promise<Ticket[]> {
|
||||
return this.repository.search(query);
|
||||
return this.ticketService.search(query);
|
||||
}
|
||||
|
||||
async paginateTicketsByState(
|
||||
|
|
@ -69,7 +62,7 @@ export class TicketService {
|
|||
cursor?: string,
|
||||
): Promise<PaginatedTicketObjectType> {
|
||||
const skip = cursor ? decodeOffsetCursor(cursor) : 0;
|
||||
const { tickets, total } = await this.repository.findByState(
|
||||
const { tickets, total } = await this.ticketService.findByState(
|
||||
state,
|
||||
filter,
|
||||
take,
|
||||
|
|
@ -88,12 +81,14 @@ export class TicketService {
|
|||
};
|
||||
}
|
||||
|
||||
// TODO: obsolete
|
||||
async getTicketStateMeta(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<TicketStateMetaObjectType> {
|
||||
return this.repository.calculateTicketStateMeta(ticketState);
|
||||
}
|
||||
|
||||
// TODO: obsolete
|
||||
getTicketStateKeyFigures(
|
||||
ticketState: TicketValidationState,
|
||||
): Promise<TicketStateKeyFiguresObjectType> {
|
||||
|
|
@ -101,12 +96,12 @@ export class TicketService {
|
|||
}
|
||||
|
||||
findToursByTicketId(ticketId: string): Promise<Tour[]> {
|
||||
return this.repository.findToursByTicketId(ticketId);
|
||||
return this.tourService.findByTicketId(ticketId);
|
||||
}
|
||||
|
||||
findStatesByTicketId(ticketId: string): Promise<TicketValidationState[]> {
|
||||
return this.repository
|
||||
.findStatesByTicketId(ticketId)
|
||||
return this.ticketService
|
||||
.findStateHistoryByTicketId(ticketId)
|
||||
.then((states) => states.map(({ state }) => state));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +0,0 @@
|
|||
import { Patient, Tour } from '@prisma/client';
|
||||
|
||||
export interface ValidationResult {
|
||||
result: boolean;
|
||||
notes: string[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
export function validatePatient(patient: Patient): ValidationResult {
|
||||
const notes: string[] = [];
|
||||
const errors: string[] = [];
|
||||
let isValid = true;
|
||||
|
||||
if (!patient?.name) {
|
||||
isValid = false;
|
||||
errors.push('Patient: Vorname ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.surname) {
|
||||
isValid = false;
|
||||
errors.push('Patient: Nachname ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.birthday) {
|
||||
isValid = false;
|
||||
notes.push('Patient: Geburtstag ist nicht gefüllt');
|
||||
}
|
||||
if (!patient?.healthinsurance) {
|
||||
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');
|
||||
// }
|
||||
|
||||
return {
|
||||
result: isValid,
|
||||
errors,
|
||||
notes,
|
||||
};
|
||||
}
|
||||
|
||||
export function validatePatientDataOnTours(tours: Tour[]): ValidationResult {
|
||||
const errors: string[] = [];
|
||||
let isValid = true;
|
||||
|
||||
for (const tour of tours) {
|
||||
if (!tour.patientName) {
|
||||
isValid = false;
|
||||
errors.push(
|
||||
`Tour ${tour.operationId}: Patient Vorname ist nicht gefüllt`,
|
||||
);
|
||||
}
|
||||
if (!tour.patientSurname) {
|
||||
isValid = false;
|
||||
errors.push(
|
||||
`Tour ${tour.operationId}: Patient Nachname ist nicht gefüllt`,
|
||||
);
|
||||
}
|
||||
if (!tour.healthInsurance) {
|
||||
isValid = false;
|
||||
errors.push(
|
||||
`Tour ${tour.operationId}: Patient Krankenversicherung ist nicht gefüllt`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
result: isValid,
|
||||
errors,
|
||||
notes: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function validateTour(tour: Tour): ValidationResult {
|
||||
const errors: string[] = [];
|
||||
let isValid = true;
|
||||
|
||||
if (!tour.ordinanceType) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Verordnungsart ist nicht gefüllt`);
|
||||
}
|
||||
if (!tour.transportType) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Transportart ist nicht gefüllt`);
|
||||
}
|
||||
if (!tour.category) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Kategorie ist nicht gefüllt`);
|
||||
}
|
||||
if (!tour.startDate) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Startzeit ist nicht gefüllt`);
|
||||
}
|
||||
if (!tour.startStreet || !tour.startZip || !tour.startCity) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Start Adresse ist nicht gefüllt`);
|
||||
}
|
||||
if (!tour.targetStreet || !tour.targetZip || !tour.targetCity) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Ziel Adresse ist nicht gefüllt`);
|
||||
}
|
||||
if (tour.occupiedKm > 9990) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Besetzte Kilometer sind zu hoch`);
|
||||
}
|
||||
|
||||
return {
|
||||
result: isValid,
|
||||
errors,
|
||||
notes: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function validateTransport(tour: Tour): ValidationResult {
|
||||
const errors: string[] = [];
|
||||
let isValid = true;
|
||||
|
||||
if (!tour.carName) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Fahrzeugname ist nicht angegeben`);
|
||||
}
|
||||
|
||||
if (!tour.driverName) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Fahrer ist nicht angegeben`);
|
||||
}
|
||||
|
||||
if (!tour.codriverName) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Beifahrer ist nicht angegeben`);
|
||||
}
|
||||
|
||||
if (!tour.startBegin) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Anfahrt-Stempel fehlt`);
|
||||
}
|
||||
|
||||
if (!tour.empty) {
|
||||
isValid = false;
|
||||
errors.push(`Tour ${tour.operationId}: Frei-Stempel fehlt`);
|
||||
}
|
||||
|
||||
return {
|
||||
result: isValid,
|
||||
errors,
|
||||
notes: [],
|
||||
};
|
||||
}
|
||||
|
|
@ -1,284 +1,17 @@
|
|||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
ApprovalState,
|
||||
Patient,
|
||||
Prisma,
|
||||
TicketValidationState,
|
||||
Tour,
|
||||
TicketValidationState
|
||||
} from '@prisma/client';
|
||||
import {
|
||||
differenceInBusinessDays,
|
||||
endOfDay,
|
||||
Interval,
|
||||
startOfDay,
|
||||
} from 'date-fns';
|
||||
import { ExpressionBuilder, Kysely } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module';
|
||||
import { differenceInBusinessDays } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { DB } from 'src/core/database/types';
|
||||
import { DocumentInfoArgs, TicketFilterArgs } from './ticket-arg.types';
|
||||
import { createPrismaQueryFromTicketFilters } from './ticket-filter';
|
||||
|
||||
function withTours(eb: ExpressionBuilder<DB, 'Ticket'>) {
|
||||
return jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('Tour')
|
||||
.selectAll('Tour')
|
||||
.whereRef('Tour.ticketId', '=', 'Ticket.id')
|
||||
.orderBy('Tour.operationId'),
|
||||
).as('tours');
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TicketRepository {
|
||||
private logger = new Logger(TicketRepository.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(KYSELY_CLIENT_PROVIDER_TOKEN) private readonly db: Kysely<DB>,
|
||||
) {}
|
||||
|
||||
async createTicket(tours: Tour[]) {
|
||||
const ticket = await this.db
|
||||
.insertInto('Ticket')
|
||||
.values({
|
||||
currentState: TicketValidationState.CREATED,
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
await this.db
|
||||
.insertInto('TicketStateHistory')
|
||||
.values({
|
||||
state: TicketValidationState.CREATED,
|
||||
ticketId: ticket.id,
|
||||
})
|
||||
.executeTakeFirst();
|
||||
|
||||
await this.db
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
ticketId: ticket.id,
|
||||
})
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
tours.map(({ id }) => id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
async addToursToTicket(ticketId: string, tours: Tour[]) {
|
||||
await this.db
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
ticketId: ticketId,
|
||||
})
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
tours.map(({ id }) => id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return this.db
|
||||
.selectFrom('Ticket')
|
||||
.where('id', '=', ticketId)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
setActionablesOnTicket(ticketId: string, errors: string[], notes: string[]) {
|
||||
return this.db
|
||||
.updateTable('Ticket')
|
||||
.set({ errors, notes })
|
||||
.where('id', '=', ticketId)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async setStateOfTicket(ticketId: string, state: TicketValidationState) {
|
||||
await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({
|
||||
currentState: state,
|
||||
})
|
||||
.where('Ticket.id', '=', ticketId)
|
||||
.execute();
|
||||
|
||||
return this.db
|
||||
.insertInto('TicketStateHistory')
|
||||
.values({ state, ticketId })
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async updateTicket(
|
||||
ticketId: string,
|
||||
data: {
|
||||
state?: TicketValidationState;
|
||||
errors?: string[];
|
||||
notes?: string[];
|
||||
isUrgent?: boolean;
|
||||
urgency?: Date;
|
||||
approvalState?: ApprovalState;
|
||||
documentInfo?: DocumentInfoArgs;
|
||||
approvalPath?: string;
|
||||
},
|
||||
) {
|
||||
const { state, ...ticketData } = data;
|
||||
|
||||
await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({ ...ticketData })
|
||||
.where('id', '=', ticketId)
|
||||
.execute();
|
||||
|
||||
if (state) {
|
||||
await this.setStateOfTicket(ticketId, state);
|
||||
}
|
||||
|
||||
return this.db
|
||||
.selectFrom('Ticket')
|
||||
.where('id', '=', ticketId)
|
||||
.selectAll('Ticket')
|
||||
.select((eb) => withTours(eb))
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
findById(id: string) {
|
||||
return this.prisma.ticket.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.ARCHIVED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAllCreated() {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAllClosed() {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
tours: {
|
||||
every: {
|
||||
check: {
|
||||
not: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
search(query: string) {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.ARCHIVED,
|
||||
},
|
||||
},
|
||||
tours: {
|
||||
some: {
|
||||
OR: [
|
||||
...query.split(' ').flatMap((word) => [
|
||||
{
|
||||
patientName: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
patientSurname: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
take: 5,
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByState(
|
||||
state: TicketValidationState,
|
||||
filter: TicketFilterArgs,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
) {
|
||||
const query: Prisma.TicketFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
AND: createPrismaQueryFromTicketFilters(filter),
|
||||
currentState: state,
|
||||
tours: {
|
||||
some: {
|
||||
check: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
|
||||
const [tickets, count] = await this.prisma.$transaction([
|
||||
this.prisma.ticket.findMany({ ...query }),
|
||||
this.prisma.ticket.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tickets,
|
||||
total: count,
|
||||
};
|
||||
}
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async calculateTicketStateMeta(ticketState: TicketValidationState) {
|
||||
const ticketWithCurrentStateAndTours: Prisma.TicketWhereInput = {
|
||||
|
|
@ -350,6 +83,7 @@ export class TicketRepository {
|
|||
|
||||
async calculateTicketStateKeyFigures(ticketState: TicketValidationState) {
|
||||
const ticketWithCurrentStateAndTours: Prisma.TicketWhereInput = {
|
||||
currentState: ticketState,
|
||||
tours: { some: { check: 0 } },
|
||||
};
|
||||
|
||||
|
|
@ -388,231 +122,4 @@ export class TicketRepository {
|
|||
dwellTimesForTicketState.length,
|
||||
};
|
||||
}
|
||||
|
||||
findPatientById(patientId: string): Promise<Patient> {
|
||||
return this.prisma.patient.findUnique({ where: { id: patientId } });
|
||||
}
|
||||
|
||||
findStatesByTicketId(
|
||||
ticketId: string,
|
||||
): Promise<{ state: TicketValidationState }[]> {
|
||||
return this.prisma.ticketStateHistory.findMany({
|
||||
where: {
|
||||
ticketId,
|
||||
},
|
||||
select: {
|
||||
state: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findToursByTicketId(ticketId: string): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
ticketId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async searchTicketIDForTour(tour: Tour): Promise<string | undefined> {
|
||||
const tours = await Promise.all([
|
||||
this.findFirstRecurringTour(tour),
|
||||
this.findTourOnSameDayWithOppositeDirection(tour),
|
||||
]);
|
||||
const firstFoundTour = tours.filter((tour) => !!tour)[0];
|
||||
if (firstFoundTour) {
|
||||
const ticket = await this.prisma.ticket.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
tours: {
|
||||
some: {
|
||||
id: firstFoundTour.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return ticket?.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findFirstRecurringTour(tour: Tour): Promise<Tour | null> {
|
||||
const institutionStreet =
|
||||
tour.direction === 'Hinfahrt' ? tour.targetStreet : tour.startStreet;
|
||||
return this.prisma.tour.findFirst({
|
||||
where: {
|
||||
type: 'Serie',
|
||||
category: tour.category,
|
||||
ordinanceType: tour.ordinanceType,
|
||||
patientId: tour.patientId,
|
||||
patientName: tour.patientName,
|
||||
patientSurname: tour.patientSurname,
|
||||
patientStreet: tour.patientStreet,
|
||||
patientCity: tour.patientCity,
|
||||
patientZip: tour.patientZip,
|
||||
OR: [
|
||||
{ targetStreet: institutionStreet },
|
||||
{ startStreet: institutionStreet },
|
||||
],
|
||||
rangeEndDate: {
|
||||
gte: tour.startDate,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findTourOnSameDayWithOppositeDirection(tour: Tour): Promise<Tour | null> {
|
||||
return this.prisma.tour.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gt: startOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: endOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startStreet: tour.targetStreet,
|
||||
},
|
||||
{
|
||||
targetStreet: tour.startStreet,
|
||||
},
|
||||
{
|
||||
direction: tour.direction === 'Hinfahrt' ? 'Rückfahrt' : 'Hinfahrt',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findToursWithTicket(interval: Interval): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeTourFromTicket(tourId: string): Promise<Tour> {
|
||||
return this.prisma.tour.update({
|
||||
where: {
|
||||
id: tourId,
|
||||
},
|
||||
data: {
|
||||
Ticket: {
|
||||
disconnect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findTours(interval: Interval): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
ticketId: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEmptyTickets(): Promise<Prisma.BatchPayload> {
|
||||
return this.prisma.ticket.deleteMany({
|
||||
where: {
|
||||
tours: {
|
||||
none: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ import {
|
|||
TicketStateMetaObjectType,
|
||||
TicketsUpdatedObjectType,
|
||||
} from './ticket.object-type';
|
||||
import { TicketService } from './tickets.service';
|
||||
import { TicketSystemService } from './ticket-system.service';
|
||||
|
||||
@Resolver(() => TicketObjectType)
|
||||
export class TicketsResolver {
|
||||
constructor(private readonly ticketsService: TicketService) {}
|
||||
constructor(private readonly ticketsService: TicketSystemService) {}
|
||||
|
||||
@Query(() => TicketObjectType)
|
||||
ticket(@Args('id') id: string) {
|
||||
|
|
|
|||
|
|
@ -1,31 +1,23 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
Anomaly,
|
||||
Prisma,
|
||||
SolutionTarget,
|
||||
SolutionTargetStep,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import { Anomaly } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
|
||||
import { TraceTypes } from 'src/core/base/trace.types';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { AnomalyDetectorService } from './anomaly-detectors/anomaly-detector.service';
|
||||
import { AnomalyTypes } from './anomaly-detectors/anomaly-types.enum';
|
||||
import { DriverCertificateDetector } from './anomaly-detectors/driver-certificate.detector';
|
||||
import { DriverCodriverSameDetector } from './anomaly-detectors/driver-codriver-same.detector';
|
||||
import { EmployeesLoggedInDetector } from './anomaly-detectors/employees-logged-in.detector';
|
||||
// import { OneTourAtATimeDetector } from './anomaly-detectors/one-tour-at-a-time.detector';
|
||||
import { WorkingEmployeeNotInDistributionSystemDetector } from './anomaly-detectors/working-employee-not-in-distribution-system.detector';
|
||||
import { AnomalyCreateDto } from '../feat-business-objects/entities/anomaly/anomalies.dto';
|
||||
import { AnomalyService } from '../feat-business-objects/entities/anomaly/anomaly.service';
|
||||
import { AnomalyDetector } from './anomaly-detectors/anomaly-detector.class';
|
||||
import { WorkingEmployeeNotInDistributionSystemDetector } from './anomaly-detectors/working-employee-not-in-distribution-system.detector';
|
||||
|
||||
@Injectable()
|
||||
export class AnomaliesService {
|
||||
private detectors: AnomalyDetector[];
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly anomalyDetectorService: AnomalyDetectorService,
|
||||
private readonly anomalyService: AnomalyService,
|
||||
) {
|
||||
this.detectors = [
|
||||
new EmployeesLoggedInDetector(this.anomalyDetectorService),
|
||||
|
|
@ -47,15 +39,9 @@ export class AnomaliesService {
|
|||
for (const anomaly of anomalies) {
|
||||
if (anomaly) {
|
||||
anomalyEntities.push(
|
||||
await this.prisma.anomaly.upsert({
|
||||
where: {
|
||||
id: anomaly.id,
|
||||
},
|
||||
create: {
|
||||
...anomaly,
|
||||
},
|
||||
update: {},
|
||||
}),
|
||||
await this.anomalyService.createEvenIfExists(
|
||||
anomaly as AnomalyCreateDto,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -64,11 +50,7 @@ export class AnomaliesService {
|
|||
}
|
||||
|
||||
async validateAnomalies(): Promise<Anomaly[]> {
|
||||
const anomalies = await this.prisma.anomaly.findMany({
|
||||
where: {
|
||||
resolvedAt: null,
|
||||
},
|
||||
});
|
||||
const anomalies = await this.anomalyService.findAllUnsolved();
|
||||
|
||||
return Promise.all(
|
||||
anomalies.map((anomaly) =>
|
||||
|
|
@ -81,171 +63,10 @@ export class AnomaliesService {
|
|||
const updatedAnomalies: Anomaly[] = [];
|
||||
for (const anomaly of validatedAnomalies) {
|
||||
updatedAnomalies.push(
|
||||
await this.prisma.anomaly.update({
|
||||
where: {
|
||||
id: anomaly.id,
|
||||
},
|
||||
data: {
|
||||
resolvedAt: anomaly.resolvedAt,
|
||||
manuallyResolved: false,
|
||||
},
|
||||
}),
|
||||
await this.anomalyService.resolveAnomaly(anomaly.id),
|
||||
);
|
||||
}
|
||||
return updatedAnomalies;
|
||||
});
|
||||
}
|
||||
|
||||
countActiveAnomalies(types: AnomalyTypes[] = []): Promise<number> {
|
||||
return this.prisma.anomaly.count({
|
||||
where: this.getWhereActiveAnomalyQuery(types),
|
||||
});
|
||||
}
|
||||
|
||||
findActiveAnomalies(
|
||||
skip?: number,
|
||||
take?: number,
|
||||
types: AnomalyTypes[] = [],
|
||||
): Promise<Anomaly[]> {
|
||||
return this.prisma.anomaly.findMany({
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: this.getWhereActiveAnomalyQuery(types),
|
||||
orderBy: {
|
||||
groupKey: 'asc',
|
||||
},
|
||||
include: {
|
||||
solution: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findActiveAnomaliesGroupedByGroupKey(
|
||||
skip = 0,
|
||||
take = 10,
|
||||
): Promise<{
|
||||
totalCount: number;
|
||||
anomalies: { key: string; anomalies: Anomaly[] }[];
|
||||
}> {
|
||||
const activeAnomalies = await this.findActiveAnomalies(
|
||||
undefined,
|
||||
undefined,
|
||||
[
|
||||
AnomalyTypes.DriverCertificate,
|
||||
AnomalyTypes.EmployeesLoggedIn,
|
||||
AnomalyTypes.OneTourAtATime,
|
||||
],
|
||||
);
|
||||
|
||||
const groupedAnomalies = this.groupAnomaliesByGroupKey(activeAnomalies);
|
||||
|
||||
return {
|
||||
totalCount: Object.keys(groupedAnomalies).length,
|
||||
anomalies: Object.entries(groupedAnomalies)
|
||||
.map(([key, anomalies]) => ({
|
||||
key,
|
||||
anomalies,
|
||||
}))
|
||||
.slice(skip, skip + take),
|
||||
};
|
||||
}
|
||||
|
||||
findById(id: string): Promise<Anomaly> {
|
||||
return this.prisma.anomaly.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findSolutionTargetsByAnomalyId(anomalyId: string): Promise<SolutionTarget[]> {
|
||||
return this.prisma.solutionTarget.findMany({
|
||||
where: {
|
||||
anomalyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findSolutionTargetStepsBySolutionTargetId(
|
||||
solutionTargetId: string,
|
||||
): Promise<SolutionTargetStep[]> {
|
||||
return this.prisma.solutionTargetStep.findMany({
|
||||
where: {
|
||||
solutionTargetId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findTourByOperationId(operationId: string): Promise<Tour> {
|
||||
return this.prisma.tour.findUnique({ where: { operationId } });
|
||||
}
|
||||
|
||||
async setAnomalyToSleep(anomalyId: string): Promise<Anomaly> {
|
||||
await this.prisma.trace.create({
|
||||
data: {
|
||||
entityKey: anomalyId,
|
||||
type: TraceTypes.AnomalyIgnored,
|
||||
},
|
||||
});
|
||||
return this.prisma.anomaly.update({
|
||||
where: {
|
||||
id: anomalyId,
|
||||
},
|
||||
data: {
|
||||
sleepSince: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async acceptAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
await this.prisma.trace.create({
|
||||
data: {
|
||||
entityKey: anomalyId,
|
||||
type: TraceTypes.AnomalyAccepted,
|
||||
},
|
||||
});
|
||||
return this.prisma.anomaly.update({
|
||||
where: {
|
||||
id: anomalyId,
|
||||
},
|
||||
data: {
|
||||
resolvedAt: new Date(),
|
||||
manuallyResolved: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private getWhereActiveAnomalyQuery(
|
||||
types: AnomalyTypes[] = [],
|
||||
): Prisma.AnomalyWhereInput {
|
||||
return {
|
||||
...(types.length ? { type: { in: types } } : {}),
|
||||
resolvedAt: null,
|
||||
OR: [
|
||||
{
|
||||
sleepSince: null,
|
||||
},
|
||||
{
|
||||
sleepSince: {
|
||||
lt: new Date(new Date().getTime() - 60 * 60 * 1000),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private groupAnomaliesByGroupKey(
|
||||
anomalies: Anomaly[],
|
||||
): Record<string, Anomaly[]> {
|
||||
return anomalies.reduce(
|
||||
(grouped, anomaly) => ({
|
||||
...grouped,
|
||||
[anomaly.groupKey]: [...(grouped[anomaly.groupKey] ?? []), anomaly],
|
||||
}),
|
||||
{} as Record<string, Anomaly[]>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { AnomaliesService } from './anomalies.service';
|
||||
import { AnomalyDetectorService } from './anomaly-detectors/anomaly-detector.service';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
imports: [PrismaModule, FeatBusinessObjectsModule],
|
||||
providers: [AnomaliesService, AnomalyDetectorService],
|
||||
exports: [AnomaliesService],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ import {
|
|||
WorkTime,
|
||||
} from '@prisma/client';
|
||||
import { Interval, addDays, addHours, endOfDay, startOfDay } from 'date-fns';
|
||||
import { AttendanceRegistrationEvents } from 'src/core/base/attendance-registration-events.enum';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { AttendanceRegistrationEvents } from 'src/modules/feat-business-objects/entities/attendance-registration/attendance-registration-events.enum';
|
||||
import { AttendanceRegistrationService } from 'src/modules/feat-business-objects/entities/attendance-registration/attendance-registration.service';
|
||||
import { EmployeeService } from 'src/modules/feat-business-objects/entities/employee/employee.service';
|
||||
import { PlannedTimeService } from 'src/modules/feat-business-objects/entities/planned-time/planned-time.service';
|
||||
import { TourService } from 'src/modules/feat-business-objects/entities/tour/tour.service';
|
||||
import { WorkTimeService } from 'src/modules/feat-business-objects/entities/work-time/work-time.service';
|
||||
|
||||
export type AnomalySkeleton = Pick<
|
||||
Prisma.AnomalyCreateInput,
|
||||
|
|
@ -23,7 +27,13 @@ export type AnomalySkeleton = Pick<
|
|||
|
||||
@Injectable()
|
||||
export class AnomalyDetectorService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly attendanceRegistrationService: AttendanceRegistrationService,
|
||||
private readonly employeeService: EmployeeService,
|
||||
private readonly plannedTimeService: PlannedTimeService,
|
||||
private readonly tourService: TourService,
|
||||
private readonly workTimeService: WorkTimeService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Find a list of tours in a given time interval
|
||||
|
|
@ -31,170 +41,26 @@ export class AnomalyDetectorService {
|
|||
* @returns
|
||||
*/
|
||||
public findToursInInterval(
|
||||
{ start, end }: Interval,
|
||||
interval: Interval,
|
||||
ordinanceTypes: string[] = [],
|
||||
): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(end),
|
||||
},
|
||||
},
|
||||
{
|
||||
check: {
|
||||
lte: 2,
|
||||
},
|
||||
},
|
||||
ordinanceTypes.length
|
||||
? { ordinanceType: { in: ordinanceTypes } }
|
||||
: {},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'asc',
|
||||
},
|
||||
});
|
||||
return this.tourService.findInInterval(interval, ordinanceTypes);
|
||||
}
|
||||
|
||||
public findToursInIntervalWithEmployee(
|
||||
{ start, end }: Interval,
|
||||
interval: Interval,
|
||||
employeeId: string,
|
||||
ordinanceType?: string,
|
||||
): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
driverId: employeeId,
|
||||
},
|
||||
{
|
||||
codriverId: employeeId,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(end),
|
||||
},
|
||||
},
|
||||
...(ordinanceType ? [{ ordinanceType }] : []),
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return this.tourService.findInIntervalWithEmployee(
|
||||
interval,
|
||||
employeeId,
|
||||
ordinanceType ? [ordinanceType] : [],
|
||||
);
|
||||
}
|
||||
|
||||
public findTourByOperationId(operationId): Promise<Tour> {
|
||||
return this.prisma.tour.findUnique({ where: { operationId } });
|
||||
return this.tourService.findByOperationId(operationId);
|
||||
}
|
||||
|
||||
public findWorkTimeByPersonnelNumberForDay(
|
||||
|
|
@ -208,23 +74,10 @@ export class AnomalyDetectorService {
|
|||
startOfGivenDay = startOfDay(day);
|
||||
}
|
||||
const endOfGivenDay = endOfDay(startOfGivenDay);
|
||||
return this.prisma.workTime.findFirst({
|
||||
where: {
|
||||
userId: personnelNumber,
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: startOfGivenDay,
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lte: endOfGivenDay,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return this.workTimeService.findByPersonnelNumberInInterval(
|
||||
personnelNumber,
|
||||
{ start: startOfGivenDay, end: endOfGivenDay },
|
||||
);
|
||||
}
|
||||
|
||||
public findAttendanceRegistrationByPersonnelNumberForShift(
|
||||
|
|
@ -242,29 +95,12 @@ export class AnomalyDetectorService {
|
|||
startOfGivenDay = startOfDay(day);
|
||||
}
|
||||
const endOfShift = addHours(day, 12);
|
||||
return this.prisma.attendanceRegistration.findMany({
|
||||
where: {
|
||||
employeeId: +personnelNumber,
|
||||
event: {
|
||||
in: eventTypes,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
dateTime: {
|
||||
gte: startOfGivenDay,
|
||||
},
|
||||
},
|
||||
{
|
||||
dateTime: {
|
||||
lt: endOfShift,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
dateTime: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
return this.attendanceRegistrationService.findByEmployeeInInterval(
|
||||
+personnelNumber,
|
||||
{ start: startOfGivenDay, end: endOfShift },
|
||||
eventTypes,
|
||||
);
|
||||
}
|
||||
|
||||
public findPlannedTimeByPersonnelNumberForDay(
|
||||
|
|
@ -278,33 +114,18 @@ export class AnomalyDetectorService {
|
|||
startOfGivenDay = startOfDay(day);
|
||||
}
|
||||
const endOfShift = addHours(day, 12);
|
||||
return this.prisma.plannedTime.findMany({
|
||||
where: {
|
||||
userId: personnelNumber,
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: startOfGivenDay,
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: endOfShift,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
return this.plannedTimeService.findByPersonnelNumberInInterval(
|
||||
personnelNumber,
|
||||
{ start: startOfGivenDay, end: endOfShift },
|
||||
);
|
||||
}
|
||||
|
||||
public findAttendanceRegistrationById(
|
||||
attendanceRegistrationId: number,
|
||||
): Promise<AttendanceRegistration> {
|
||||
return this.prisma.attendanceRegistration.findUnique({
|
||||
where: {
|
||||
return this.attendanceRegistrationService.findById(
|
||||
attendanceRegistrationId,
|
||||
},
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
public findAttendanceRegistrationInInterval(
|
||||
|
|
@ -314,37 +135,19 @@ export class AnomalyDetectorService {
|
|||
AttendanceRegistrationEvents.ClockedOut,
|
||||
],
|
||||
): Promise<AttendanceRegistration[]> {
|
||||
return this.prisma.attendanceRegistration.findMany({
|
||||
where: {
|
||||
event: {
|
||||
in: eventTypes,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
dateTime: {
|
||||
gte: startOfDay(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
dateTime: {
|
||||
lt: endOfDay(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
dateTime: 'asc',
|
||||
},
|
||||
});
|
||||
return this.attendanceRegistrationService.findInInterval(
|
||||
interval,
|
||||
eventTypes,
|
||||
);
|
||||
}
|
||||
|
||||
public findEmployeeById(employeeId: string): Promise<Employee> {
|
||||
return this.prisma.employee.findUnique({ where: { id: employeeId } });
|
||||
return this.employeeService.findById(employeeId);
|
||||
}
|
||||
|
||||
public findEmployeeByPersonnelNumber(
|
||||
personnelNumber: string,
|
||||
): Promise<Employee> {
|
||||
return this.prisma.employee.findFirst({ where: { personnelNumber } });
|
||||
return this.employeeService.findByPersonnelNumber(personnelNumber);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { Anomaly, Prisma } from '@prisma/client';
|
|||
import { Interval, format } from 'date-fns';
|
||||
import { toZonedTime } from 'date-fns-tz';
|
||||
|
||||
import { AnomalyTypes } from 'src/modules/feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AnomalyDetector } from './anomaly-detector.class';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
|
||||
export class DriverCertificateDetector extends AnomalyDetector {
|
||||
private readonly logger = new Logger(DriverCertificateDetector.name);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { Logger } from '@nestjs/common';
|
|||
import { Anomaly, Prisma } from '@prisma/client';
|
||||
import { Interval, format } from 'date-fns';
|
||||
import { toZonedTime } from 'date-fns-tz';
|
||||
import { AnomalyTypes } from 'src/modules/feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AnomalyDetector } from './anomaly-detector.class';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
|
||||
export class DriverCodriverSameDetector extends AnomalyDetector {
|
||||
private readonly logger = new Logger(DriverCodriverSameDetector.name);
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import {
|
|||
isWithinInterval,
|
||||
set,
|
||||
} from 'date-fns';
|
||||
import { toZonedTime, fromZonedTime } from 'date-fns-tz';
|
||||
import { fromZonedTime, toZonedTime } from 'date-fns-tz';
|
||||
|
||||
import { AnomalyTypes } from 'src/modules/feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AnomalyDetector } from './anomaly-detector.class';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
|
||||
export class EmployeesLoggedInDetector extends AnomalyDetector {
|
||||
private readonly logger = new Logger(EmployeesLoggedInDetector.name);
|
||||
|
|
@ -200,11 +200,13 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
|||
return false;
|
||||
}
|
||||
if (!employee.personnelNumber) {
|
||||
this.logger.warn(`Employee has no personnelNumber ${JSON.stringify({
|
||||
this.logger.warn(
|
||||
`Employee has no personnelNumber ${JSON.stringify({
|
||||
id: employee.id,
|
||||
surname: employee.surname,
|
||||
name: employee.name,
|
||||
})}`);
|
||||
})}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import {
|
|||
} from 'date-fns';
|
||||
import { fromZonedTime, toZonedTime } from 'date-fns-tz';
|
||||
|
||||
import { AnomalyTypes } from 'src/modules/feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AnomalyDetector } from './anomaly-detector.class';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
|
||||
export class OneTourAtATimeDetector extends AnomalyDetector {
|
||||
private readonly logger = new Logger(OneTourAtATimeDetector.name);
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import { Anomaly, Prisma } from '@prisma/client';
|
|||
import { Interval, endOfDay, format, startOfDay } from 'date-fns';
|
||||
import { toZonedTime } from 'date-fns-tz';
|
||||
|
||||
import { AttendanceRegistrationEvents } from 'src/core/base/attendance-registration-events.enum';
|
||||
import { AnomalyTypes } from 'src/modules/feat-business-objects/entities/anomaly/anomaly-types.enum';
|
||||
import { AttendanceRegistrationEvents } from 'src/modules/feat-business-objects/entities/attendance-registration/attendance-registration-events.enum';
|
||||
import { AnomalyDetector } from './anomaly-detector.class';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
|
||||
export class WorkingEmployeeNotInDistributionSystemDetector extends AnomalyDetector {
|
||||
private readonly logger = new Logger(
|
||||
|
|
@ -83,6 +83,12 @@ export class WorkingEmployeeNotInDistributionSystemDetector extends AnomalyDetec
|
|||
await this.dataService.findAttendanceRegistrationById(
|
||||
+attendanceRegistrationId,
|
||||
);
|
||||
if (!attendanceRegistration) {
|
||||
this.logger.error(
|
||||
`Cannot verify anomaly ${anomaly.id}. AttendanceRegistration ${attendanceRegistrationId} couldn't be found`,
|
||||
);
|
||||
return anomaly;
|
||||
}
|
||||
const employeeId = employeeIdParts.join('-');
|
||||
const employee = await this.dataService.findEmployeeById(employeeId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PlannedTime, Tour } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class CarEntityRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
findToursInInterval(interval: Interval): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findPlannedTimeByOrdinanceTypesInInterval(
|
||||
ordinanceType: string,
|
||||
interval: Interval,
|
||||
): Promise<PlannedTime[]> {
|
||||
return this.prisma.plannedTime.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lte: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
...(ordinanceType !== 'ALL'
|
||||
? [
|
||||
{
|
||||
department: {
|
||||
contains: ordinanceType,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
department: {
|
||||
not: 'Leitstelle',
|
||||
},
|
||||
},
|
||||
{
|
||||
department: {
|
||||
not: 'Büro',
|
||||
},
|
||||
},
|
||||
{
|
||||
department: {
|
||||
not: 'Fortbildung',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
not: '0',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PlannedTime, Tour } from '@prisma/client';
|
||||
import { CarEntityRepository } from './car-entity.repository';
|
||||
import { CarEntity } from './car.entity';
|
||||
import { Interval } from 'date-fns';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
import { PlannedTimeService } from '../entities/planned-time/planned-time.service';
|
||||
import { TourService } from '../entities/tour/tour.service';
|
||||
import { CarEntity } from './car.entity';
|
||||
|
||||
@Injectable()
|
||||
export class CarEntityService {
|
||||
constructor(private readonly repository: CarEntityRepository) {}
|
||||
constructor(
|
||||
private readonly plannedTimeService: PlannedTimeService,
|
||||
private readonly tourService: TourService,
|
||||
) {}
|
||||
|
||||
async getCarsInInterval(
|
||||
interval: Interval,
|
||||
|
|
@ -41,10 +45,9 @@ export class CarEntityService {
|
|||
interval: Interval,
|
||||
ordinanceType: string,
|
||||
): Promise<CarEntity[]> {
|
||||
const toursInFullRange =
|
||||
await this.repository.findToursInInterval(interval);
|
||||
const toursInFullRange = await this.tourService.findInInterval(interval);
|
||||
const plannedTimeByOrdinanceType =
|
||||
await this.repository.findPlannedTimeByOrdinanceTypesInInterval(
|
||||
await this.plannedTimeService.findByOrdinanceTypesInInterval(
|
||||
ordinanceType,
|
||||
interval,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
export interface AnomalyCreateDto {
|
||||
id: string;
|
||||
type: string;
|
||||
description: string;
|
||||
groupKey: string;
|
||||
groupDescription: string;
|
||||
workedOnBy: string;
|
||||
sleepTimer: number;
|
||||
sleepSince?: Date | string | null;
|
||||
resolvedAt?: Date | string | null;
|
||||
manuallyResolved?: boolean;
|
||||
createdAt?: Date | string;
|
||||
solution?: {
|
||||
create: {
|
||||
id?: string;
|
||||
name: string;
|
||||
steps?: {
|
||||
create?: { description: string }[];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface AnomalyUpdateDto {
|
||||
resolvedAt: Date;
|
||||
manuallyResolved: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const ANOMALY_SLEEP_TIME_MS = 60 * 60 * 1000;
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
Anomaly,
|
||||
Prisma,
|
||||
SolutionTarget,
|
||||
SolutionTargetStep,
|
||||
} from '@prisma/client';
|
||||
import { subMilliseconds } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { AnomalyCreateDto } from './anomalies.dto';
|
||||
import { ANOMALY_SLEEP_TIME_MS } from './anomalies.token';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
|
||||
@Injectable()
|
||||
export class AnomalyRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
// Create anomaly if ID is not found, otherwise continue without change
|
||||
async createEvenIfExists(anomaly: AnomalyCreateDto): Promise<Anomaly> {
|
||||
return await this.prisma.anomaly.upsert({
|
||||
where: {
|
||||
id: anomaly.id,
|
||||
},
|
||||
create: {
|
||||
...anomaly,
|
||||
},
|
||||
update: {},
|
||||
});
|
||||
}
|
||||
|
||||
// Resolves the anomaly
|
||||
async resolveAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
return await this.prisma.anomaly.update({
|
||||
where: {
|
||||
id: anomalyId,
|
||||
},
|
||||
data: {
|
||||
resolvedAt: new Date(),
|
||||
manuallyResolved: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async snoozeAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
return await this.prisma.anomaly.update({
|
||||
where: {
|
||||
id: anomalyId,
|
||||
},
|
||||
data: {
|
||||
sleepSince: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async acceptAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
return await this.prisma.anomaly.update({
|
||||
where: {
|
||||
id: anomalyId,
|
||||
},
|
||||
data: {
|
||||
resolvedAt: new Date(),
|
||||
manuallyResolved: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Find all anomalies which are unresolved
|
||||
async findAllUnsolved(): Promise<Anomaly[]> {
|
||||
return await this.prisma.anomaly.findMany({
|
||||
where: {
|
||||
resolvedAt: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAllVisible(
|
||||
skip?: number,
|
||||
take?: number,
|
||||
types: AnomalyTypes[] = [],
|
||||
): Promise<Anomaly[]> {
|
||||
return await this.prisma.anomaly.findMany({
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: this.visibleAnomalyQuery(types),
|
||||
orderBy: {
|
||||
groupKey: 'asc',
|
||||
},
|
||||
include: {
|
||||
solution: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findById(anomalyId: string): Promise<Anomaly> {
|
||||
return await this.prisma.anomaly.findUnique({
|
||||
where: {
|
||||
id: anomalyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async countVisibleAnomalies(types: AnomalyTypes[] = []): Promise<number> {
|
||||
return await this.prisma.anomaly.count({
|
||||
where: {
|
||||
...this.visibleAnomalyQuery(types),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findSolutionTargetsByAnomalyId(
|
||||
anomalyId: string,
|
||||
): Promise<SolutionTarget[]> {
|
||||
return await this.prisma.solutionTarget.findMany({
|
||||
where: {
|
||||
anomalyId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findSolutionTargetStepsBySolutionTargetId(
|
||||
solutionTargetId: string,
|
||||
): Promise<SolutionTargetStep[]> {
|
||||
return await this.prisma.solutionTargetStep.findMany({
|
||||
where: {
|
||||
solutionTargetId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by `resolvedAt` and `sleepSince`
|
||||
private visibleAnomalyQuery(types: AnomalyTypes[]): Prisma.AnomalyWhereInput {
|
||||
return {
|
||||
...(types.length ? { type: { in: types } } : {}),
|
||||
resolvedAt: null,
|
||||
OR: [
|
||||
{
|
||||
sleepSince: null,
|
||||
},
|
||||
{
|
||||
sleepSince: {
|
||||
lt: subMilliseconds(new Date(), ANOMALY_SLEEP_TIME_MS),
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Anomaly, SolutionTarget, SolutionTargetStep } from '@prisma/client';
|
||||
import { AnomalyCreateDto } from './anomalies.dto';
|
||||
import { AnomalyTypes } from './anomaly-types.enum';
|
||||
import { AnomalyRepository } from './anomaly.repository';
|
||||
|
||||
@Injectable()
|
||||
export class AnomalyService {
|
||||
constructor(private readonly repository: AnomalyRepository) {}
|
||||
|
||||
async createEvenIfExists(anomaly: AnomalyCreateDto): Promise<Anomaly> {
|
||||
return this.repository.createEvenIfExists(anomaly);
|
||||
}
|
||||
|
||||
async resolveAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
return this.repository.resolveAnomaly(anomalyId);
|
||||
}
|
||||
|
||||
async snoozeAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
return this.repository.snoozeAnomaly(anomalyId);
|
||||
}
|
||||
|
||||
async acceptAnomaly(anomalyId: string): Promise<Anomaly> {
|
||||
return this.repository.acceptAnomaly(anomalyId);
|
||||
}
|
||||
|
||||
async findAllUnsolved(): Promise<Anomaly[]> {
|
||||
return this.repository.findAllUnsolved();
|
||||
}
|
||||
|
||||
async findAllVisible(
|
||||
skip?: number,
|
||||
take?: number,
|
||||
types: AnomalyTypes[] = [],
|
||||
): Promise<Anomaly[]> {
|
||||
return this.repository.findAllVisible(skip, take, types);
|
||||
}
|
||||
|
||||
async findById(anomalyId: string): Promise<Anomaly> {
|
||||
return this.repository.findById(anomalyId);
|
||||
}
|
||||
|
||||
async countVisibleAnomalies(types: AnomalyTypes[] = []): Promise<number> {
|
||||
return this.repository.countVisibleAnomalies(types);
|
||||
}
|
||||
|
||||
async findSolutionTargetsByAnomalyId(
|
||||
anomalyId: string,
|
||||
): Promise<SolutionTarget[]> {
|
||||
return this.repository.findSolutionTargetsByAnomalyId(anomalyId);
|
||||
}
|
||||
|
||||
async findSolutionTargetStepsBySolutionTargetId(
|
||||
solutionTargetId: string,
|
||||
): Promise<SolutionTargetStep[]> {
|
||||
return this.repository.findSolutionTargetStepsBySolutionTargetId(
|
||||
solutionTargetId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AttendanceRegistration } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { AttendanceRegistrationEvents } from './attendance-registration-events.enum';
|
||||
|
||||
@Injectable()
|
||||
export class AttendanceRegistrationRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findById(
|
||||
attendanceRegistrationId: number,
|
||||
): Promise<AttendanceRegistration> {
|
||||
return await this.prisma.attendanceRegistration.findUnique({
|
||||
where: {
|
||||
attendanceRegistrationId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findInInterval(
|
||||
interval: Interval,
|
||||
eventTypes: AttendanceRegistrationEvents[] = [
|
||||
AttendanceRegistrationEvents.ClockedIn,
|
||||
AttendanceRegistrationEvents.ClockedOut,
|
||||
],
|
||||
): Promise<AttendanceRegistration[]> {
|
||||
return await this.prisma.attendanceRegistration.findMany({
|
||||
where: {
|
||||
event: {
|
||||
in: eventTypes,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
dateTime: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
dateTime: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
dateTime: 'asc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByEmployeeInInterval(
|
||||
employeeId: number,
|
||||
timeRange: Interval,
|
||||
eventTypes: AttendanceRegistrationEvents[] = [
|
||||
AttendanceRegistrationEvents.ClockedIn,
|
||||
AttendanceRegistrationEvents.ClockedOut,
|
||||
],
|
||||
): Promise<AttendanceRegistration[]> {
|
||||
return await this.prisma.attendanceRegistration.findMany({
|
||||
where: {
|
||||
employeeId,
|
||||
event: {
|
||||
in: eventTypes,
|
||||
},
|
||||
AND: [
|
||||
{
|
||||
dateTime: {
|
||||
gte: new Date(timeRange.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
dateTime: {
|
||||
lt: new Date(timeRange.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
dateTime: 'asc',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AttendanceRegistrationRepository } from './attendance-registration.repository';
|
||||
import { AttendanceRegistration } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { AttendanceRegistrationEvents } from './attendance-registration-events.enum';
|
||||
|
||||
@Injectable()
|
||||
export class AttendanceRegistrationService {
|
||||
constructor(private readonly repository: AttendanceRegistrationRepository) {}
|
||||
|
||||
async findById(
|
||||
attendanceRegistrationId: number,
|
||||
): Promise<AttendanceRegistration> {
|
||||
return this.repository.findById(attendanceRegistrationId);
|
||||
}
|
||||
|
||||
async findInInterval(
|
||||
interval: Interval,
|
||||
eventTypes: AttendanceRegistrationEvents[],
|
||||
): Promise<AttendanceRegistration[]> {
|
||||
return this.repository.findInInterval(interval, eventTypes);
|
||||
}
|
||||
|
||||
async findByEmployeeInInterval(
|
||||
employeeId: number,
|
||||
interval: Interval,
|
||||
eventTypes: AttendanceRegistrationEvents[],
|
||||
): Promise<AttendanceRegistration[]> {
|
||||
return this.repository.findByEmployeeInInterval(
|
||||
employeeId,
|
||||
interval,
|
||||
eventTypes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Employee } from '@prisma/client';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class EmployeeRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findById(employeeId: string): Promise<Employee> {
|
||||
return await this.prisma.employee.findUnique({ where: { id: employeeId } });
|
||||
}
|
||||
|
||||
async findByPersonnelNumber(personnelNumber: string): Promise<Employee> {
|
||||
return await this.prisma.employee.findFirst({ where: { personnelNumber } });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { EmployeeRepository } from './employee.repository';
|
||||
import { Employee } from '@prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class EmployeeService {
|
||||
constructor(private readonly repository: EmployeeRepository) {}
|
||||
|
||||
async findById(employeeId: string): Promise<Employee> {
|
||||
return this.repository.findById(employeeId);
|
||||
}
|
||||
|
||||
async findByPersonnelNumber(personnelNumber: string): Promise<Employee> {
|
||||
return this.repository.findByPersonnelNumber(personnelNumber);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Patient } from '@prisma/client';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class PatientRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findById(patientId: string): Promise<Patient> {
|
||||
return this.prisma.patient.findUnique({ where: { id: patientId } });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Patient } from '@prisma/client';
|
||||
import { PatientRepository } from './patient.repository';
|
||||
|
||||
@Injectable()
|
||||
export class PatientService {
|
||||
constructor(private readonly repository: PatientRepository) {}
|
||||
|
||||
async findById(patientId: string): Promise<Patient> {
|
||||
return this.repository.findById(patientId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PlannedTime } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class PlannedTimeRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
): Promise<PlannedTime[]> {
|
||||
return await this.prisma.plannedTime.findMany({
|
||||
where: {
|
||||
userId: personnelNumber,
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByOrdinanceTypesInInterval(
|
||||
ordinanceType: string | undefined,
|
||||
interval: Interval,
|
||||
): Promise<PlannedTime[]> {
|
||||
const allOrdinanceTypes =
|
||||
ordinanceType == undefined || ordinanceType === 'ALL' ? true : false;
|
||||
return await this.prisma.plannedTime.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lte: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
...(allOrdinanceTypes
|
||||
? []
|
||||
: [
|
||||
{
|
||||
department: {
|
||||
contains: ordinanceType,
|
||||
},
|
||||
},
|
||||
]),
|
||||
{
|
||||
department: {
|
||||
not: 'Leitstelle',
|
||||
},
|
||||
},
|
||||
{
|
||||
department: {
|
||||
not: 'Büro',
|
||||
},
|
||||
},
|
||||
{
|
||||
department: {
|
||||
not: 'Fortbildung',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
note: null,
|
||||
},
|
||||
{
|
||||
userId: {
|
||||
not: '0',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { PlannedTime } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { PlannedTimeRepository } from './planned-time.repository';
|
||||
|
||||
@Injectable()
|
||||
export class PlannedTimeService {
|
||||
constructor(private readonly repository: PlannedTimeRepository) {}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
): Promise<PlannedTime[]> {
|
||||
return this.repository.findByPersonnelNumberInInterval(
|
||||
personnelNumber,
|
||||
interval,
|
||||
);
|
||||
}
|
||||
|
||||
async findByOrdinanceTypesInInterval(
|
||||
ordinanceType: string | undefined,
|
||||
interval: Interval,
|
||||
): Promise<PlannedTime[]> {
|
||||
return this.repository.findByOrdinanceTypesInInterval(
|
||||
ordinanceType,
|
||||
interval,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AvicennaTenant, CostType, Tariff, TariffGroup } from '@prisma/client';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class TariffRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findTenant(
|
||||
healthInsurance: string,
|
||||
ordinanceType: string,
|
||||
): Promise<AvicennaTenant> {
|
||||
return await this.prisma.avicennaTenant.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
name: healthInsurance,
|
||||
},
|
||||
{
|
||||
name: {
|
||||
contains: healthInsurance,
|
||||
},
|
||||
},
|
||||
],
|
||||
tariffGroups: {
|
||||
some: {
|
||||
ordinanceType,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByTariffGroupDescription(
|
||||
description: string,
|
||||
ordinanceType: string,
|
||||
dateValid: Date,
|
||||
): Promise<Tariff & { tariffGroup: TariffGroup; costTypes: CostType[] }> {
|
||||
return await this.prisma.tariff.findFirst({
|
||||
where: {
|
||||
tariffGroup: {
|
||||
description: {
|
||||
contains: description,
|
||||
},
|
||||
ordinanceType,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
AND: [
|
||||
{ validTo: null },
|
||||
{
|
||||
validFrom: {
|
||||
lte: dateValid,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ validTo: { gte: dateValid } },
|
||||
{ validFrom: { lte: dateValid } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
tariffGroup: true,
|
||||
costTypes: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByTenantId(
|
||||
tenantId: string,
|
||||
ordinanceType: string,
|
||||
dateValid: Date,
|
||||
): Promise<Tariff & { tariffGroup: TariffGroup; costTypes: CostType[] }> {
|
||||
return await this.prisma.tariff.findFirst({
|
||||
where: {
|
||||
tariffGroup: {
|
||||
tenants: {
|
||||
some: {
|
||||
id: tenantId,
|
||||
},
|
||||
},
|
||||
ordinanceType,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
AND: [
|
||||
{ validTo: null },
|
||||
{
|
||||
validFrom: {
|
||||
lte: dateValid,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ validTo: { gte: dateValid } },
|
||||
{ validFrom: { lte: dateValid } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
tariffGroup: true,
|
||||
costTypes: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AvicennaTenant, CostType, Tariff, TariffGroup } from '@prisma/client';
|
||||
import { TariffRepository } from './tariff.repository';
|
||||
|
||||
@Injectable()
|
||||
export class TariffService {
|
||||
constructor(private readonly repository: TariffRepository) {}
|
||||
|
||||
async findTenant(
|
||||
healthInsurance: string,
|
||||
ordinanceType: string,
|
||||
): Promise<AvicennaTenant> {
|
||||
return this.repository.findTenant(healthInsurance, ordinanceType);
|
||||
}
|
||||
|
||||
async findByTariffGroupDescription(
|
||||
description: string,
|
||||
ordinanceType: string,
|
||||
dateValid: Date,
|
||||
): Promise<Tariff & { tariffGroup: TariffGroup; costTypes: CostType[] }> {
|
||||
return this.repository.findByTariffGroupDescription(
|
||||
description,
|
||||
ordinanceType,
|
||||
dateValid,
|
||||
);
|
||||
}
|
||||
|
||||
async findByTenantId(
|
||||
tenantId: string,
|
||||
ordinanceType: string,
|
||||
dateValid: Date,
|
||||
): Promise<Tariff & { tariffGroup: TariffGroup; costTypes: CostType[] }> {
|
||||
return this.repository.findByTenantId(tenantId, ordinanceType, dateValid);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
ApprovalState,
|
||||
Prisma,
|
||||
Ticket,
|
||||
TicketStateHistory,
|
||||
TicketValidationState,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import { ExpressionBuilder, Kysely } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
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 { createPrismaQueryFromTicketFilters } from './ticket-filter';
|
||||
import { TicketFilterDto, UpdateDocumentInfoDto } from './tickets.dto';
|
||||
|
||||
function withTours(eb: ExpressionBuilder<DB, 'Ticket'>) {
|
||||
return jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('Tour')
|
||||
.selectAll('Tour')
|
||||
.whereRef('Tour.ticketId', '=', 'Ticket.id')
|
||||
.orderBy('Tour.operationId'),
|
||||
).as('tours');
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TicketRepository {
|
||||
private logger = new Logger(TicketRepository.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(KYSELY_CLIENT_PROVIDER_TOKEN) private readonly db: Kysely<DB>,
|
||||
) {}
|
||||
|
||||
async findById(ticketId: string): Promise<Ticket> {
|
||||
return await this.prisma.ticket.findUnique({
|
||||
where: { id: ticketId },
|
||||
include: { tours: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findAllCreated(): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return await this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAllClosed(): Promise<Ticket[]> {
|
||||
return await this.prisma.ticket.findMany({
|
||||
where: {
|
||||
tours: {
|
||||
every: {
|
||||
check: {
|
||||
not: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findAll(): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return await this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.ARCHIVED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByState(
|
||||
state: TicketValidationState,
|
||||
filter: TicketFilterDto,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
) {
|
||||
const query: Prisma.TicketFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
AND: createPrismaQueryFromTicketFilters(filter),
|
||||
currentState: state,
|
||||
tours: {
|
||||
some: {
|
||||
check: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
|
||||
const [tickets, count] = await this.prisma.$transaction([
|
||||
this.prisma.ticket.findMany({ ...query }),
|
||||
this.prisma.ticket.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tickets,
|
||||
total: count,
|
||||
};
|
||||
}
|
||||
|
||||
async findStateHistoryByTicketId(
|
||||
ticketId: string,
|
||||
): Promise<{ state: TicketValidationState }[]> {
|
||||
return await this.prisma.ticketStateHistory.findMany({
|
||||
where: {
|
||||
ticketId,
|
||||
},
|
||||
select: {
|
||||
state: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findIdByTourId(tourId: string): Promise<{ id: string }> {
|
||||
return await this.prisma.ticket.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
tours: {
|
||||
some: {
|
||||
id: tourId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async search(query: string): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.ARCHIVED,
|
||||
},
|
||||
},
|
||||
tours: {
|
||||
some: {
|
||||
OR: [
|
||||
...query.split(' ').flatMap((word) => [
|
||||
{
|
||||
patientName: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
patientSurname: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
take: 5,
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async createTicket(tours: Tour[]): Promise<Ticket> {
|
||||
this.logger.log(`Creating Ticket for ${tours.length} tours`);
|
||||
try {
|
||||
return await this.db.transaction().execute(async (trx) => {
|
||||
const ticket = await trx
|
||||
.insertInto('Ticket')
|
||||
.values({
|
||||
currentState: TicketValidationState.CREATED,
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
await trx
|
||||
.insertInto('TicketStateHistory')
|
||||
.values({
|
||||
state: TicketValidationState.CREATED,
|
||||
ticketId: ticket.id,
|
||||
})
|
||||
.executeTakeFirst();
|
||||
|
||||
await trx
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
ticketId: ticket.id,
|
||||
})
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
tours.map(({ id }) => id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return ticket as Ticket;
|
||||
});
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async addToursToTicket(ticketId: string, tours: Tour[]): Promise<Ticket> {
|
||||
await this.db
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
ticketId: ticketId,
|
||||
})
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
tours.map(({ id }) => id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return (await this.db
|
||||
.selectFrom('Ticket')
|
||||
.where('id', '=', ticketId)
|
||||
.selectAll()
|
||||
.executeTakeFirst()) as Ticket;
|
||||
}
|
||||
|
||||
async setActionablesOnTicket(
|
||||
ticketId: string,
|
||||
errors: string[],
|
||||
notes: string[],
|
||||
): Promise<Ticket> {
|
||||
return (await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({ errors, notes })
|
||||
.where('id', '=', ticketId)
|
||||
.returningAll()
|
||||
.executeTakeFirst()) as Ticket;
|
||||
}
|
||||
|
||||
async setStateOfTicket(
|
||||
ticketId: string,
|
||||
state: TicketValidationState,
|
||||
): Promise<TicketStateHistory> {
|
||||
await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({
|
||||
currentState: state,
|
||||
})
|
||||
.where('Ticket.id', '=', ticketId)
|
||||
.execute();
|
||||
|
||||
return await this.db
|
||||
.insertInto('TicketStateHistory')
|
||||
.values({ state, ticketId })
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async updateTicket(
|
||||
ticketId: string,
|
||||
data: {
|
||||
state?: TicketValidationState;
|
||||
errors?: string[];
|
||||
notes?: string[];
|
||||
isUrgent?: boolean;
|
||||
urgency?: Date;
|
||||
approvalState?: ApprovalState;
|
||||
documentInfo?: UpdateDocumentInfoDto;
|
||||
approvalPath?: string;
|
||||
},
|
||||
): Promise<Ticket & { tours: Tour[] }> {
|
||||
const { state, ...ticketData } = data;
|
||||
|
||||
await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({ ...ticketData })
|
||||
.where('id', '=', ticketId)
|
||||
.execute();
|
||||
|
||||
if (state) {
|
||||
await this.setStateOfTicket(ticketId, state);
|
||||
}
|
||||
|
||||
return (await this.db
|
||||
.selectFrom('Ticket')
|
||||
.where('id', '=', ticketId)
|
||||
.selectAll('Ticket')
|
||||
.select((eb) => withTours(eb))
|
||||
.executeTakeFirst()) as Ticket & { tours: Tour[] };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
ApprovalState,
|
||||
Ticket,
|
||||
TicketStateHistory,
|
||||
TicketValidationState,
|
||||
Tour,
|
||||
} from '@prisma/client';
|
||||
import {
|
||||
TicketFilterDto,
|
||||
UpdateDocumentInfoDto,
|
||||
} from 'src/modules/feat-business-objects/entities/ticket/tickets.dto';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
|
||||
@Injectable()
|
||||
export class TicketService {
|
||||
constructor(private readonly repository: TicketRepository) {}
|
||||
|
||||
async findById(ticketId: string): Promise<Ticket> {
|
||||
return this.repository.findById(ticketId);
|
||||
}
|
||||
|
||||
async findAllCreated(): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return this.repository.findAllCreated();
|
||||
}
|
||||
|
||||
async findAllClosed(): Promise<Ticket[]> {
|
||||
return this.repository.findAllClosed();
|
||||
}
|
||||
|
||||
async findAll(): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return this.repository.findAll();
|
||||
}
|
||||
|
||||
async findByState(
|
||||
state: TicketValidationState,
|
||||
filter: TicketFilterDto,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
) {
|
||||
return this.repository.findByState(state, filter, take, skip);
|
||||
}
|
||||
|
||||
async findStateHistoryByTicketId(
|
||||
ticketId: string,
|
||||
): Promise<{ state: TicketValidationState }[]> {
|
||||
return this.repository.findStateHistoryByTicketId(ticketId);
|
||||
}
|
||||
|
||||
async findIdByTourId(tourId: string): Promise<{ id: string }> {
|
||||
return this.repository.findIdByTourId(tourId);
|
||||
}
|
||||
|
||||
async search(query: string): Promise<(Ticket & { tours: Tour[] })[]> {
|
||||
return this.repository.search(query);
|
||||
}
|
||||
|
||||
async createTicket(tours: Tour[]): Promise<Ticket> {
|
||||
return this.repository.createTicket(tours);
|
||||
}
|
||||
|
||||
async addToursToTicket(ticketId: string, tours: Tour[]): Promise<Ticket> {
|
||||
return this.repository.addToursToTicket(ticketId, tours);
|
||||
}
|
||||
|
||||
async setActionablesOnTicket(
|
||||
ticketId: string,
|
||||
errors: string[],
|
||||
notes: string[],
|
||||
): Promise<Ticket> {
|
||||
return this.repository.setActionablesOnTicket(ticketId, errors, notes);
|
||||
}
|
||||
|
||||
async setStateOfTicket(
|
||||
ticketId: string,
|
||||
state: TicketValidationState,
|
||||
): Promise<TicketStateHistory> {
|
||||
return this.repository.setStateOfTicket(ticketId, state);
|
||||
}
|
||||
|
||||
async updateTicket(
|
||||
ticketId: string,
|
||||
data: {
|
||||
state?: TicketValidationState;
|
||||
errors?: string[];
|
||||
notes?: string[];
|
||||
isUrgent?: boolean;
|
||||
urgency?: Date;
|
||||
approvalState?: ApprovalState;
|
||||
documentInfo?: UpdateDocumentInfoDto;
|
||||
approvalPath?: string;
|
||||
},
|
||||
): Promise<Ticket & { tours: Tour[] }> {
|
||||
return this.repository.updateTicket(ticketId, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { ApprovalState } from "@prisma/client";
|
||||
|
||||
export interface UpdateApprovalInfoDto {
|
||||
isNeeded?: boolean;
|
||||
whoRequested?: string;
|
||||
answeredFrom?: string;
|
||||
location?: string;
|
||||
}
|
||||
|
||||
export interface UpdateDocumentInfoDto {
|
||||
approval: UpdateApprovalInfoDto;
|
||||
tdLocation?: string;
|
||||
}
|
||||
|
||||
export interface TicketFilterDto {
|
||||
errors?: string[];
|
||||
approvalStates?: ApprovalState[];
|
||||
documentInfo?: ('approval' | 'transport_document')[]
|
||||
}
|
||||
|
|
@ -0,0 +1,387 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, Tour } from '@prisma/client';
|
||||
import { endOfDay, Interval, startOfDay } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { AccountingState } from 'src/modules/app-accounting/accounting-state';
|
||||
import {
|
||||
createPrismaQueryFromAccountingFilters,
|
||||
TourAccountingFilterDto,
|
||||
} from './tours.filter';
|
||||
|
||||
function onlyRealTours(): Prisma.TourWhereInput[] {
|
||||
return [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TourRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findByOperationId(operationId: string): Promise<Tour> {
|
||||
return await this.prisma.tour.findFirst({
|
||||
where: { operationId },
|
||||
});
|
||||
}
|
||||
|
||||
async findInInterval(
|
||||
interval: Interval,
|
||||
ordinanceTypes: string[] = [],
|
||||
): Promise<Tour[]> {
|
||||
return await this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
...(ordinanceTypes.length
|
||||
? ordinanceTypes.map((ot) => ({
|
||||
ordinanceType: { contains: ot },
|
||||
}))
|
||||
: []),
|
||||
],
|
||||
},
|
||||
...onlyRealTours(),
|
||||
],
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public findInIntervalWithEmployee(
|
||||
{ start, end }: Interval,
|
||||
employeeId: string,
|
||||
ordinanceTypes: string[] = [],
|
||||
): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
driverId: employeeId,
|
||||
},
|
||||
{
|
||||
codriverId: employeeId,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(end),
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
...(ordinanceTypes.length
|
||||
? ordinanceTypes.map((ot) => ({
|
||||
ordinanceType: { contains: ot },
|
||||
}))
|
||||
: []),
|
||||
],
|
||||
},
|
||||
...onlyRealTours(),
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByTicketId(ticketId: string): Promise<Tour[]> {
|
||||
return await this.prisma.tour.findMany({
|
||||
where: { ticketId },
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------- Ticket System ------------------- //
|
||||
async findFirstRecurring(tour: Tour): Promise<Tour | null> {
|
||||
const institutionStreet =
|
||||
tour.direction === 'Hinfahrt' ? tour.targetStreet : tour.startStreet;
|
||||
return await this.prisma.tour.findFirst({
|
||||
where: {
|
||||
type: 'Serie',
|
||||
category: tour.category,
|
||||
ordinanceType: tour.ordinanceType,
|
||||
patientId: tour.patientId,
|
||||
patientName: tour.patientName,
|
||||
patientSurname: tour.patientSurname,
|
||||
patientStreet: tour.patientStreet,
|
||||
patientCity: tour.patientCity,
|
||||
patientZip: tour.patientZip,
|
||||
OR: [
|
||||
{ targetStreet: institutionStreet },
|
||||
{ startStreet: institutionStreet },
|
||||
],
|
||||
rangeEndDate: {
|
||||
gte: tour.startDate,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findOnSameDayWithOppositeDirection(tour: Tour): Promise<Tour | null> {
|
||||
return await this.prisma.tour.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gt: startOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: endOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startStreet: tour.targetStreet,
|
||||
},
|
||||
{
|
||||
targetStreet: tour.startStreet,
|
||||
},
|
||||
{
|
||||
direction: tour.direction === 'Hinfahrt' ? 'Rückfahrt' : 'Hinfahrt',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findWithTicket(interval: Interval): Promise<Tour[]> {
|
||||
return await this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async removeTourFromTicket(tourId: string): Promise<Tour> {
|
||||
return await this.prisma.tour.update({
|
||||
where: {
|
||||
id: tourId,
|
||||
},
|
||||
data: {
|
||||
Ticket: {
|
||||
disconnect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// --------------------- Accounting --------------------- //
|
||||
async findByAccountingState(
|
||||
state: AccountingState,
|
||||
filter: TourAccountingFilterDto,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
): Promise<{ tours: Tour[]; total: number }> {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const query: Prisma.TourFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 && !filter?.deviation
|
||||
? { revenueDeviation: { not: 0 } }
|
||||
: {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [
|
||||
...createPrismaQueryFromAccountingFilters(filter),
|
||||
...onlyRealTours(),
|
||||
],
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
|
||||
const [tours, count] = await this.prisma.$transaction([
|
||||
this.prisma.tour.findMany({ ...query }),
|
||||
this.prisma.tour.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tours,
|
||||
total: count,
|
||||
};
|
||||
}
|
||||
|
||||
async calculateAccountingStateMeta(state: AccountingState): Promise<{
|
||||
count: number;
|
||||
revenueSum?: number;
|
||||
positiveRevenueDeviation?: number;
|
||||
negativeRevenueDeviation?: number;
|
||||
}> {
|
||||
const checkValue = this.getCheckValueForAccountingState(state);
|
||||
const ticketWithCurrentStateAndTours: Prisma.TourWhereInput = {
|
||||
check: checkValue,
|
||||
...(checkValue === 2 ? { revenueDeviation: { not: 0 } } : {}),
|
||||
empty: {
|
||||
not: null,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
AND: [...onlyRealTours()],
|
||||
};
|
||||
|
||||
const [
|
||||
count,
|
||||
revenueSum,
|
||||
positiveRevenueDeviation,
|
||||
negativeRevenueDeviation,
|
||||
] = await this.prisma.$transaction([
|
||||
this.prisma.tour.count({
|
||||
where: { ...ticketWithCurrentStateAndTours },
|
||||
}),
|
||||
this.prisma.tour.aggregate({
|
||||
_sum: {
|
||||
revenue: true,
|
||||
},
|
||||
where: {
|
||||
...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,
|
||||
};
|
||||
}
|
||||
|
||||
private getCheckValueForAccountingState(state: AccountingState): number {
|
||||
switch (state) {
|
||||
case AccountingState.Billable:
|
||||
return 0;
|
||||
case AccountingState.PreparedForBilling:
|
||||
return 1;
|
||||
case AccountingState.Billed:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Tour } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { TourRepository } from './tour.repository';
|
||||
import { AccountingState } from 'src/modules/app-accounting/accounting-state';
|
||||
import { TourAccountingFilterDto } from './tours.filter';
|
||||
|
||||
@Injectable()
|
||||
export class TourService {
|
||||
constructor(private readonly repository: TourRepository) {}
|
||||
|
||||
async findInInterval(
|
||||
interval: Interval,
|
||||
ordinanceTypes?: string[],
|
||||
): Promise<Tour[]> {
|
||||
return this.repository.findInInterval(interval, ordinanceTypes);
|
||||
}
|
||||
|
||||
public findInIntervalWithEmployee(
|
||||
interval: Interval,
|
||||
employeeId: string,
|
||||
ordinanceTypes: string[],
|
||||
): Promise<Tour[]> {
|
||||
return this.repository.findInIntervalWithEmployee(
|
||||
interval,
|
||||
employeeId,
|
||||
ordinanceTypes,
|
||||
);
|
||||
}
|
||||
|
||||
async findByOperationId(operationId: string): Promise<Tour> {
|
||||
return this.repository.findByOperationId(operationId);
|
||||
}
|
||||
|
||||
async findByTicketId(ticketId: string): Promise<Tour[]> {
|
||||
return this.repository.findByTicketId(ticketId);
|
||||
}
|
||||
|
||||
async findFirstRecurring(tour: Tour): Promise<Tour | null> {
|
||||
return this.repository.findFirstRecurring(tour);
|
||||
}
|
||||
|
||||
async findOnSameDayWithOppositeDirection(tour: Tour): Promise<Tour | null> {
|
||||
return this.repository.findOnSameDayWithOppositeDirection(tour);
|
||||
}
|
||||
|
||||
async findToursWithTicket(interval: Interval): Promise<Tour[]> {
|
||||
return this.repository.findWithTicket(interval);
|
||||
}
|
||||
|
||||
async removeTourFromTicket(tourId: string): Promise<Tour> {
|
||||
return this.repository.removeTourFromTicket(tourId);
|
||||
}
|
||||
|
||||
async findByAccountingState(
|
||||
state: AccountingState,
|
||||
filter: TourAccountingFilterDto,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
): Promise<{ tours: Tour[]; total: number }> {
|
||||
return this.repository.findByAccountingState(state, filter, take, skip);
|
||||
}
|
||||
|
||||
async calculateAccountingStateMeta(state: AccountingState): Promise<{
|
||||
count: number;
|
||||
revenueSum?: number;
|
||||
positiveRevenueDeviation?: number;
|
||||
negativeRevenueDeviation?: number;
|
||||
}> {
|
||||
return this.repository.calculateAccountingStateMeta(state);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import { Prisma, TicketValidationState } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
|
||||
export interface AccountingFilter {
|
||||
export interface TourAccountingFilterDto {
|
||||
deviation?: 'pos' | 'neg' | 'both'[];
|
||||
missing?: 'information' | 'documents'[];
|
||||
dateRange?: Interval;
|
||||
}
|
||||
|
||||
export function createPrismaQueryFromAccountingFilters(
|
||||
filter: AccountingFilter,
|
||||
filter: TourAccountingFilterDto,
|
||||
): Prisma.TourWhereInput[] {
|
||||
const conditions: Prisma.TourWhereInput[] = [];
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { WorkTime } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class WorkTimeRepository {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
): Promise<WorkTime> {
|
||||
return await this.prisma.workTime.findFirst({
|
||||
where: {
|
||||
userId: personnelNumber,
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lte: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { WorkTime } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { WorkTimeRepository } from './work-time.repository';
|
||||
|
||||
@Injectable()
|
||||
export class WorkTimeService {
|
||||
constructor(private readonly repository: WorkTimeRepository) {}
|
||||
|
||||
async findByPersonnelNumberInInterval(
|
||||
personnelNumber: string,
|
||||
interval: Interval,
|
||||
): Promise<WorkTime> {
|
||||
return this.repository.findByPersonnelNumberInInterval(
|
||||
personnelNumber,
|
||||
interval,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,59 @@
|
|||
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";
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { CarEntityService } from './car-entity/car-entity.service';
|
||||
import { AnomalyRepository } from './entities/anomaly/anomaly.repository';
|
||||
import { AnomalyService } from './entities/anomaly/anomaly.service';
|
||||
import { AttendanceRegistrationRepository } from './entities/attendance-registration/attendance-registration.repository';
|
||||
import { AttendanceRegistrationService } from './entities/attendance-registration/attendance-registration.service';
|
||||
import { EmployeeRepository } from './entities/employee/employee.repository';
|
||||
import { EmployeeService } from './entities/employee/employee.service';
|
||||
import { PatientRepository } from './entities/patient/patient.repository';
|
||||
import { PatientService } from './entities/patient/patient.service';
|
||||
import { PlannedTimeRepository } from './entities/planned-time/planned-time.repository';
|
||||
import { PlannedTimeService } from './entities/planned-time/planned-time.service';
|
||||
import { TariffRepository } from './entities/tariff/tariff.repository';
|
||||
import { TariffService } from './entities/tariff/tariff.service';
|
||||
import { TicketRepository } from './entities/ticket/ticket.repository';
|
||||
import { TicketService } from './entities/ticket/ticket.service';
|
||||
import { TourRepository } from './entities/tour/tour.repository';
|
||||
import { TourService } from './entities/tour/tour.service';
|
||||
import { WorkTimeRepository } from './entities/work-time/work-time.repository';
|
||||
import { WorkTimeService } from './entities/work-time/work-time.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
exports: [CarEntityService],
|
||||
providers: [CarEntityRepository, CarEntityService]
|
||||
exports: [
|
||||
CarEntityService,
|
||||
AnomalyService,
|
||||
AttendanceRegistrationService,
|
||||
EmployeeService,
|
||||
PatientService,
|
||||
PlannedTimeService,
|
||||
TariffService,
|
||||
TicketService,
|
||||
TourService,
|
||||
WorkTimeService,
|
||||
],
|
||||
providers: [
|
||||
CarEntityService,
|
||||
AnomalyRepository,
|
||||
AnomalyService,
|
||||
AttendanceRegistrationRepository,
|
||||
AttendanceRegistrationService,
|
||||
EmployeeRepository,
|
||||
EmployeeService,
|
||||
PatientRepository,
|
||||
PatientService,
|
||||
PlannedTimeRepository,
|
||||
PlannedTimeService,
|
||||
TariffRepository,
|
||||
TariffService,
|
||||
TicketRepository,
|
||||
TicketService,
|
||||
TourRepository,
|
||||
TourService,
|
||||
WorkTimeRepository,
|
||||
WorkTimeService,
|
||||
],
|
||||
})
|
||||
export class FeatBusinessObjectsModule {}
|
||||
|
|
@ -8,8 +8,8 @@ import {
|
|||
} from '@prisma/client';
|
||||
import { Interval, addDays, isSaturday, isWithinInterval } from 'date-fns';
|
||||
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
import { TariffService } from '../feat-business-objects/entities/tariff/tariff.service';
|
||||
import { BILLING_INFORMATION_BY_ORDINANCE_TYPE } from './billing-information-by-ordinance-type';
|
||||
import {
|
||||
calculateDesinfektionszuschlag,
|
||||
|
|
@ -36,8 +36,8 @@ export class CalculationService {
|
|||
private healthInsurancesWithNoTenant = [];
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly personnelCostCalculationService: PersonnelCostCalculationService,
|
||||
private readonly tariffService: TariffService,
|
||||
) {}
|
||||
|
||||
async calculateTourRevenue(tour: Tour): Promise<number> {
|
||||
|
|
@ -305,127 +305,29 @@ export class CalculationService {
|
|||
tourDate: Date,
|
||||
) {
|
||||
if (!healthInsurance || healthInsurance === '') {
|
||||
return this.prisma.tariff.findFirst({
|
||||
where: {
|
||||
tariffGroup: {
|
||||
description: {
|
||||
contains: 'AOK',
|
||||
},
|
||||
return this.tariffService.findByTariffGroupDescription(
|
||||
'AOK',
|
||||
ordinanceType,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
AND: [
|
||||
{ validTo: null },
|
||||
{
|
||||
validFrom: {
|
||||
lte: tourDate,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ validTo: { gte: tourDate } },
|
||||
{ validFrom: { lte: tourDate } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
tariffGroup: true,
|
||||
costTypes: true,
|
||||
},
|
||||
});
|
||||
tourDate,
|
||||
);
|
||||
}
|
||||
const tenant = await this.prisma.avicennaTenant.findFirst({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
name: healthInsurance,
|
||||
},
|
||||
{
|
||||
name: {
|
||||
contains: healthInsurance,
|
||||
},
|
||||
},
|
||||
],
|
||||
tariffGroups: {
|
||||
some: {
|
||||
const tenant = await this.tariffService.findTenant(
|
||||
healthInsurance,
|
||||
ordinanceType,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
if (!tenant) {
|
||||
this.healthInsurancesWithNoTenant.push(healthInsurance);
|
||||
return this.prisma.tariff.findFirst({
|
||||
where: {
|
||||
tariffGroup: {
|
||||
description: {
|
||||
contains: 'AOK',
|
||||
},
|
||||
return this.tariffService.findByTariffGroupDescription(
|
||||
'AOK',
|
||||
ordinanceType,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
AND: [
|
||||
{ validTo: null },
|
||||
{
|
||||
validFrom: {
|
||||
lte: tourDate,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ validTo: { gte: tourDate } },
|
||||
{ validFrom: { lte: tourDate } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
tariffGroup: true,
|
||||
costTypes: true,
|
||||
},
|
||||
});
|
||||
tourDate,
|
||||
);
|
||||
}
|
||||
return this.prisma.tariff.findFirst({
|
||||
where: {
|
||||
tariffGroup: {
|
||||
tenants: {
|
||||
some: {
|
||||
id: tenant.id,
|
||||
},
|
||||
},
|
||||
return this.tariffService.findByTenantId(
|
||||
tenant.id,
|
||||
ordinanceType,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
AND: [
|
||||
{ validTo: null },
|
||||
{
|
||||
validFrom: {
|
||||
lte: tourDate,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ validTo: { gte: tourDate } },
|
||||
{ validFrom: { lte: tourDate } },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
include: {
|
||||
tariffGroup: true,
|
||||
costTypes: true,
|
||||
},
|
||||
});
|
||||
tourDate,
|
||||
);
|
||||
}
|
||||
|
||||
getHealthInsurancesWithNoTenant(): string[] {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ import { Module } from '@nestjs/common';
|
|||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { CalculationService } from './calculation.service';
|
||||
import { PersonnelCostCalculationService } from './personnel-cost-calculation.service';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
imports: [PrismaModule, FeatBusinessObjectsModule],
|
||||
providers: [CalculationService, PersonnelCostCalculationService],
|
||||
exports: [CalculationService],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
set,
|
||||
subDays,
|
||||
subHours,
|
||||
subMonths,
|
||||
subWeeks
|
||||
} from 'date-fns';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { generateIntervalForTimeRange } from 'src/utils';
|
||||
|
|
@ -310,7 +310,7 @@ export class KpiService {
|
|||
},
|
||||
},
|
||||
createdAt: {
|
||||
gte: subMonths(new Date(), 3),
|
||||
gte: subWeeks(new Date(), 2),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import { ApprovalState } from '@prisma/client';
|
||||
import { TicketFilter } from './ticket-filter';
|
||||
|
||||
export class TicketFilterArgs implements TicketFilter {
|
||||
errors: string[];
|
||||
approvalStates: ApprovalState[];
|
||||
documentInfo: ('approval' | 'transport_document')[];
|
||||
}
|
||||
|
||||
export interface ApprovalInfoArgs {
|
||||
isNeeded?: boolean;
|
||||
whoRequested?: string;
|
||||
answeredFrom?: string;
|
||||
location?: string;
|
||||
}
|
||||
|
||||
export interface DocumentInfoArgs {
|
||||
approval: ApprovalInfoArgs;
|
||||
tdLocation?: string;
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { ApprovalState, Prisma } from '@prisma/client';
|
||||
|
||||
export interface TicketFilter {
|
||||
errors?: string[];
|
||||
approvalStates?: ApprovalState[];
|
||||
documentInfo?: ('approval' | 'transport_document')[];
|
||||
}
|
||||
|
||||
export function createPrismaQueryFromTicketFilters(
|
||||
filter: TicketFilter,
|
||||
): Prisma.TicketWhereInput[] {
|
||||
const conditions: Prisma.TicketWhereInput[] = [];
|
||||
|
||||
Object.entries(filter ?? {}).forEach(([property, args]) => {
|
||||
switch (property) {
|
||||
case 'errors':
|
||||
if (args.length > 0)
|
||||
conditions.push({
|
||||
errors: {
|
||||
hasSome: [...args],
|
||||
},
|
||||
});
|
||||
return;
|
||||
case 'approvalStates':
|
||||
if (args.length > 0)
|
||||
conditions.push({
|
||||
approvalState: {
|
||||
in: [...args],
|
||||
},
|
||||
});
|
||||
return;
|
||||
case 'documentInfo':
|
||||
if (args.includes('approval')) {
|
||||
conditions.push({
|
||||
OR: [
|
||||
{
|
||||
documentInfo: {
|
||||
path: ['approval', 'location'],
|
||||
not: 'company',
|
||||
},
|
||||
},
|
||||
{
|
||||
documentInfo: {
|
||||
path: ['approval', 'location'],
|
||||
equals: Prisma.JsonNull,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (args.includes('td_company')) {
|
||||
conditions.push({
|
||||
documentInfo: {
|
||||
path: ['tdLocation'],
|
||||
equals: 'company',
|
||||
},
|
||||
});
|
||||
}
|
||||
if (args.includes('td_customer')) {
|
||||
conditions.push({
|
||||
documentInfo: {
|
||||
path: ['tdLocation'],
|
||||
equals: 'customer',
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
return conditions;
|
||||
}
|
||||
|
|
@ -6,35 +6,41 @@ import {
|
|||
Tour,
|
||||
} from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { DocumentInfoArgs } from './ticket-arg.types';
|
||||
import { PatientService } from '../feat-business-objects/entities/patient/patient.service';
|
||||
import { TicketService } from '../feat-business-objects/entities/ticket/ticket.service';
|
||||
import { UpdateDocumentInfoDto } from '../feat-business-objects/entities/ticket/tickets.dto';
|
||||
import { TourService } from '../feat-business-objects/entities/tour/tour.service';
|
||||
import {
|
||||
ValidationResult,
|
||||
validatePatient,
|
||||
validatePatientDataOnTours,
|
||||
validateTour,
|
||||
} from './ticket-validation.functions';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
|
||||
@Injectable()
|
||||
export class TicketLifecycleService {
|
||||
private readonly logger = new Logger(TicketLifecycleService.name);
|
||||
|
||||
constructor(private readonly repository: TicketRepository) {}
|
||||
constructor(
|
||||
private readonly patientService: PatientService,
|
||||
private readonly ticketService: TicketService,
|
||||
private readonly tourService: TourService,
|
||||
) {}
|
||||
|
||||
async createTickets(interval: Interval): Promise<void> {
|
||||
try {
|
||||
const tours = await this.repository.findTours(interval);
|
||||
const tours = await this.tourService.findInInterval(interval);
|
||||
|
||||
for (const tour of tours) {
|
||||
if (tour.ticketId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ticketId = await this.repository.searchTicketIDForTour(tour);
|
||||
const ticketId = await this.searchTicketIDForTour(tour);
|
||||
if (ticketId) {
|
||||
await this.repository.addToursToTicket(ticketId, [tour]);
|
||||
await this.ticketService.addToursToTicket(ticketId, [tour]);
|
||||
} else {
|
||||
await this.repository.createTicket([tour]);
|
||||
await this.ticketService.createTicket([tour]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,9 +53,9 @@ export class TicketLifecycleService {
|
|||
|
||||
async validateDocumentInfoUpdate(
|
||||
ticketId: string,
|
||||
documentInfo: DocumentInfoArgs,
|
||||
documentInfo: UpdateDocumentInfoDto,
|
||||
) {
|
||||
const ticket = await this.repository.findById(ticketId);
|
||||
const ticket = await this.ticketService.findById(ticketId);
|
||||
|
||||
switch (ticket.currentState) {
|
||||
case TicketValidationState.USER_INTERACTION_REQUIRED:
|
||||
|
|
@ -68,14 +74,14 @@ export class TicketLifecycleService {
|
|||
stateUpdate = TicketValidationState.TD_UNCERTAIN;
|
||||
else stateUpdate = TicketValidationState.DOCUMENTS_MISSING;
|
||||
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
return this.ticketService.updateTicket(ticketId, {
|
||||
documentInfo,
|
||||
approvalState: approvalStateUpdate,
|
||||
state: stateUpdate,
|
||||
});
|
||||
|
||||
case TicketValidationState.TD_UNCERTAIN:
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
return this.ticketService.updateTicket(ticketId, {
|
||||
documentInfo,
|
||||
state:
|
||||
ticket.approvalState === ApprovalState.FREE &&
|
||||
|
|
@ -84,7 +90,7 @@ export class TicketLifecycleService {
|
|||
: TicketValidationState.DOCUMENTS_MISSING,
|
||||
});
|
||||
case TicketValidationState.DOCUMENTS_MISSING:
|
||||
return this.repository.updateTicket(ticketId, {
|
||||
return this.ticketService.updateTicket(ticketId, {
|
||||
documentInfo,
|
||||
...((ticket.approvalState === ApprovalState.FREE ||
|
||||
documentInfo.approval.location === 'company') &&
|
||||
|
|
@ -95,8 +101,21 @@ export class TicketLifecycleService {
|
|||
}
|
||||
}
|
||||
|
||||
private async searchTicketIDForTour(tour: Tour): Promise<string | undefined> {
|
||||
const tours = await Promise.all([
|
||||
this.tourService.findFirstRecurring(tour),
|
||||
this.tourService.findOnSameDayWithOppositeDirection(tour),
|
||||
]);
|
||||
const firstFoundTour = tours.filter((tour) => !!tour)[0];
|
||||
if (firstFoundTour) {
|
||||
const ticket = await this.ticketService.findIdByTourId(firstFoundTour.id);
|
||||
return ticket?.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async validateCreatedTickets(): Promise<void> {
|
||||
const tickets = await this.repository.findAllCreated();
|
||||
const tickets = await this.ticketService.findAllCreated();
|
||||
this.logger.debug(`Found ${tickets.length} with state CREATED`);
|
||||
|
||||
for (const ticket of tickets) {
|
||||
|
|
@ -109,7 +128,7 @@ export class TicketLifecycleService {
|
|||
if (isValid) {
|
||||
const { result: approvalResult } = await this.isApprovalNeeded(ticket);
|
||||
if (approvalResult) {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
await this.ticketService.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
|
|
@ -126,14 +145,14 @@ export class TicketLifecycleService {
|
|||
ordinanceType.indexOf('TSW') > -1,
|
||||
)
|
||||
) {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
await this.ticketService.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.TD_UNCERTAIN,
|
||||
approvalState: ApprovalState.FREE,
|
||||
});
|
||||
} else {
|
||||
await this.repository.updateTicket(ticket.id, {
|
||||
await this.ticketService.updateTicket(ticket.id, {
|
||||
errors,
|
||||
notes,
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
|
|
@ -142,15 +161,19 @@ export class TicketLifecycleService {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
await this.repository.setActionablesOnTicket(ticket.id, errors, notes);
|
||||
await this.ticketService.setActionablesOnTicket(
|
||||
ticket.id,
|
||||
errors,
|
||||
notes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async archiveClosedTickets() {
|
||||
const closedTickets = await this.repository.findAllClosed();
|
||||
const closedTickets = await this.ticketService.findAllClosed();
|
||||
for (const closedTicket of closedTickets) {
|
||||
await this.repository.setStateOfTicket(
|
||||
await this.ticketService.setStateOfTicket(
|
||||
closedTicket.id,
|
||||
TicketValidationState.ARCHIVED,
|
||||
);
|
||||
|
|
@ -183,7 +206,7 @@ export class TicketLifecycleService {
|
|||
if (!patientId && ticket.tours.at(0)?.type === 'Spontan') {
|
||||
patientResult = validatePatientDataOnTours(ticket.tours);
|
||||
} else {
|
||||
const patient = await this.repository.findPatientById(patientId);
|
||||
const patient = await this.patientService.findById(patientId);
|
||||
if (!patient) {
|
||||
patientResult = validatePatientDataOnTours(ticket.tours);
|
||||
} else {
|
||||
|
|
@ -254,7 +277,7 @@ export class TicketLifecycleService {
|
|||
['BTW', 'TSW'].includes(tour.ordinanceType) &&
|
||||
tour.category !== 'Dialyse'
|
||||
) {
|
||||
const patient = await this.repository.findPatientById(tour.patientId);
|
||||
const patient = await this.patientService.findById(tour.patientId);
|
||||
if (
|
||||
['3', '4', '5'].includes(patient.careDegree) &&
|
||||
(['aG', 'Bi', 'H'].includes(patient.disabilityMark1) ||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||
import { FeatBusinessObjectsModule } from '../feat-business-objects/feat-business-objects.module';
|
||||
import { TicketLifecycleService } from './ticket-lifecycle.service';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
import { TicketService } from './tickets.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [TicketRepository, TicketService, TicketLifecycleService],
|
||||
exports: [TicketService],
|
||||
imports: [PrismaModule, FeatBusinessObjectsModule],
|
||||
providers: [TicketService, TicketLifecycleService],
|
||||
exports: [TicketService, TicketLifecycleService],
|
||||
})
|
||||
export class TicketSystemModule {}
|
||||
export class FeatTicketSystemModule {}
|
||||
|
|
|
|||
|
|
@ -1,504 +0,0 @@
|
|||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { Patient, Prisma, TicketValidationState, Tour } from '@prisma/client';
|
||||
import { Interval, endOfDay, startOfDay } from 'date-fns';
|
||||
import { ExpressionBuilder, Kysely } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
|
||||
import { KYSELY_CLIENT_PROVIDER_TOKEN } from 'src/core/database/database.module';
|
||||
import { PrismaService } from 'src/core/database/prisma.service';
|
||||
import { ApprovalState, DB } from 'src/core/database/types';
|
||||
import { DocumentInfoArgs, TicketFilterArgs } from './ticket-arg.types';
|
||||
import { createPrismaQueryFromTicketFilters } from './ticket-filter';
|
||||
|
||||
function withTours(eb: ExpressionBuilder<DB, 'Ticket'>) {
|
||||
return jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('Tour')
|
||||
.selectAll('Tour')
|
||||
.whereRef('Tour.ticketId', '=', 'Ticket.id')
|
||||
.orderBy('Tour.operationId'),
|
||||
).as('tours');
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TicketRepository {
|
||||
private logger = new Logger(TicketRepository.name);
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@Inject(KYSELY_CLIENT_PROVIDER_TOKEN) private readonly db: Kysely<DB>,
|
||||
) {}
|
||||
|
||||
async createTicket(tours: Tour[]) {
|
||||
this.logger.log(`Creating Ticket for ${tours.length} tours`);
|
||||
try {
|
||||
const ticket = await this.db
|
||||
.insertInto('Ticket')
|
||||
.values({
|
||||
currentState: TicketValidationState.CREATED,
|
||||
})
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
|
||||
await this.db
|
||||
.insertInto('TicketStateHistory')
|
||||
.values({
|
||||
state: TicketValidationState.CREATED,
|
||||
ticketId: ticket.id,
|
||||
})
|
||||
.executeTakeFirst();
|
||||
|
||||
await this.db
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
ticketId: ticket.id,
|
||||
})
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
tours.map(({ id }) => id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return ticket;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async addToursToTicket(ticketId: string, tours: Tour[]) {
|
||||
await this.db
|
||||
.updateTable('Tour')
|
||||
.set({
|
||||
ticketId: ticketId,
|
||||
})
|
||||
.where(
|
||||
'id',
|
||||
'in',
|
||||
tours.map(({ id }) => id),
|
||||
)
|
||||
.execute();
|
||||
|
||||
return this.db
|
||||
.selectFrom('Ticket')
|
||||
.where('id', '=', ticketId)
|
||||
.selectAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
setActionablesOnTicket(ticketId: string, errors: string[], notes: string[]) {
|
||||
return this.db
|
||||
.updateTable('Ticket')
|
||||
.set({ errors, notes })
|
||||
.where('id', '=', ticketId)
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async setStateOfTicket(ticketId: string, state: TicketValidationState) {
|
||||
await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({
|
||||
currentState: state,
|
||||
})
|
||||
.where('Ticket.id', '=', ticketId)
|
||||
.execute();
|
||||
|
||||
return this.db
|
||||
.insertInto('TicketStateHistory')
|
||||
.values({ state, ticketId })
|
||||
.returningAll()
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
async updateTicket(
|
||||
ticketId: string,
|
||||
data: {
|
||||
state?: TicketValidationState;
|
||||
errors?: string[];
|
||||
notes?: string[];
|
||||
isUrgent?: boolean;
|
||||
urgency?: Date;
|
||||
approvalState?: ApprovalState;
|
||||
documentInfo?: DocumentInfoArgs;
|
||||
approvalPath?: string;
|
||||
},
|
||||
) {
|
||||
const { state, ...ticketData } = data;
|
||||
|
||||
await this.db
|
||||
.updateTable('Ticket')
|
||||
.set({ ...ticketData })
|
||||
.where('id', '=', ticketId)
|
||||
.execute();
|
||||
|
||||
if (state) {
|
||||
await this.setStateOfTicket(ticketId, state);
|
||||
}
|
||||
|
||||
return this.db
|
||||
.selectFrom('Ticket')
|
||||
.where('id', '=', ticketId)
|
||||
.selectAll('Ticket')
|
||||
.select((eb) => withTours(eb))
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
findById(id: string) {
|
||||
return this.prisma.ticket.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.ARCHIVED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAllCreated() {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.USER_INTERACTION_REQUIRED,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findAllClosed() {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
tours: {
|
||||
every: {
|
||||
check: {
|
||||
not: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
search(query: string) {
|
||||
return this.prisma.ticket.findMany({
|
||||
where: {
|
||||
state: {
|
||||
none: {
|
||||
state: TicketValidationState.ARCHIVED,
|
||||
},
|
||||
},
|
||||
tours: {
|
||||
some: {
|
||||
OR: [
|
||||
...query.split(' ').flatMap((word) => [
|
||||
{
|
||||
patientName: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
patientSurname: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
{
|
||||
operationId: {
|
||||
contains: word,
|
||||
mode: Prisma.QueryMode.insensitive,
|
||||
},
|
||||
},
|
||||
]),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
take: 5,
|
||||
include: {
|
||||
tours: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async findByState(
|
||||
state: TicketValidationState,
|
||||
filter: TicketFilterArgs,
|
||||
take?: number,
|
||||
skip?: number,
|
||||
) {
|
||||
const query: Prisma.TicketFindManyArgs = {
|
||||
...{
|
||||
...(skip ? { skip, take: 10 } : {}),
|
||||
...(take ? { take } : {}),
|
||||
...(take && !skip ? { skip: 0 } : {}),
|
||||
},
|
||||
where: {
|
||||
AND: createPrismaQueryFromTicketFilters(filter),
|
||||
currentState: state,
|
||||
tours: {
|
||||
some: {
|
||||
check: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
};
|
||||
|
||||
const [tickets, count] = await this.prisma.$transaction([
|
||||
this.prisma.ticket.findMany({ ...query }),
|
||||
this.prisma.ticket.count({ where: query.where }),
|
||||
]);
|
||||
return {
|
||||
tickets,
|
||||
total: count,
|
||||
};
|
||||
}
|
||||
|
||||
findPatientById(patientId: string): Promise<Patient> {
|
||||
return this.prisma.patient.findUnique({ where: { id: patientId } });
|
||||
}
|
||||
|
||||
findStatesByTicketId(
|
||||
ticketId: string,
|
||||
): Promise<{ state: TicketValidationState }[]> {
|
||||
return this.prisma.ticketStateHistory.findMany({
|
||||
where: {
|
||||
ticketId,
|
||||
},
|
||||
select: {
|
||||
state: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findToursByTicketId(ticketId: string): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
ticketId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async searchTicketIDForTour(tour: Tour): Promise<string | undefined> {
|
||||
const tours = await Promise.all([
|
||||
this.findFirstRecurringTour(tour),
|
||||
this.findTourOnSameDayWithOppositeDirection(tour),
|
||||
]);
|
||||
const firstFoundTour = tours.filter((tour) => !!tour)[0];
|
||||
if (firstFoundTour) {
|
||||
const ticket = await this.prisma.ticket.findFirst({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
tours: {
|
||||
some: {
|
||||
id: firstFoundTour.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return ticket?.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findFirstRecurringTour(tour: Tour): Promise<Tour | null> {
|
||||
const institutionStreet =
|
||||
tour.direction === 'Hinfahrt' ? tour.targetStreet : tour.startStreet;
|
||||
return this.prisma.tour.findFirst({
|
||||
where: {
|
||||
type: 'Serie',
|
||||
category: tour.category,
|
||||
ordinanceType: tour.ordinanceType,
|
||||
patientId: tour.patientId,
|
||||
patientName: tour.patientName,
|
||||
patientSurname: tour.patientSurname,
|
||||
patientStreet: tour.patientStreet,
|
||||
patientCity: tour.patientCity,
|
||||
patientZip: tour.patientZip,
|
||||
OR: [
|
||||
{ targetStreet: institutionStreet },
|
||||
{ startStreet: institutionStreet },
|
||||
],
|
||||
rangeEndDate: {
|
||||
gte: tour.startDate,
|
||||
},
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
startDate: 'desc',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findTourOnSameDayWithOppositeDirection(tour: Tour): Promise<Tour | null> {
|
||||
return this.prisma.tour.findFirst({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gt: startOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: endOfDay(new Date(tour.startDate)),
|
||||
},
|
||||
},
|
||||
{
|
||||
startStreet: tour.targetStreet,
|
||||
},
|
||||
{
|
||||
targetStreet: tour.startStreet,
|
||||
},
|
||||
{
|
||||
direction: tour.direction === 'Hinfahrt' ? 'Rückfahrt' : 'Hinfahrt',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findToursWithTicket(interval: Interval): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
ticketId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
removeTourFromTicket(tourId: string): Promise<Tour> {
|
||||
return this.prisma.tour.update({
|
||||
where: {
|
||||
id: tourId,
|
||||
},
|
||||
data: {
|
||||
Ticket: {
|
||||
disconnect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
findTours(interval: Interval): Promise<Tour[]> {
|
||||
return this.prisma.tour.findMany({
|
||||
where: {
|
||||
AND: [
|
||||
{
|
||||
startDate: {
|
||||
gte: new Date(interval.start),
|
||||
},
|
||||
},
|
||||
{
|
||||
startDate: {
|
||||
lt: new Date(interval.end),
|
||||
},
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
ticketId: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: 'b7424477-a4b2-42f7-b7e7-a4bef24f6979',
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: '3554e1a9-9c07-45e5-b53c-692a77e4fa9a',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteEmptyTickets(): Promise<Prisma.BatchPayload> {
|
||||
return this.prisma.ticket.deleteMany({
|
||||
where: {
|
||||
tours: {
|
||||
none: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,15 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Ticket, TicketValidationState, Tour } from '@prisma/client';
|
||||
import { Interval } from 'date-fns';
|
||||
import { DocumentInfoArgs } from './ticket-arg.types';
|
||||
import { TicketLifecycleService } from './ticket-lifecycle.service';
|
||||
import {
|
||||
TicketSystemEventTypes,
|
||||
TicketSystemValidatedEvent,
|
||||
} from './ticket-system.events';
|
||||
import { TicketRepository } from './ticket.repository';
|
||||
|
||||
@Injectable()
|
||||
export class TicketService {
|
||||
constructor(
|
||||
private readonly repository: TicketRepository,
|
||||
private readonly lifecycle: TicketLifecycleService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
|
@ -26,30 +22,4 @@ export class TicketService {
|
|||
new TicketSystemValidatedEvent(0),
|
||||
);
|
||||
}
|
||||
|
||||
async updateDocumentInfo(ticketId: string, documentInfo: DocumentInfoArgs) {
|
||||
return this.lifecycle.validateDocumentInfoUpdate(ticketId, documentInfo);
|
||||
}
|
||||
|
||||
findById(id: string): Promise<Ticket> {
|
||||
return this.repository.findById(id);
|
||||
}
|
||||
|
||||
findAll(): Promise<Ticket[]> {
|
||||
return this.repository.findAll() as Promise<Ticket[]>;
|
||||
}
|
||||
|
||||
searchTickets(query: string): Promise<Ticket[]> {
|
||||
return this.repository.search(query);
|
||||
}
|
||||
|
||||
findToursByTicketId(ticketId: string): Promise<Tour[]> {
|
||||
return this.repository.findToursByTicketId(ticketId);
|
||||
}
|
||||
|
||||
findStatesByTicketId(ticketId: string): Promise<TicketValidationState[]> {
|
||||
return this.repository
|
||||
.findStatesByTicketId(ticketId)
|
||||
.then((states) => states.map(({ state }) => state));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue