feat(MED-161): update sync private message

This commit is contained in:
2025-09-17 11:17:30 +03:00
parent ecc8c2b982
commit 2019c2c1fc
2 changed files with 184 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
import { getSupabaseServerAdminClient } from "@/packages/supabase/src/clients/server-admin-client";
import type { AnalysisResponseElement } from "../types/analysis-response-element";
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
export async function getExistingAnalysisResponseElements({
analysisResponseId,
@@ -15,3 +16,40 @@ export async function getExistingAnalysisResponseElements({
return data as AnalysisResponseElement[];
}
export async function upsertAnalysisResponse({
analysisOrderId,
orderNumber,
orderStatus,
userId,
}: {
analysisOrderId: number;
orderNumber: string;
orderStatus: typeof AnalysisOrderStatus[keyof typeof AnalysisOrderStatus];
userId: string;
}) {
const { data: analysisResponse } = await getSupabaseServerAdminClient()
.schema('medreport')
.from('analysis_responses')
.upsert(
{
analysis_order_id: analysisOrderId,
order_number: orderNumber,
order_status: orderStatus,
user_id: userId,
},
{ onConflict: 'order_number', ignoreDuplicates: false },
)
.select('id')
.throwOnError();
const analysisResponseId = analysisResponse?.[0]?.id;
if (!analysisResponseId) {
throw new Error(
`Failed to insert or update analysis order response (order id: ${analysisOrderId}, order number: ${orderNumber})`,
);
}
return { analysisResponseId };
}

View File

@@ -10,10 +10,12 @@ import {
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
import type {
ResponseUuringuGrupp,
MedipostOrderResponse
MedipostOrderResponse,
ResponseUuring,
} from '@/packages/shared/src/types/medipost-analysis';
import { toArray } from '@/lib/utils';
import type { AnalysisOrder } from '~/lib/types/analysis-order';
import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element';
import { Tables } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
@@ -27,7 +29,7 @@ import { composeOrderXML, OrderedAnalysisElement } from './medipostXML.service';
import { getAccountAdmin } from '../account.service';
import { logMedipostDispatch } from '../audit.service';
import { MedipostValidationError } from './MedipostValidationError';
import { getExistingAnalysisResponseElements } from '../analysis-order.service';
import { getExistingAnalysisResponseElements, upsertAnalysisResponse } from '../analysis-order.service';
const BASE_URL = process.env.MEDIPOST_URL!;
const USER = process.env.MEDIPOST_USER!;
@@ -56,6 +58,111 @@ export async function getLatestPrivateMessageListItem({
return getLatestMessage({ messages: data?.messages, excludedMessageIds });
}
const logger = (analysisOrder: AnalysisOrder, externalId: string, analysisResponseId: string) => (message: string, error?: PostgrestError | null) => {
const messageFormatted = `[${analysisOrder.id}] [${externalId}] [${analysisResponseId}] ${message}`;
if (error) {
console.info(messageFormatted, error);
} else {
console.info(messageFormatted);
}
};
function canCreateAnalysisResponseElement({
existingElements,
groupUuring: {
UuringuElement: {
UuringOlek: status,
UuringId: analysisElementOriginalId,
},
},
responseValue,
log,
}: {
existingElements: AnalysisResponseElement[];
groupUuring: ResponseUuring;
responseValue: number | null;
log: ReturnType<typeof 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} already exists for order with response value ${existingAnalysisResponseElement.response_value} but new response has no value`);
return false;
}
return true;
}
async function getAnalysisResponseElementsForGroup({
analysisResponseId,
analysisGroup,
log,
}: {
analysisResponseId: number;
analysisGroup: ResponseUuringuGrupp;
log: ReturnType<typeof logger>;
}) {
const groupUuringItems = toArray(analysisGroup.Uuring as ResponseUuringuGrupp['Uuring']);
log(`Order has results in group '${analysisGroup.UuringuGruppNimi}' for ${groupUuringItems.length} analysis elements`);
const existingElements = await getExistingAnalysisResponseElements({ analysisResponseId });
const results: Omit<AnalysisResponseElement, 'created_at' | 'updated_at' | '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 responseValue = (() => {
const valueAsNumber = Number(response.VastuseVaartus);
if (isNaN(valueAsNumber)) {
return null;
}
return valueAsNumber;
})();
if (!canCreateAnalysisResponseElement({ existingElements, groupUuring, responseValue, log })) {
continue;
}
results.push({
analysis_element_original_id: analysisElementOriginalId,
analysis_response_id: analysisResponseId,
norm_lower: response.NormAlum?.['#text'] ?? null,
norm_lower_included:
response.NormAlum?.['@_kaasaarvatud'].toLowerCase() === 'jah',
norm_status: response.NormiStaatus,
norm_upper: response.NormYlem?.['#text'] ?? null,
norm_upper_included:
response.NormYlem?.['@_kaasaarvatud'].toLowerCase() === 'jah',
response_time: response.VastuseAeg ?? null,
response_value: responseValue,
unit: groupUuringElement.Mootyhik ?? null,
original_response_element: groupUuringElement,
analysis_name: groupUuringElement.UuringNimi || groupUuringElement.KNimetus,
comment: groupUuringElement.UuringuKommentaar ?? null,
status: status.toString(),
});
}
}
return results;
}
export async function syncPrivateMessage({
messageResponse,
order,
@@ -63,111 +170,60 @@ export async function syncPrivateMessage({
messageResponse: NonNullable<MedipostOrderResponse['Saadetis']['Vastus']>;
order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
}) {
const supabase = getSupabaseServerAdminClient()
const supabase = getSupabaseServerAdminClient();
const { data: analysisOrder, error: analysisOrderError } = await supabase
const externalId = messageResponse.ValisTellimuseId;
const orderNumber = messageResponse.TellimuseNumber;
const orderStatus = AnalysisOrderStatus[messageResponse.TellimuseOlek];
const log = logger(order, externalId, orderNumber);
const { data: analysisOrder } = await supabase
.schema('medreport')
.from('analysis_orders')
.select('user_id')
.eq('id', order.id);
.select('id, user_id')
.eq('id', order.id)
.single()
.throwOnError();
if (analysisOrderError || !analysisOrder?.[0]?.user_id) {
throw new Error(
`Could not find analysis order with id ${messageResponse.ValisTellimuseId}`,
);
}
const { analysisResponseId } = await upsertAnalysisResponse({
analysisOrderId: order.id,
orderNumber,
orderStatus,
userId: analysisOrder.user_id,
});
const { data: analysisResponse, error } = await supabase
.schema('medreport')
.from('analysis_responses')
.upsert(
{
analysis_order_id: order.id,
order_number: messageResponse.TellimuseNumber,
order_status: AnalysisOrderStatus[messageResponse.TellimuseOlek],
user_id: analysisOrder[0].user_id,
},
{ onConflict: 'order_number', ignoreDuplicates: false },
)
.select('id');
if (error || !analysisResponse?.[0]?.id) {
throw new Error(
`Failed to insert or update analysis order response (external id: ${messageResponse?.TellimuseNumber})`,
);
}
const analysisGroups = toArray(messageResponse.UuringuGrupp);
console.info(`Order has results for ${analysisGroups.length} analysis groups`);
const responses: Omit<
Tables<{ schema: 'medreport' }, 'analysis_response_elements'>,
'id' | 'created_at' | 'updated_at'
>[] = [];
const analysisResponseId = analysisResponse[0]!.id;
log(`Order has results for ${analysisGroups.length} analysis groups`);
for (const analysisGroup of analysisGroups) {
const groupItems = toArray(
analysisGroup.Uuring as ResponseUuringuGrupp['Uuring'],
);
console.info(`Order has results in group ${analysisGroup.UuringuGruppNimi} for ${groupItems.length} analysis elements`);
for (const item of groupItems) {
const element = item.UuringuElement;
const elementAnalysisResponses = toArray(element.UuringuVastus);
log(`[${analysisGroups.indexOf(analysisGroup) + 1}/${analysisGroups.length}] Syncing analysis group '${analysisGroup.UuringuGruppNimi}'`);
responses.push(
...elementAnalysisResponses.map((response) => ({
analysis_element_original_id: element.UuringId,
analysis_response_id: analysisResponseId,
norm_lower: response.NormAlum?.['#text'] ?? null,
norm_lower_included:
response.NormAlum?.['@_kaasaarvatud'].toLowerCase() === 'jah',
norm_status: response.NormiStaatus,
norm_upper: response.NormYlem?.['#text'] ?? null,
norm_upper_included:
response.NormYlem?.['@_kaasaarvatud'].toLowerCase() === 'jah',
response_time: response.VastuseAeg ?? null,
response_value: response.VastuseVaartus,
unit: element.Mootyhik ?? null,
original_response_element: element,
analysis_name: element.UuringNimi || element.KNimetus,
comment: element.UuringuKommentaar ?? '',
})),
);
const elements = await getAnalysisResponseElementsForGroup({
analysisResponseId,
analysisGroup,
log,
});
for (const element of elements) {
const { error } = await supabase
.schema('medreport')
.from('analysis_response_elements')
.insert(element);
if (error) {
log(`Failed to insert order response elements for response id ${analysisResponseId} (order id: ${analysisOrder.id})`, error);
}
}
}
const { error: deleteError } = await supabase
.schema('medreport')
.from('analysis_response_elements')
.delete()
.eq('analysis_response_id', analysisResponseId);
if (deleteError) {
throw new Error(
`Failed to clean up response elements for response id ${analysisResponseId}`,
);
}
const { error: elementInsertError } = await supabase
.schema('medreport')
.from('analysis_response_elements')
.insert(responses);
if (elementInsertError) {
throw new Error(
`Failed to insert order response elements for response id ${analysisResponseId}`,
);
}
const existingAnalysisResponseElements = await getExistingAnalysisResponseElements({ analysisResponseId });
const allOrderResponseElements = await getExistingAnalysisResponseElements({ analysisResponseId });
const expectedOrderResponseElements = order.analysis_element_ids?.length ?? 0;
if (existingAnalysisResponseElements.length !== expectedOrderResponseElements) {
if (allOrderResponseElements.length !== expectedOrderResponseElements) {
return { isPartial: true };
}
const statusFromResponse = AnalysisOrderStatus[messageResponse.TellimuseOlek];
return { isCompleted: statusFromResponse === 'COMPLETED' };
return { isCompleted: orderStatus === 'COMPLETED' };
}
export async function readPrivateMessageResponse({