Compare commits
1 Commits
5ef7f58f5d
...
269b4c3e27
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
269b4c3e27 |
@@ -59,3 +59,37 @@ MONTONIO_API_URL=https://sandbox-stargate.montonio.com
|
|||||||
|
|
||||||
# JOBS
|
# JOBS
|
||||||
JOBS_API_TOKEN=73ce073c-6dd4-11f0-8e75-8fee89786197
|
JOBS_API_TOKEN=73ce073c-6dd4-11f0-8e75-8fee89786197
|
||||||
|
|
||||||
|
|
||||||
|
MEDUSA_BACKEND_URL=http://5.181.51.38:9000
|
||||||
|
MEDUSA_BACKEND_PUBLIC_URL=http://5.181.51.38:9000
|
||||||
|
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_0ec86252438b38ce18d5601f7877e4395d7e0a6afa8687dfea8d37af33015633
|
||||||
|
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL=http://5.181.51.38:54321
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
|
||||||
|
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL=https://klocrucggryikaxzvxgc.supabase.co
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imtsb2NydWNnZ3J5aWtheHp2eGdjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTY5ODQ2MjgsImV4cCI6MjA3MjU2MDYyOH0.2XOQngowcymiSUZO_XEEWAWzco2uRIjwG7TAeRRLIdU
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imtsb2NydWNnZ3J5aWtheHp2eGdjIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc1Njk4NDYyOCwiZXhwIjoyMDcyNTYwNjI4fQ.1UZR7AqSD9bOy1gtZRGhOCNoESsw2W-DoFDDsNNMwoE
|
||||||
|
|
||||||
|
MEDUSA_BACKEND_URL=https://backoffice-test.medreport.ee
|
||||||
|
MEDUSA_BACKEND_PUBLIC_URL=https://backoffice-test.medreport.ee
|
||||||
|
MEDUSA_SECRET_API_KEY=sk_5ac1c1c12c144cd744b6c881050d459e339ddf6a3d14eda271a0cc4f9d3812cb
|
||||||
|
NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_e740b9ca22b31c4b44862044f001dbcf8f46d47d40f430733d0c75bef14d2d6a
|
||||||
|
|
||||||
|
#MEDUSA_BACKEND_URL=https://backoffice.medreport.ee
|
||||||
|
#MEDUSA_BACKEND_PUBLIC_URL=https://backoffice.medreport.ee
|
||||||
|
#MEDUSA_SECRET_API_KEY=sk_fdb1808fbabf62979cc46316aa997378ffbb87882883e8f5c3ee47cee39dcac5
|
||||||
|
#NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY=pk_068d930c33fea53608a410d84a51935f6ce2ccec5bef8e0ecf75eaee602ac486
|
||||||
|
|
||||||
|
# PROD
|
||||||
|
NEXT_PUBLIC_SUPABASE_URL=https://oqsdacktkhmbylmzstjq.supabase.co
|
||||||
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9xc2RhY2t0a2htYnlsbXpzdGpxIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDY1MjgxMjMsImV4cCI6MjA2MjEwNDEyM30.LdHCTWxijFmhXdnT9KVuLRAVbtSwY7OO-oLtpd8GmO0
|
||||||
|
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9xc2RhY2t0a2htYnlsbXpzdGpxIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0NjUyODEyMywiZXhwIjoyMDYyMTA0MTIzfQ.KVcnkZ21Pd0XkJho23dZqFHawVTLQqfvF7l2RxsELLk
|
||||||
|
MEDIPOST_URL=https://medipost2.medisoft.ee:8443/Medipost/MedipostServlet
|
||||||
|
MEDIPOST_USER=medreport
|
||||||
|
MEDIPOST_PASSWORD=85MXFFDB7
|
||||||
|
MEDIPOST_RECIPIENT=HTI
|
||||||
|
MEDIPOST_MESSAGE_SENDER=medreport
|
||||||
|
MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK=false
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import MedipostPrivateMessageSync from '~/lib/services/medipost/medipostPrivateMessageSync.service';
|
import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api';
|
||||||
|
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
|
||||||
|
|
||||||
|
import MedipostResultsSyncService from '~/lib/services/medipost/medipostResultsSync.service';
|
||||||
|
|
||||||
type ProcessedMessage = {
|
type ProcessedMessage = {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
@@ -16,22 +19,31 @@ type GroupedResults = {
|
|||||||
|
|
||||||
export default async function syncAnalysisResults() {
|
export default async function syncAnalysisResults() {
|
||||||
console.info('Syncing analysis results');
|
console.info('Syncing analysis results');
|
||||||
const sync = new MedipostPrivateMessageSync();
|
const supabase = getSupabaseServerAdminClient();
|
||||||
|
const api = createUserAnalysesApi(supabase);
|
||||||
|
const syncService = new MedipostResultsSyncService();
|
||||||
|
|
||||||
const processedMessages: ProcessedMessage[] = [];
|
const processedMessages: ProcessedMessage[] = [];
|
||||||
const excludedMessageIds: string[] = [];
|
const excludedMessageIds: string[] = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
const result = await sync.handleNextPrivateMessage({ excludedMessageIds });
|
const result = await syncService.readPrivateMessageResponse({ excludedMessageIds });
|
||||||
|
if (result.messageId) {
|
||||||
|
processedMessages.push(result as ProcessedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
const { messageId } = result;
|
// await api.sendAnalysisResultsNotification({
|
||||||
if (!messageId) {
|
// hasFullAnalysisResponse: result.hasFullAnalysisResponse,
|
||||||
|
// hasPartialAnalysisResponse: result.hasAnalysisResponse,
|
||||||
|
// analysisOrderId: result.analysisOrderId,
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (!result.messageId) {
|
||||||
console.info('No more messages to process');
|
console.info('No more messages to process');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processedMessages.push(result as ProcessedMessage);
|
if (!excludedMessageIds.includes(result.messageId)) {
|
||||||
if (!excludedMessageIds.includes(messageId)) {
|
excludedMessageIds.push(result.messageId);
|
||||||
excludedMessageIds.push(messageId);
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export async function POST(request: Request) {
|
|||||||
action: 'send_fake_analysis_results_to_medipost',
|
action: 'send_fake_analysis_results_to_medipost',
|
||||||
xml: messageXml,
|
xml: messageXml,
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
medipostPrivateMessageId: `fake-response-${Date.now()}`,
|
|
||||||
});
|
});
|
||||||
await sendPrivateMessageTestResponse({ messageXml });
|
await sendPrivateMessageTestResponse({ messageXml });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { ArrowLeft, MessageCircle } from 'lucide-react';
|
import { ArrowLeft, MessageCircle } from 'lucide-react';
|
||||||
@@ -21,22 +20,6 @@ const ErrorPage = ({
|
|||||||
}) => {
|
}) => {
|
||||||
useCaptureException(error);
|
useCaptureException(error);
|
||||||
|
|
||||||
// Ignore next.js internal transient navigation errors that occur during auth state changes
|
|
||||||
const isTransientNavigationError =
|
|
||||||
error?.message?.includes('Error in input stream') ||
|
|
||||||
error?.message?.includes('AbortError') ||
|
|
||||||
(error?.name === 'ChunkLoadError');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isTransientNavigationError && typeof window !== 'undefined') {
|
|
||||||
window.location.href = '/';
|
|
||||||
}
|
|
||||||
}, [isTransientNavigationError]);
|
|
||||||
|
|
||||||
if (isTransientNavigationError) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-screen flex-1 flex-col'}>
|
<div className={'flex h-screen flex-1 flex-col'}>
|
||||||
<SiteHeader />
|
<SiteHeader />
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { ArrowLeft, MessageCircle } from 'lucide-react';
|
import { ArrowLeft, MessageCircle } from 'lucide-react';
|
||||||
@@ -21,22 +20,6 @@ const GlobalErrorPage = ({
|
|||||||
reset: () => void;
|
reset: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
useCaptureException(error);
|
useCaptureException(error);
|
||||||
|
|
||||||
// Ignore next.js internal transient navigation errors that occur during auth state changes
|
|
||||||
const isTransientNavigationError =
|
|
||||||
error?.message?.includes('Error in input stream') ||
|
|
||||||
error?.message?.includes('AbortError') ||
|
|
||||||
(error?.name === 'ChunkLoadError');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isTransientNavigationError && typeof window !== 'undefined') {
|
|
||||||
window.location.href = '/';
|
|
||||||
}
|
|
||||||
}, [isTransientNavigationError]);
|
|
||||||
|
|
||||||
if (isTransientNavigationError) {
|
|
||||||
return <div />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { pathsConfig } from '@kit/shared/config';
|
|||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
import { formatDateAndTime } from '@kit/shared/utils';
|
|
||||||
|
|
||||||
import { loadCurrentUserAccount } from '~/home/(user)/_lib/server/load-user-account';
|
import { loadCurrentUserAccount } from '~/home/(user)/_lib/server/load-user-account';
|
||||||
import { loadUserAnalysis } from '~/home/(user)/_lib/server/load-user-analysis';
|
import { loadUserAnalysis } from '~/home/(user)/_lib/server/load-user-analysis';
|
||||||
@@ -104,14 +103,7 @@ export default async function AnalysisResultsPage({
|
|||||||
<h6>
|
<h6>
|
||||||
<Trans i18nKey={`orders:status.${analysisResponse.order.status}`} />
|
<Trans i18nKey={`orders:status.${analysisResponse.order.status}`} />
|
||||||
<ButtonTooltip
|
<ButtonTooltip
|
||||||
content={
|
content={`${analysisResponse.order.createdAt ? new Date(analysisResponse?.order?.createdAt).toLocaleString() : ''}`}
|
||||||
<Trans
|
|
||||||
i18nKey="analysis-results:orderCreatedAt"
|
|
||||||
values={{
|
|
||||||
createdAt: formatDateAndTime(analysisResponse.order.createdAt)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
className="ml-6"
|
className="ml-6"
|
||||||
/>
|
/>
|
||||||
</h6>
|
</h6>
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
import type { PostgrestError } from "@supabase/supabase-js";
|
|
||||||
|
|
||||||
import { toArray } from '@kit/shared/utils';
|
|
||||||
import type { AnalysisOrder } from "~/lib/types/order";
|
|
||||||
import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element';
|
|
||||||
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
|
|
||||||
import type {
|
|
||||||
MedipostAnalysisResult,
|
|
||||||
ResponseUuringuGrupp,
|
|
||||||
} from '@/packages/shared/src/types/medipost-analysis';
|
|
||||||
|
|
||||||
import { getAnalysisResponseElementsForGroup } from "./medipostPrivateMessage.service";
|
|
||||||
import {
|
|
||||||
getExistingAnalysisResponseElements,
|
|
||||||
upsertAnalysisResponse,
|
|
||||||
upsertAnalysisResponseElement,
|
|
||||||
} from "../analysis-order.service";
|
|
||||||
import type { Logger } from './types';
|
|
||||||
|
|
||||||
type AnalysisResponseElementMapped = Omit<
|
|
||||||
AnalysisResponseElement,
|
|
||||||
'created_at' | 'updated_at' | 'id' | 'analysis_response_id'
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type SyncResult =
|
|
||||||
| {
|
|
||||||
isCompleted: boolean;
|
|
||||||
isPartial?: undefined;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
isPartial: boolean;
|
|
||||||
isCompleted?: undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class MedipostAnalysisResultService {
|
|
||||||
public async storeAnalysisResult({
|
|
||||||
messageResponse: {
|
|
||||||
TellimuseNumber: orderNumber,
|
|
||||||
TellimuseOlek,
|
|
||||||
UuringuGrupp,
|
|
||||||
},
|
|
||||||
analysisOrder,
|
|
||||||
log,
|
|
||||||
}: {
|
|
||||||
messageResponse: Pick<
|
|
||||||
NonNullable<MedipostAnalysisResult>,
|
|
||||||
'TellimuseNumber' | 'TellimuseOlek' | 'UuringuGrupp'
|
|
||||||
>;
|
|
||||||
analysisOrder: AnalysisOrder;
|
|
||||||
log: Logger;
|
|
||||||
}): Promise<SyncResult> {
|
|
||||||
const orderStatus = AnalysisOrderStatus[TellimuseOlek];
|
|
||||||
|
|
||||||
const { analysisResponseId } = await upsertAnalysisResponse({
|
|
||||||
analysisOrderId: analysisOrder.id,
|
|
||||||
orderNumber,
|
|
||||||
orderStatus,
|
|
||||||
userId: analysisOrder.user_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingElements = await getExistingAnalysisResponseElements({
|
|
||||||
analysisResponseId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const analysisGroups = toArray(UuringuGrupp);
|
|
||||||
log(`Order has results for ${analysisGroups.length} analysis groups`);
|
|
||||||
|
|
||||||
const newElements = await this.getNewAnalysisResponseElements({
|
|
||||||
analysisGroups,
|
|
||||||
existingElements,
|
|
||||||
log,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const element of newElements) {
|
|
||||||
try {
|
|
||||||
await upsertAnalysisResponseElement({
|
|
||||||
element: {
|
|
||||||
...element,
|
|
||||||
analysis_response_id: analysisResponseId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
log(
|
|
||||||
`Failed to create order response element for response id ${analysisResponseId}, element id '${element.analysis_element_original_id}'`,
|
|
||||||
"error",
|
|
||||||
e as PostgrestError,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasAllResults = await this.hasAllAnalysisResponseElements({
|
|
||||||
analysisResponseId,
|
|
||||||
analysisOrder,
|
|
||||||
});
|
|
||||||
|
|
||||||
log(`Order has ${hasAllResults ? 'all' : 'some'} results, status is ${orderStatus}`);
|
|
||||||
|
|
||||||
return hasAllResults
|
|
||||||
? { isCompleted: orderStatus === 'COMPLETED' }
|
|
||||||
: { isPartial: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getNewAnalysisResponseElements({
|
|
||||||
analysisGroups,
|
|
||||||
existingElements,
|
|
||||||
log,
|
|
||||||
}: {
|
|
||||||
analysisGroups: ResponseUuringuGrupp[];
|
|
||||||
existingElements: AnalysisResponseElement[];
|
|
||||||
log: Logger;
|
|
||||||
}): Promise<AnalysisResponseElementMapped[]> {
|
|
||||||
const newElements: AnalysisResponseElementMapped[] = [];
|
|
||||||
|
|
||||||
for (const analysisGroup of analysisGroups) {
|
|
||||||
log(
|
|
||||||
`[${analysisGroups.indexOf(analysisGroup) + 1}/${analysisGroups.length}] Syncing analysis group '${analysisGroup.UuringuGruppNimi}'`,
|
|
||||||
);
|
|
||||||
const elements = await getAnalysisResponseElementsForGroup({
|
|
||||||
analysisGroup,
|
|
||||||
existingElements,
|
|
||||||
log,
|
|
||||||
});
|
|
||||||
newElements.push(...elements);
|
|
||||||
}
|
|
||||||
|
|
||||||
return newElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async hasAllAnalysisResponseElements({
|
|
||||||
analysisResponseId,
|
|
||||||
analysisOrder,
|
|
||||||
}: {
|
|
||||||
analysisResponseId: number;
|
|
||||||
analysisOrder: Pick<AnalysisOrder, 'analysis_element_ids'>;
|
|
||||||
}): Promise<boolean> {
|
|
||||||
const allOrderResponseElements = await getExistingAnalysisResponseElements({
|
|
||||||
analysisResponseId,
|
|
||||||
});
|
|
||||||
const expectedOrderResponseElements = analysisOrder.analysis_element_ids?.length ?? 0;
|
|
||||||
return allOrderResponseElements.length >= expectedOrderResponseElements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,19 +29,6 @@ export async function getLatestMessage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMedipostActionLog({
|
|
||||||
medipostPrivateMessageId,
|
|
||||||
}: {
|
|
||||||
medipostPrivateMessageId: string;
|
|
||||||
}) {
|
|
||||||
const { data: existingRecord } = await getSupabaseServerAdminClient()
|
|
||||||
.schema('medreport').from('medipost_actions')
|
|
||||||
.select('id')
|
|
||||||
.eq('medipost_private_message_id', medipostPrivateMessageId)
|
|
||||||
.single();
|
|
||||||
return existingRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function upsertMedipostActionLog({
|
export async function upsertMedipostActionLog({
|
||||||
action,
|
action,
|
||||||
xml,
|
xml,
|
||||||
@@ -64,10 +51,6 @@ export async function upsertMedipostActionLog({
|
|||||||
medipostExternalOrderId?: string | null;
|
medipostExternalOrderId?: string | null;
|
||||||
medipostPrivateMessageId?: string | null;
|
medipostPrivateMessageId?: string | null;
|
||||||
}) {
|
}) {
|
||||||
if (typeof medipostPrivateMessageId !== 'string') {
|
|
||||||
throw new Error('medipostPrivateMessageId is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const recordData = {
|
const recordData = {
|
||||||
action,
|
action,
|
||||||
xml,
|
xml,
|
||||||
@@ -79,19 +62,18 @@ export async function upsertMedipostActionLog({
|
|||||||
medipost_private_message_id: medipostPrivateMessageId,
|
medipost_private_message_id: medipostPrivateMessageId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const existingActionLog = await getMedipostActionLog({ medipostPrivateMessageId });
|
const query = getSupabaseServerAdminClient()
|
||||||
if (existingActionLog) {
|
|
||||||
console.info(`Medipost action log already exists for private message id: ${medipostPrivateMessageId}`);
|
|
||||||
return { medipostActionId: existingActionLog.id };
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info(`Inserting medipost action log for private message id: ${medipostPrivateMessageId}`);
|
|
||||||
const { data } = await getSupabaseServerAdminClient()
|
|
||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
.from('medipost_actions')
|
.from('medipost_actions');
|
||||||
.insert(recordData)
|
const { data } = medipostPrivateMessageId
|
||||||
.select('id')
|
? await query
|
||||||
.throwOnError();
|
.upsert(recordData, {
|
||||||
|
onConflict: 'medipost_private_message_id',
|
||||||
|
ignoreDuplicates: false,
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.throwOnError()
|
||||||
|
: await query.insert(recordData).select('id').throwOnError();
|
||||||
|
|
||||||
const medipostActionId = data?.[0]?.id;
|
const medipostActionId = data?.[0]?.id;
|
||||||
if (!medipostActionId) {
|
if (!medipostActionId) {
|
||||||
@@ -102,46 +84,3 @@ export async function upsertMedipostActionLog({
|
|||||||
|
|
||||||
return { medipostActionId };
|
return { medipostActionId };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createMedipostActionLogForError({
|
|
||||||
privateMessageXml,
|
|
||||||
medipostPrivateMessageId,
|
|
||||||
medusaOrderId,
|
|
||||||
medipostExternalOrderId,
|
|
||||||
}: {
|
|
||||||
privateMessageXml: string;
|
|
||||||
medipostPrivateMessageId: string;
|
|
||||||
medusaOrderId?: string;
|
|
||||||
medipostExternalOrderId: string;
|
|
||||||
}) {
|
|
||||||
await upsertMedipostActionLog({
|
|
||||||
action: 'sync_analysis_results_from_medipost',
|
|
||||||
xml: privateMessageXml,
|
|
||||||
hasAnalysisResults: false,
|
|
||||||
medipostPrivateMessageId,
|
|
||||||
medusaOrderId,
|
|
||||||
medipostExternalOrderId,
|
|
||||||
hasError: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createMedipostActionLogForSuccess({
|
|
||||||
privateMessageXml,
|
|
||||||
medipostPrivateMessageId,
|
|
||||||
medusaOrderId,
|
|
||||||
medipostExternalOrderId,
|
|
||||||
}: {
|
|
||||||
privateMessageXml: string;
|
|
||||||
medipostPrivateMessageId: string;
|
|
||||||
medusaOrderId: string;
|
|
||||||
medipostExternalOrderId: string;
|
|
||||||
}) {
|
|
||||||
await upsertMedipostActionLog({
|
|
||||||
action: 'sync_analysis_results_from_medipost',
|
|
||||||
xml: privateMessageXml,
|
|
||||||
hasAnalysisResults: true,
|
|
||||||
medipostPrivateMessageId: medipostPrivateMessageId,
|
|
||||||
medusaOrderId,
|
|
||||||
medipostExternalOrderId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
import type { GetMessageListResponse } from '~/lib/types/medipost';
|
|
||||||
import { MedipostAction } from '~/lib/types/medipost';
|
|
||||||
import type { MedipostOrderResponse } from '@/packages/shared/src/types/medipost-analysis';
|
|
||||||
|
|
||||||
import { validateMedipostResponse } from './medipostValidate.service';
|
|
||||||
import { parseXML } from '../util/xml.service';
|
|
||||||
import { getLatestMessage } from './medipostMessageBase.service';
|
|
||||||
|
|
||||||
const BASE_URL = process.env.MEDIPOST_URL!;
|
|
||||||
const USER = process.env.MEDIPOST_USER!;
|
|
||||||
const PASSWORD = process.env.MEDIPOST_PASSWORD!;
|
|
||||||
|
|
||||||
const IS_ENABLED_DELETE_PRIVATE_MESSAGE =
|
|
||||||
process.env.MEDIPOST_ENABLE_DELETE_RESPONSE_PRIVATE_MESSAGE_ON_READ ===
|
|
||||||
'true';
|
|
||||||
|
|
||||||
export default class MedipostMessageClient {
|
|
||||||
public async getLatestPrivateMessageListItem({
|
|
||||||
excludedMessageIds,
|
|
||||||
}: {
|
|
||||||
excludedMessageIds: string[];
|
|
||||||
}) {
|
|
||||||
const { data } = await axios.get<GetMessageListResponse>(BASE_URL, {
|
|
||||||
params: {
|
|
||||||
Action: MedipostAction.GetPrivateMessageList,
|
|
||||||
User: USER,
|
|
||||||
Password: PASSWORD,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.code && data.code !== 0) {
|
|
||||||
throw new Error('Failed to get private message list');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await getLatestMessage({
|
|
||||||
messages: data?.messages,
|
|
||||||
excludedMessageIds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getPrivateMessage(messageId: string) {
|
|
||||||
const { data } = await axios.get(BASE_URL, {
|
|
||||||
params: {
|
|
||||||
Action: MedipostAction.GetPrivateMessage,
|
|
||||||
User: USER,
|
|
||||||
Password: PASSWORD,
|
|
||||||
MessageId: messageId,
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/xml',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await validateMedipostResponse(data, { canHaveEmptyCode: true });
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: (await parseXML(data)) as MedipostOrderResponse,
|
|
||||||
xml: data as string,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deletePrivateMessage({
|
|
||||||
medipostPrivateMessageId,
|
|
||||||
}: {
|
|
||||||
medipostPrivateMessageId: string;
|
|
||||||
}) {
|
|
||||||
if (!IS_ENABLED_DELETE_PRIVATE_MESSAGE) {
|
|
||||||
console.info(`Skipping delete private message id=${medipostPrivateMessageId} because deleting is not enabled`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await axios.get(BASE_URL, {
|
|
||||||
params: {
|
|
||||||
Action: MedipostAction.DeletePrivateMessage,
|
|
||||||
User: USER,
|
|
||||||
Password: PASSWORD,
|
|
||||||
MessageId: medipostPrivateMessageId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.code && data.code !== 0) {
|
|
||||||
throw new Error(`Failed to delete private message (id: ${medipostPrivateMessageId})`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import type { MedipostOrderResponse, MedipostAnalysisResult } from '@/packages/shared/src/types/medipost-analysis';
|
|
||||||
|
|
||||||
interface ParsedMessageData {
|
|
||||||
analysisResult: NonNullable<MedipostAnalysisResult>;
|
|
||||||
orderNumber: string;
|
|
||||||
medipostExternalOrderId: number;
|
|
||||||
medipostExternalOrderIdRaw: string | number;
|
|
||||||
patientPersonalCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParseMessageResult =
|
|
||||||
| {
|
|
||||||
success: true;
|
|
||||||
data: ParsedMessageData;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
success: false;
|
|
||||||
reason: 'no_analysis_result' | 'invalid_order_id' | 'invalid_patient_code';
|
|
||||||
medipostExternalOrderIdRaw?: string | number;
|
|
||||||
medipostExternalOrderId?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class MedipostMessageParser {
|
|
||||||
public extractAnalysisResult(
|
|
||||||
message: MedipostOrderResponse,
|
|
||||||
): ParsedMessageData['analysisResult'] | null {
|
|
||||||
return message?.Saadetis?.Vastus ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public extractOrderId(
|
|
||||||
message: MedipostOrderResponse,
|
|
||||||
analysisResult: ParsedMessageData['analysisResult'],
|
|
||||||
): { orderId: number; rawOrderId: string | number } | null {
|
|
||||||
const rawOrderId =
|
|
||||||
message.Saadetis?.Tellimus?.ValisTellimuseId ||
|
|
||||||
analysisResult.ValisTellimuseId;
|
|
||||||
|
|
||||||
if (!rawOrderId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderId = Number(rawOrderId);
|
|
||||||
if (isNaN(orderId)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { orderId, rawOrderId };
|
|
||||||
}
|
|
||||||
|
|
||||||
public extractOrderNumber(
|
|
||||||
analysisResult: ParsedMessageData['analysisResult'],
|
|
||||||
): string {
|
|
||||||
return analysisResult.TellimuseNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public extractPatientPersonalCode(
|
|
||||||
analysisResult: ParsedMessageData['analysisResult'],
|
|
||||||
): string | null {
|
|
||||||
return analysisResult.Patsient.Isikukood?.toString() ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public parseMessage(message: MedipostOrderResponse): ParseMessageResult {
|
|
||||||
const analysisResult = this.extractAnalysisResult(message);
|
|
||||||
|
|
||||||
if (!analysisResult) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
reason: 'no_analysis_result',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderIdResult = this.extractOrderId(message, analysisResult);
|
|
||||||
if (!orderIdResult) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
reason: 'invalid_order_id',
|
|
||||||
medipostExternalOrderIdRaw:
|
|
||||||
message.Saadetis?.Tellimus?.ValisTellimuseId ||
|
|
||||||
analysisResult.ValisTellimuseId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const patientPersonalCode = this.extractPatientPersonalCode(analysisResult);
|
|
||||||
if (!patientPersonalCode) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
reason: 'invalid_patient_code',
|
|
||||||
medipostExternalOrderIdRaw: orderIdResult.rawOrderId,
|
|
||||||
medipostExternalOrderId: orderIdResult.orderId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const orderNumber = this.extractOrderNumber(analysisResult);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
analysisResult,
|
|
||||||
orderNumber,
|
|
||||||
medipostExternalOrderId: orderIdResult.orderId,
|
|
||||||
medipostExternalOrderIdRaw: orderIdResult.rawOrderId,
|
|
||||||
patientPersonalCode,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -244,7 +244,6 @@ export async function sendOrderToMedipost({
|
|||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
responseXml: e.response,
|
responseXml: e.response,
|
||||||
hasError: true,
|
hasError: true,
|
||||||
medipostPrivateMessageId: `send-order-to-medipost-${Date.now()}`,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -261,7 +260,6 @@ export async function sendOrderToMedipost({
|
|||||||
hasAnalysisResults: false,
|
hasAnalysisResults: false,
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
hasError: true,
|
hasError: true,
|
||||||
medipostPrivateMessageId: `send-order-to-medipost-${Date.now()}`,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +278,6 @@ export async function sendOrderToMedipost({
|
|||||||
xml: orderXml,
|
xml: orderXml,
|
||||||
hasAnalysisResults: false,
|
hasAnalysisResults: false,
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
medipostPrivateMessageId: `send-order-to-medipost-${Date.now()}`,
|
|
||||||
});
|
});
|
||||||
await createUserAnalysesApi(
|
await createUserAnalysesApi(
|
||||||
getSupabaseServerAdminClient(),
|
getSupabaseServerAdminClient(),
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
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 = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
389
lib/services/medipost/medipostResultsSync.service.ts
Normal file
389
lib/services/medipost/medipostResultsSync.service.ts
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import type { PostgrestError, SupabaseClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
|
import type { Database, Tables } from '@kit/supabase/database';
|
||||||
|
import { toArray } from '@kit/shared/utils';
|
||||||
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
|
|
||||||
|
import type { GetMessageListResponse } from '~/lib/types/medipost';
|
||||||
|
import { MedipostAction } from '~/lib/types/medipost';
|
||||||
|
import type { AnalysisOrder } from "~/lib/types/order";
|
||||||
|
import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element';
|
||||||
|
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
|
||||||
|
import type {
|
||||||
|
MedipostOrderResponse,
|
||||||
|
ResponseUuringuGrupp,
|
||||||
|
} from '@/packages/shared/src/types/medipost-analysis';
|
||||||
|
import { createUserAnalysesApi } from "@/packages/features/user-analyses/src/server/api";
|
||||||
|
|
||||||
|
import { getAnalysisResponseElementsForGroup } from "./medipostPrivateMessage.service";
|
||||||
|
import { getAnalysisOrder } from "../order.service";
|
||||||
|
import { getLatestMessage, upsertMedipostActionLog } from "./medipostMessageBase.service";
|
||||||
|
import { getAccountAdmin } from "../account.service";
|
||||||
|
import { getExistingAnalysisResponseElements, upsertAnalysisResponse, upsertAnalysisResponseElement } from "../analysis-order.service";
|
||||||
|
import { validateMedipostResponse } from './medipostValidate.service';
|
||||||
|
import { parseXML } from '../util/xml.service';
|
||||||
|
import type { Logger } from './types';
|
||||||
|
|
||||||
|
interface ISyncResult {
|
||||||
|
messageId: string | null;
|
||||||
|
hasAnalysisResponse: boolean;
|
||||||
|
hasPartialAnalysisResponse: boolean;
|
||||||
|
hasFullAnalysisResponse: boolean;
|
||||||
|
medusaOrderId: string | undefined;
|
||||||
|
analysisOrderId: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ERROR_RESPONSE: ISyncResult = {
|
||||||
|
messageId: null,
|
||||||
|
hasAnalysisResponse: false,
|
||||||
|
hasPartialAnalysisResponse: false,
|
||||||
|
hasFullAnalysisResponse: false,
|
||||||
|
medusaOrderId: undefined,
|
||||||
|
analysisOrderId: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_URL = process.env.MEDIPOST_URL!;
|
||||||
|
const USER = process.env.MEDIPOST_USER!;
|
||||||
|
const PASSWORD = process.env.MEDIPOST_PASSWORD!;
|
||||||
|
|
||||||
|
const IS_ENABLED_DELETE_PRIVATE_MESSAGE = false;
|
||||||
|
// process.env.MEDIPOST_ENABLE_DELETE_RESPONSE_PRIVATE_MESSAGE_ON_READ ===
|
||||||
|
// 'true';
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class MedipostResultsSyncService {
|
||||||
|
private readonly client: SupabaseClient<Database>;
|
||||||
|
private readonly userAnalysesApi: ReturnType<typeof createUserAnalysesApi>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.client = getSupabaseServerAdminClient();
|
||||||
|
this.userAnalysesApi = createUserAnalysesApi(this.client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async readPrivateMessageResponse({
|
||||||
|
excludedMessageIds,
|
||||||
|
}: {
|
||||||
|
excludedMessageIds: string[];
|
||||||
|
}): Promise<ISyncResult> {
|
||||||
|
let messageId: string | null = null;
|
||||||
|
let hasAnalysisResponse = false;
|
||||||
|
let hasPartialAnalysisResponse = false;
|
||||||
|
let hasFullAnalysisResponse = false;
|
||||||
|
let medusaOrderId: string | undefined = undefined;
|
||||||
|
let analysisOrderId: number | undefined = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const privateMessage = await this.getLatestPrivateMessageListItem({
|
||||||
|
excludedMessageIds,
|
||||||
|
});
|
||||||
|
messageId = privateMessage?.messageId ?? null;
|
||||||
|
|
||||||
|
if (!privateMessage || !messageId) {
|
||||||
|
return ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { messageId: privateMessageId } = privateMessage;
|
||||||
|
const { message: privateMessageContent, xml: privateMessageXml } =
|
||||||
|
await this.getPrivateMessage(privateMessageId);
|
||||||
|
|
||||||
|
const messageResponse = privateMessageContent?.Saadetis?.Vastus;
|
||||||
|
if (!messageResponse) {
|
||||||
|
console.info(`Skipping private message id=${privateMessageId} because it has no response`);
|
||||||
|
return ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const medipostExternalOrderId =
|
||||||
|
privateMessageContent?.Saadetis?.Tellimus?.ValisTellimuseId ||
|
||||||
|
messageResponse?.ValisTellimuseId;
|
||||||
|
|
||||||
|
console.info("PATSIENT", JSON.stringify(messageResponse?.Patsient, null, 2));
|
||||||
|
|
||||||
|
const patientPersonalCode = messageResponse?.Patsient.Isikukood?.toString();
|
||||||
|
analysisOrderId = Number(medipostExternalOrderId);
|
||||||
|
|
||||||
|
const hasInvalidOrderId = isNaN(analysisOrderId);
|
||||||
|
|
||||||
|
if (hasInvalidOrderId || !messageResponse || !patientPersonalCode) {
|
||||||
|
console.log({
|
||||||
|
privateMessageContent,
|
||||||
|
saadetis: privateMessageContent?.Saadetis,
|
||||||
|
messageResponse,
|
||||||
|
});
|
||||||
|
console.error(
|
||||||
|
`Invalid order id or message response or patient personal code, medipostExternalOrderId=${medipostExternalOrderId}, privateMessageId=${privateMessageId}, patientPersonalCode=${patientPersonalCode}`,
|
||||||
|
);
|
||||||
|
// await upsertMedipostActionLog({
|
||||||
|
// action: 'sync_analysis_results_from_medipost',
|
||||||
|
// xml: privateMessageXml,
|
||||||
|
// hasAnalysisResults: false,
|
||||||
|
// medipostPrivateMessageId: privateMessageId,
|
||||||
|
// medusaOrderId,
|
||||||
|
// medipostExternalOrderId,
|
||||||
|
// hasError: true,
|
||||||
|
// });
|
||||||
|
return {
|
||||||
|
...ERROR_RESPONSE,
|
||||||
|
messageId,
|
||||||
|
...(!hasInvalidOrderId && { medusaOrderId, analysisOrderId }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let analysisOrder: AnalysisOrder;
|
||||||
|
try {
|
||||||
|
analysisOrder = await getAnalysisOrder({ analysisOrderId });
|
||||||
|
medusaOrderId = analysisOrder.medusa_order_id;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Get analysis order error", e);
|
||||||
|
await this.deletePrivateMessage(privateMessageId);
|
||||||
|
throw new Error(
|
||||||
|
`No analysis order found for Medipost message ValisTellimuseId=${medipostExternalOrderId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderPerson = await getAccountAdmin({
|
||||||
|
primaryOwnerUserId: analysisOrder.user_id,
|
||||||
|
});
|
||||||
|
if (orderPerson.personal_code !== patientPersonalCode) {
|
||||||
|
throw new Error(
|
||||||
|
`Order person personal code does not match Medipost message Patsient.Isikukood=${patientPersonalCode}, orderPerson.personal_code=${orderPerson.personal_code}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await this.syncPrivateMessage({
|
||||||
|
messageResponse,
|
||||||
|
order: analysisOrder,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
`Successfully synced analysis results from Medipost message privateMessageId=${privateMessageId}`,
|
||||||
|
);
|
||||||
|
await upsertMedipostActionLog({
|
||||||
|
action: 'sync_analysis_results_from_medipost',
|
||||||
|
xml: privateMessageXml,
|
||||||
|
hasAnalysisResults: true,
|
||||||
|
medipostPrivateMessageId: privateMessageId,
|
||||||
|
medusaOrderId,
|
||||||
|
medipostExternalOrderId,
|
||||||
|
});
|
||||||
|
if (status.isPartial) {
|
||||||
|
await this.userAnalysesApi.updateAnalysisOrderStatus({
|
||||||
|
medusaOrderId,
|
||||||
|
orderStatus: 'PARTIAL_ANALYSIS_RESPONSE',
|
||||||
|
});
|
||||||
|
hasAnalysisResponse = true;
|
||||||
|
hasPartialAnalysisResponse = true;
|
||||||
|
} else if (status.isCompleted) {
|
||||||
|
await this.userAnalysesApi.updateAnalysisOrderStatus({
|
||||||
|
medusaOrderId,
|
||||||
|
orderStatus: 'FULL_ANALYSIS_RESPONSE',
|
||||||
|
});
|
||||||
|
await this.deletePrivateMessage(privateMessageId);
|
||||||
|
hasAnalysisResponse = true;
|
||||||
|
hasFullAnalysisResponse = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(
|
||||||
|
`Failed to process private message id=${messageId}, message=${(e as Error).message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
messageId,
|
||||||
|
hasAnalysisResponse,
|
||||||
|
hasPartialAnalysisResponse,
|
||||||
|
hasFullAnalysisResponse,
|
||||||
|
medusaOrderId,
|
||||||
|
analysisOrderId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncPrivateMessage({
|
||||||
|
messageResponse: {
|
||||||
|
ValisTellimuseId: externalId,
|
||||||
|
TellimuseNumber: orderNumber,
|
||||||
|
TellimuseOlek,
|
||||||
|
UuringuGrupp,
|
||||||
|
},
|
||||||
|
order,
|
||||||
|
}: {
|
||||||
|
messageResponse: Pick<
|
||||||
|
NonNullable<MedipostOrderResponse['Saadetis']['Vastus']>,
|
||||||
|
'ValisTellimuseId' | 'TellimuseNumber' | 'TellimuseOlek' | 'UuringuGrupp'
|
||||||
|
>;
|
||||||
|
order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
||||||
|
}) {
|
||||||
|
const orderStatus = AnalysisOrderStatus[TellimuseOlek];
|
||||||
|
|
||||||
|
const log = logger(order, externalId, orderNumber);
|
||||||
|
|
||||||
|
const { data: analysisOrder } = await this.client
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_orders')
|
||||||
|
.select('id, user_id')
|
||||||
|
.eq('id', order.id)
|
||||||
|
.single()
|
||||||
|
.throwOnError();
|
||||||
|
|
||||||
|
console.info("ANALYSIS ORDER", JSON.stringify(analysisOrder, null, 2));
|
||||||
|
throw new Error("early return");
|
||||||
|
|
||||||
|
const { analysisResponseId } = await upsertAnalysisResponse({
|
||||||
|
analysisOrderId: order.id,
|
||||||
|
orderNumber,
|
||||||
|
orderStatus,
|
||||||
|
userId: analysisOrder.user_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingElements = await getExistingAnalysisResponseElements({
|
||||||
|
analysisResponseId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const analysisGroups = toArray(UuringuGrupp);
|
||||||
|
log(`Order has results for ${analysisGroups.length} analysis groups`);
|
||||||
|
const newElements = await this.getNewAnalysisResponseElements({
|
||||||
|
analysisGroups,
|
||||||
|
existingElements,
|
||||||
|
log,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const element of newElements) {
|
||||||
|
try {
|
||||||
|
await upsertAnalysisResponseElement({
|
||||||
|
element: {
|
||||||
|
...element,
|
||||||
|
analysis_response_id: analysisResponseId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(
|
||||||
|
`Failed to create order response element for response id ${analysisResponseId}, element id '${element.analysis_element_original_id}' (order id: ${order.id})`,
|
||||||
|
e as PostgrestError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await this.hasAllAnalysisResponseElements({ analysisResponseId, order }))
|
||||||
|
? { isCompleted: orderStatus === 'COMPLETED' }
|
||||||
|
: { isPartial: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getLatestPrivateMessageListItem({
|
||||||
|
excludedMessageIds,
|
||||||
|
}: {
|
||||||
|
excludedMessageIds: string[];
|
||||||
|
}) {
|
||||||
|
const { data } = await axios.get<GetMessageListResponse>(BASE_URL, {
|
||||||
|
params: {
|
||||||
|
Action: MedipostAction.GetPrivateMessageList,
|
||||||
|
User: USER,
|
||||||
|
Password: PASSWORD,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.code && data.code !== 0) {
|
||||||
|
throw new Error('Failed to get private message list');
|
||||||
|
}
|
||||||
|
|
||||||
|
return await getLatestMessage({
|
||||||
|
messages: data?.messages,
|
||||||
|
excludedMessageIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getNewAnalysisResponseElements({
|
||||||
|
analysisGroups,
|
||||||
|
existingElements,
|
||||||
|
log,
|
||||||
|
}: {
|
||||||
|
analysisGroups: ResponseUuringuGrupp[];
|
||||||
|
existingElements: AnalysisResponseElement[];
|
||||||
|
log: Logger;
|
||||||
|
}) {
|
||||||
|
const newElements: Omit<
|
||||||
|
AnalysisResponseElement,
|
||||||
|
'created_at' | 'updated_at' | 'id' | 'analysis_response_id'
|
||||||
|
>[] = [];
|
||||||
|
for (const analysisGroup of analysisGroups) {
|
||||||
|
log(
|
||||||
|
`[${analysisGroups.indexOf(analysisGroup) + 1}/${analysisGroups.length}] Syncing analysis group '${analysisGroup.UuringuGruppNimi}'`,
|
||||||
|
);
|
||||||
|
const elements = await getAnalysisResponseElementsForGroup({
|
||||||
|
analysisGroup,
|
||||||
|
existingElements,
|
||||||
|
log,
|
||||||
|
});
|
||||||
|
newElements.push(...elements);
|
||||||
|
}
|
||||||
|
return newElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async hasAllAnalysisResponseElements({
|
||||||
|
analysisResponseId,
|
||||||
|
order,
|
||||||
|
}: {
|
||||||
|
analysisResponseId: number;
|
||||||
|
order: Pick<AnalysisOrder, 'analysis_element_ids'>;
|
||||||
|
}) {
|
||||||
|
const allOrderResponseElements = await getExistingAnalysisResponseElements({
|
||||||
|
analysisResponseId,
|
||||||
|
});
|
||||||
|
const expectedOrderResponseElements = order.analysis_element_ids?.length ?? 0;
|
||||||
|
return allOrderResponseElements.length >= expectedOrderResponseElements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deletePrivateMessage(messageId: string) {
|
||||||
|
if (!IS_ENABLED_DELETE_PRIVATE_MESSAGE) {
|
||||||
|
console.info(`Skipping delete private message id=${messageId} because deleting is not enabled`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data } = await axios.get(BASE_URL, {
|
||||||
|
params: {
|
||||||
|
Action: MedipostAction.DeletePrivateMessage,
|
||||||
|
User: USER,
|
||||||
|
Password: PASSWORD,
|
||||||
|
MessageId: messageId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.code && data.code !== 0) {
|
||||||
|
throw new Error(`Failed to delete private message (id: ${messageId})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPrivateMessage(messageId: string) {
|
||||||
|
const { data } = await axios.get(BASE_URL, {
|
||||||
|
params: {
|
||||||
|
Action: MedipostAction.GetPrivateMessage,
|
||||||
|
User: USER,
|
||||||
|
Password: PASSWORD,
|
||||||
|
MessageId: messageId,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/xml',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await validateMedipostResponse(data, { canHaveEmptyCode: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: (await parseXML(data)) as MedipostOrderResponse,
|
||||||
|
xml: data as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import type { IMedipostResponseXMLBase } from '@/packages/shared/src/types/medipost-analysis';
|
import type { IMedipostResponseXMLBase } from '@/packages/shared/src/types/medipost-analysis';
|
||||||
import type { AnalysisOrder } from '~/lib/types/order';
|
|
||||||
|
|
||||||
import { parseXML } from '../util/xml.service';
|
import { parseXML } from '../util/xml.service';
|
||||||
import { MedipostValidationError } from './MedipostValidationError';
|
import { MedipostValidationError } from './MedipostValidationError';
|
||||||
import { getAccountAdmin } from '../account.service';
|
|
||||||
|
|
||||||
export async function validateMedipostResponse(
|
export async function validateMedipostResponse(
|
||||||
response: string,
|
response: string,
|
||||||
@@ -26,20 +24,3 @@ export async function validateMedipostResponse(
|
|||||||
throw new MedipostValidationError(response);
|
throw new MedipostValidationError(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function validateOrderPerson({
|
|
||||||
analysisOrder,
|
|
||||||
patientPersonalCode,
|
|
||||||
}: {
|
|
||||||
analysisOrder: AnalysisOrder;
|
|
||||||
patientPersonalCode: string;
|
|
||||||
}) {
|
|
||||||
const orderPerson = await getAccountAdmin({
|
|
||||||
primaryOwnerUserId: analysisOrder.user_id,
|
|
||||||
});
|
|
||||||
if (orderPerson.personal_code !== patientPersonalCode) {
|
|
||||||
throw new Error(
|
|
||||||
`Order person personal code does not match Medipost message Patsient.Isikukood=${patientPersonalCode}, orderPerson.personal_code=${orderPerson.personal_code}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
export type Logger = (message: string, level?: 'info' | 'error' | 'warn', error?: Error | null) => void;
|
import type { PostgrestError } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
export type Logger = (message: string, error?: PostgrestError | null) => void;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { StoreOrder } from '@medusajs/types';
|
import type { StoreOrder } from '@medusajs/types';
|
||||||
|
|
||||||
|
import type { Tables } from '@kit/supabase/database';
|
||||||
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ export async function getAnalysisOrder({
|
|||||||
export async function getAnalysisOrders({
|
export async function getAnalysisOrders({
|
||||||
orderStatus,
|
orderStatus,
|
||||||
}: {
|
}: {
|
||||||
orderStatus?: AnalysisOrder['status'];
|
orderStatus?: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status'];
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const client = getSupabaseServerClient();
|
const client = getSupabaseServerClient();
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ export async function getAnalysisOrdersAdmin({
|
|||||||
orderStatus,
|
orderStatus,
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
}: {
|
}: {
|
||||||
orderStatus?: AnalysisOrder['status'];
|
orderStatus?: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status'];
|
||||||
medusaOrderId?: string | null;
|
medusaOrderId?: string | null;
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const query = getSupabaseServerAdminClient()
|
const query = getSupabaseServerAdminClient()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function ButtonTooltip({
|
|||||||
content,
|
content,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
content?: string | React.ReactNode;
|
content?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
if (!content) return null;
|
if (!content) return null;
|
||||||
|
|||||||
@@ -130,4 +130,3 @@ export type MedipostOrderResponse = IMedipostResponseXMLBase & {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type MedipostAnalysisResult = MedipostOrderResponse['Saadetis']['Vastus'];
|
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export function getServiceRoleKey() {
|
|||||||
*/
|
*/
|
||||||
export function warnServiceRoleKeyUsage() {
|
export function warnServiceRoleKeyUsage() {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
console.warn(
|
// console.warn(
|
||||||
`[Dev Only] This is a simple warning to let you know you are using the Supabase Service Role. Make sure it's the right call.`,
|
// `[Dev Only] This is a simple warning to let you know you are using the Supabase Service Role. Make sure it's the right call.`,
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,9 +65,7 @@ export function useAuthChangeListener({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to home instead of reloading to avoid state mismatch errors
|
window.location.reload();
|
||||||
// during the transition
|
|
||||||
window.location.assign('/');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,5 @@
|
|||||||
"view": "View results",
|
"view": "View results",
|
||||||
"notification": {
|
"notification": {
|
||||||
"body": "You have new analysis results"
|
"body": "You have new analysis results"
|
||||||
},
|
}
|
||||||
"orderCreatedAt": "Order created at: {{createdAt}}"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,5 @@
|
|||||||
"view": "Vaata tulemusi",
|
"view": "Vaata tulemusi",
|
||||||
"notification": {
|
"notification": {
|
||||||
"body": "Teil on valmis uued analüüsi tulemused"
|
"body": "Teil on valmis uued analüüsi tulemused"
|
||||||
},
|
}
|
||||||
"orderCreatedAt": "Tellimus loodud: {{createdAt}}"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,5 @@
|
|||||||
"orderTitle": "Заказ {{orderNumber}}",
|
"orderTitle": "Заказ {{orderNumber}}",
|
||||||
"notification": {
|
"notification": {
|
||||||
"body": "Teil on valmis uued analüüsi tulemused"
|
"body": "Teil on valmis uued analüüsi tulemused"
|
||||||
},
|
}
|
||||||
"orderCreatedAt": "Заказ создан: {{createdAt}}"
|
|
||||||
}
|
}
|
||||||
|
|||||||
6
run-test-sync-local.sh
Normal file → Executable file
6
run-test-sync-local.sh
Normal file → Executable file
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
MEDUSA_ORDER_ID="order_01K1TQQHZGPXKDHAH81TDSNGXR"
|
MEDUSA_ORDER_ID="order_01K9SMB00HJ1W37S1HM0DN2SFV"
|
||||||
|
|
||||||
# HOSTNAME="https://test.medreport.ee"
|
# HOSTNAME="https://test.medreport.ee"
|
||||||
# JOBS_API_TOKEN="fd26ec26-70ed-11f0-9e95-431ac3b15a84"
|
# JOBS_API_TOKEN="fd26ec26-70ed-11f0-9e95-431ac3b15a84"
|
||||||
@@ -33,7 +33,7 @@ function sync_analysis_groups_store() {
|
|||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
# 1. Sync analysis groups from Medipost to B2B
|
# 1. Sync analysis groups from Medipost to B2B
|
||||||
sync_analysis_groups
|
#sync_analysis_groups
|
||||||
|
|
||||||
# 2. Optional - sync all Medipost analysis groups from B2B to Medusa (or add manually)
|
# 2. Optional - sync all Medipost analysis groups from B2B to Medusa (or add manually)
|
||||||
#sync_analysis_groups_store
|
#sync_analysis_groups_store
|
||||||
@@ -41,7 +41,7 @@ sync_analysis_groups
|
|||||||
# 3. Set up products configurations in Medusa so B2B "Telli analüüs" page shows the product and you can do payment flow
|
# 3. Set up products configurations in Medusa so B2B "Telli analüüs" page shows the product and you can do payment flow
|
||||||
|
|
||||||
# 4. After payment is done, run `send_medipost_test_response` to send the fake test results to Medipost
|
# 4. After payment is done, run `send_medipost_test_response` to send the fake test results to Medipost
|
||||||
# send_medipost_test_response
|
send_medipost_test_response
|
||||||
|
|
||||||
# 5. Run `sync_analysis_results` to sync the all new Medipost results to B2B
|
# 5. Run `sync_analysis_results` to sync the all new Medipost results to B2B
|
||||||
# sync_analysis_results
|
# sync_analysis_results
|
||||||
|
|||||||
Reference in New Issue
Block a user