avicenna/service/data-hub/data-connector/src/modules/feat-tickets/ticket-lifecycle.service.ts

293 lines
9.0 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import {
ApprovalState,
Ticket,
TicketValidationState,
Tour,
} from '@prisma/client';
import { Interval } from 'date-fns';
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';
@Injectable()
export class TicketLifecycleService {
private readonly logger = new Logger(TicketLifecycleService.name);
constructor(
private readonly patientService: PatientService,
private readonly ticketService: TicketService,
private readonly tourService: TourService,
) {}
async createTickets(interval: Interval): Promise<void> {
try {
const tours = await this.tourService.findInInterval(interval);
for (const tour of tours) {
if (tour.ticketId) {
continue;
}
const ticketId = await this.searchTicketIDForTour(tour);
if (ticketId) {
await this.ticketService.addToursToTicket(ticketId, [tour]);
} else {
await this.ticketService.createTicket([tour]);
}
}
await this.validateCreatedTickets();
// await this.archiveClosedTickets();
} catch (error) {
this.logger.error(error);
}
}
async validateDocumentInfoUpdate(
ticketId: string,
documentInfo: UpdateDocumentInfoDto,
) {
const ticket = await this.ticketService.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.ticketService.updateTicket(ticketId, {
documentInfo,
approvalState: approvalStateUpdate,
state: stateUpdate,
});
case TicketValidationState.TD_UNCERTAIN:
return this.ticketService.updateTicket(ticketId, {
documentInfo,
state:
ticket.approvalState === ApprovalState.FREE &&
documentInfo.tdLocation === 'company'
? TicketValidationState.ARCHIVED
: TicketValidationState.DOCUMENTS_MISSING,
});
case TicketValidationState.DOCUMENTS_MISSING:
return this.ticketService.updateTicket(ticketId, {
documentInfo,
...((ticket.approvalState === ApprovalState.FREE ||
documentInfo.approval.location === 'company') &&
documentInfo.tdLocation === 'company'
? { state: TicketValidationState.ARCHIVED }
: {}),
});
}
}
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.ticketService.findAllCreated();
this.logger.debug(`Found ${tickets.length} with state CREATED`);
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.ticketService.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.ticketService.updateTicket(ticket.id, {
errors,
notes,
state: TicketValidationState.TD_UNCERTAIN,
approvalState: ApprovalState.FREE,
});
} else {
await this.ticketService.updateTicket(ticket.id, {
errors,
notes,
state: TicketValidationState.USER_INTERACTION_REQUIRED,
approvalState: ApprovalState.FREE,
});
}
}
} else {
await this.ticketService.setActionablesOnTicket(
ticket.id,
errors,
notes,
);
}
}
}
private async archiveClosedTickets() {
const closedTickets = await this.ticketService.findAllClosed();
for (const closedTicket of closedTickets) {
await this.ticketService.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 {
const patient = await this.patientService.findById(patientId);
if (!patient) {
patientResult = validatePatientDataOnTours(ticket.tours);
} else {
patientResult = validatePatient(patient);
}
}
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)),
);
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.patientService.findById(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;
}
}