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 { 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 { 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 { 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 { 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 { 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 { // 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; } }