import type { SupabaseClient } from "@supabase/supabase-js"; import type { Database } from '@kit/supabase/database'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; import type { AnalysisOrder } from "~/lib/types/order"; import { createUserAnalysesApi } from "@/packages/features/user-analyses/src/server/api"; import { getAnalysisOrder } from "../order.service"; import { createMedipostActionLogForError, createMedipostActionLogForSuccess, getMedipostActionLog } from "./medipostMessageBase.service"; import type { Logger } from './types'; import MedipostMessageClient from './medipostMessageClient.service'; import MedipostMessageParser from './medipostMessageParser.service'; import MedipostAnalysisResultService from './medipostAnalysisResult.service'; import { validateOrderPerson } from "./medipostValidate.service"; interface IPrivateMessageSyncResult { messageId: string | null; hasAnalysisResponse: boolean; hasPartialAnalysisResponse: boolean; hasFullAnalysisResponse: boolean; medusaOrderId: string | undefined; analysisOrderId: number | undefined; } const NO_RESULT: IPrivateMessageSyncResult = { messageId: null, hasAnalysisResponse: false, hasPartialAnalysisResponse: false, hasFullAnalysisResponse: false, medusaOrderId: undefined, analysisOrderId: undefined, }; export default class MedipostPrivateMessageSync { private readonly client: SupabaseClient; private readonly userAnalysesApi: ReturnType; private readonly messageClient: MedipostMessageClient; private readonly messageParser: MedipostMessageParser; private readonly analysisResultService: MedipostAnalysisResultService; private loggerContext: { analysisOrderId?: number; orderNumber?: string; medipostPrivateMessageId?: string; } = {}; constructor() { this.client = getSupabaseServerAdminClient(); this.userAnalysesApi = createUserAnalysesApi(this.client); this.messageClient = new MedipostMessageClient(); this.messageParser = new MedipostMessageParser(); this.analysisResultService = new MedipostAnalysisResultService(); } public async handleNextPrivateMessage({ excludedMessageIds, }: { excludedMessageIds: string[]; }): Promise { let medipostPrivateMessageId: string | null = null; let hasAnalysisResponse = false; let hasPartialAnalysisResponse = false; let hasFullAnalysisResponse = false; let medusaOrderId: string | undefined = undefined; let medipostExternalOrderId: number | undefined = undefined; try { const privateMessage = await this.messageClient.getLatestPrivateMessageListItem({ excludedMessageIds, }); medipostPrivateMessageId = privateMessage?.messageId ?? null; if (!medipostPrivateMessageId) { return NO_RESULT; } this.loggerContext.medipostPrivateMessageId = medipostPrivateMessageId; if (await getMedipostActionLog({ medipostPrivateMessageId })) { this.logger()(`Medipost action log already exists for private message`); return { ...NO_RESULT, messageId: medipostPrivateMessageId }; } const { message: privateMessageContent, xml: privateMessageXml } = await this.messageClient.getPrivateMessage(medipostPrivateMessageId); const parseResult = this.messageParser.parseMessage(privateMessageContent); if (!parseResult.success) { const createErrorLog = async () => createMedipostActionLogForError({ privateMessageXml, medipostPrivateMessageId: medipostPrivateMessageId!, medipostExternalOrderId: parseResult.medipostExternalOrderIdRaw?.toString() ?? '', }); switch (parseResult.reason) { case 'no_analysis_result': console.info(`Missing results in private message, id=${medipostPrivateMessageId}`); break; case 'invalid_order_id': console.error(`Invalid order id in private message, id=${medipostPrivateMessageId}`); await createErrorLog(); break; case 'invalid_patient_code': console.error(`Invalid patient personal code in private message, id=${medipostPrivateMessageId}`); await createErrorLog(); break; } return { ...NO_RESULT, messageId: medipostPrivateMessageId, analysisOrderId: parseResult.medipostExternalOrderId, }; } const { analysisResult: analysisResultResponse, orderNumber, medipostExternalOrderIdRaw, patientPersonalCode, } = parseResult.data; this.loggerContext.orderNumber = orderNumber; medipostExternalOrderId = parseResult.data.medipostExternalOrderId; this.loggerContext.analysisOrderId = medipostExternalOrderId; let analysisOrder: AnalysisOrder; try { this.logger()(`Getting analysis order for message`); analysisOrder = await getAnalysisOrder({ analysisOrderId: medipostExternalOrderId }); medusaOrderId = analysisOrder.medusa_order_id; } catch (e) { this.logger()("Get analysis order error", "error", e as Error); await this.messageClient.deletePrivateMessage({ medipostPrivateMessageId }); throw new Error( `No analysis order found for Medipost message ValisTellimuseId=${medipostExternalOrderIdRaw}`, ); } await validateOrderPerson({ analysisOrder, patientPersonalCode }); this.logger()('Storing analysis results'); const result = await this.analysisResultService.storeAnalysisResult({ messageResponse: analysisResultResponse, analysisOrder, log: this.logger(), }); this.logger()('Creating medipost action log for success'); await createMedipostActionLogForSuccess({ privateMessageXml, medipostPrivateMessageId, medusaOrderId, medipostExternalOrderId: medipostExternalOrderIdRaw.toString(), }); if (result.isPartial) { this.logger()('Updating analysis order status to PARTIAL_ANALYSIS_RESPONSE'); await this.userAnalysesApi.updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'PARTIAL_ANALYSIS_RESPONSE', }); hasAnalysisResponse = true; hasPartialAnalysisResponse = true; } else if (result.isCompleted) { this.logger()('Updating analysis order status to FULL_ANALYSIS_RESPONSE'); await this.userAnalysesApi.updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'FULL_ANALYSIS_RESPONSE', }); await this.messageClient.deletePrivateMessage({ medipostPrivateMessageId }); hasAnalysisResponse = true; hasFullAnalysisResponse = true; } this.logger()('Sending analysis results notification'); await this.userAnalysesApi.sendAnalysisResultsNotification({ hasFullAnalysisResponse, hasPartialAnalysisResponse, analysisOrderId: medipostExternalOrderId, }); this.logger()('Successfully synced analysis results'); } catch (e) { console.warn( `Failed to process private message id=${medipostPrivateMessageId}, message=${(e as Error).message}`, ); } finally { this.clearLoggerContext(); } return { messageId: medipostPrivateMessageId, hasAnalysisResponse, hasPartialAnalysisResponse, hasFullAnalysisResponse, medusaOrderId, analysisOrderId: medipostExternalOrderId, }; } private logger(): Logger { const { analysisOrderId, orderNumber, medipostPrivateMessageId } = this.loggerContext; return (message, level = 'info', error) => { const messageFormatted = `[${analysisOrderId ?? ''}] [${orderNumber ?? '-'}] [${medipostPrivateMessageId ?? '-'}] ${message}`; const logFn = console[level]; if (error) { logFn(messageFormatted, error); } else { logFn(messageFormatted); } }; } private clearLoggerContext(): void { this.loggerContext = {}; } }