224 lines
8.0 KiB
TypeScript
224 lines
8.0 KiB
TypeScript
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<Database>;
|
|
private readonly userAnalysesApi: ReturnType<typeof createUserAnalysesApi>;
|
|
|
|
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<IPrivateMessageSyncResult> {
|
|
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 = {};
|
|
}
|
|
|
|
}
|