Files
medreport_mrb2b/lib/services/medipost/medipostPrivateMessage.service.ts

292 lines
8.9 KiB
TypeScript

'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<UuringElement, 'UuringOlek' | 'UuringId'>;
};
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<ResponseUuringuGrupp, 'UuringuGruppNimi' | 'Uuring'>;
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',
});
}