'use server'; import { MedipostAction } from '@/lib/types/medipost'; import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api'; import type { ResponseUuringuGrupp, UuringElement, } from '@/packages/shared/src/types/medipost-analysis'; import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; import axios from 'axios'; import { toArray } from '@kit/shared/utils'; import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element'; import { getAccountAdmin } from '../account.service'; import { getAnalyses } from '../analyses.service'; import { getAnalysisElementsAdmin } from '../analysis-element.service'; import { logMedipostDispatch } from '../audit.service'; import { getAnalysisOrder } from '../order.service'; import { MedipostValidationError } from './MedipostValidationError'; import { upsertMedipostActionLog, } from './medipostMessageBase.service'; import { validateMedipostResponse } from './medipostValidate.service'; import { OrderedAnalysisElement, composeOrderXML } from './medipostXML.service'; import type { Logger } from './types'; const BASE_URL = process.env.MEDIPOST_URL!; const USER = process.env.MEDIPOST_USER!; const PASSWORD = process.env.MEDIPOST_PASSWORD!; const RECIPIENT = process.env.MEDIPOST_RECIPIENT!; export async function canCreateAnalysisResponseElement({ existingElements, groupUuring: { UuringuElement: { UuringOlek: status, UuringId: analysisElementOriginalId }, }, responseValue, log, }: { existingElements: Pick< AnalysisResponseElement, 'analysis_element_original_id' | 'status' | 'response_value' >[]; groupUuring: { UuringuElement: Pick; }; responseValue: number | null; log: Logger; }) { const existingAnalysisResponseElement = existingElements.find( ({ analysis_element_original_id }) => analysis_element_original_id === analysisElementOriginalId, ); if (!existingAnalysisResponseElement) { return true; } if (Number(existingAnalysisResponseElement.status) > status) { log( `Analysis response element id=${analysisElementOriginalId} already exists for order in higher status ${existingAnalysisResponseElement.status} than ${status}`, ); return false; } if (existingAnalysisResponseElement.response_value && !responseValue) { log( `Analysis response element id=${analysisElementOriginalId} ${existingAnalysisResponseElement.response_value} but new response has no value`, ); return false; } return true; } export async function getAnalysisResponseElementsForGroup({ analysisGroup, existingElements, log, }: { analysisGroup: Pick; existingElements: Pick< AnalysisResponseElement, 'analysis_element_original_id' | 'status' | 'response_value' >[]; log: Logger; }) { const groupUuringItems = toArray( analysisGroup.Uuring as ResponseUuringuGrupp['Uuring'], ); log( `Order has results in group '${analysisGroup.UuringuGruppNimi}' for ${groupUuringItems.length} analysis elements`, ); const results: Omit< AnalysisResponseElement, 'created_at' | 'updated_at' | 'id' | 'analysis_response_id' >[] = []; for (const groupUuring of groupUuringItems) { const groupUuringElement = groupUuring.UuringuElement; const elementAnalysisResponses = toArray(groupUuringElement.UuringuVastus); const status = groupUuringElement.UuringOlek; log( `Group uuring '${analysisGroup.UuringuGruppNimi}' has status ${status}`, ); for (const response of elementAnalysisResponses) { const analysisElementOriginalId = groupUuringElement.UuringId; const vastuseVaartus = response.VastuseVaartus; const responseValue = (() => { const valueAsNumber = Number(vastuseVaartus); if (isNaN(valueAsNumber)) { return null; } return valueAsNumber; })(); if ( !(await canCreateAnalysisResponseElement({ existingElements, groupUuring, responseValue, log, })) ) { continue; } const mappedResponse = createUserAnalysesApi( getSupabaseServerAdminClient(), ).mapUuringVastus({ uuringVastus: response }); results.push({ analysis_element_original_id: analysisElementOriginalId, norm_lower: mappedResponse.normLower, norm_lower_included: mappedResponse.normLowerIncluded, norm_status: mappedResponse.normStatus, norm_upper: mappedResponse.normUpper, norm_upper_included: mappedResponse.normUpperIncluded, response_time: mappedResponse.responseTime, response_value: mappedResponse.responseValue, unit: groupUuringElement.Mootyhik ?? null, original_response_element: groupUuringElement, analysis_name: groupUuringElement.UuringNimi || groupUuringElement.KNimetus, comment: groupUuringElement.UuringuKommentaar ?? null, status: status.toString(), response_value_is_within_norm: mappedResponse.responseValueIsWithinNorm, response_value_is_negative: mappedResponse.responseValueIsNegative, }); } } return results; } export async function sendPrivateMessage(messageXml: string) { const body = new FormData(); body.append('Action', MedipostAction.SendPrivateMessage); body.append('User', USER); body.append('Password', PASSWORD); body.append('Receiver', RECIPIENT); body.append('MessageType', 'Tellimus'); body.append( 'Message', new Blob([messageXml], { type: 'text/xml; charset=UTF-8', }), ); const { data } = await axios.post(BASE_URL, body); await validateMedipostResponse(data); } export async function sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements, }: { medusaOrderId: string; orderedAnalysisElements: OrderedAnalysisElement[]; }) { const medreportOrder = await getAnalysisOrder({ medusaOrderId }); const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id, }); const orderedAnalysesIds = orderedAnalysisElements .map(({ analysisId }) => analysisId) .filter(Boolean) as number[]; const orderedAnalysisElementsIds = orderedAnalysisElements .map(({ analysisElementId }) => analysisElementId) .filter(Boolean) as number[]; const analyses = await getAnalyses({ ids: orderedAnalysesIds }); if (analyses.length !== orderedAnalysesIds.length) { throw new Error( `Got ${analyses.length} analyses, expected ${orderedAnalysesIds.length}`, ); } const analysisElements = await getAnalysisElementsAdmin({ ids: orderedAnalysisElementsIds, }); if (analysisElements.length !== orderedAnalysisElementsIds.length) { throw new Error( `Got ${analysisElements.length} analysis elements, expected ${orderedAnalysisElementsIds.length}`, ); } const orderXml = await composeOrderXML({ analyses, analysisElements, person: { idCode: account.personal_code!, firstName: account.name ?? '', lastName: account.last_name ?? '', phone: account.phone ?? '', }, orderId: medreportOrder.id, comment: '', }); try { await sendPrivateMessage(orderXml); } catch (e) { const isMedipostError = e instanceof MedipostValidationError; if (isMedipostError) { await logMedipostDispatch({ medusaOrderId, isSuccess: false, isMedipostError, errorMessage: e.response, }); console.error( `Failed to send order to Medipost, medusaOrderId=${medusaOrderId}, error=${e.response}`, ); await upsertMedipostActionLog({ action: 'send_order_to_medipost', xml: orderXml, hasAnalysisResults: false, medusaOrderId, responseXml: e.response, hasError: true, medipostPrivateMessageId: `send-order-to-medipost-${Date.now()}`, }); } else { console.error( `Failed to send order to Medipost, medusaOrderId=${medusaOrderId}, error=${e}`, ); await logMedipostDispatch({ medusaOrderId, isSuccess: false, isMedipostError, }); await upsertMedipostActionLog({ action: 'send_order_to_medipost', xml: orderXml, hasAnalysisResults: false, medusaOrderId, hasError: true, medipostPrivateMessageId: `send-order-to-medipost-${Date.now()}`, }); } throw e; } console.info( `Successfully sent order to Medipost, medusaOrderId=${medusaOrderId}`, ); await logMedipostDispatch({ medusaOrderId, isSuccess: true, isMedipostError: false, }); await upsertMedipostActionLog({ action: 'send_order_to_medipost', xml: orderXml, hasAnalysisResults: false, medusaOrderId, medipostPrivateMessageId: `send-order-to-medipost-${Date.now()}`, }); await createUserAnalysesApi( getSupabaseServerAdminClient(), ).updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING', }); }