293 lines
9.0 KiB
TypeScript
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;
|
|
}
|
|
}
|