Move medipostPrivateMessage.service to separate classes, improve logging
This commit is contained in:
223
lib/services/medipost/medipostPrivateMessageSync.service.ts
Normal file
223
lib/services/medipost/medipostPrivateMessageSync.service.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
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 = {};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user