diff --git a/app/api/order/medipost-create/route.ts b/app/api/order/medipost-create/route.ts deleted file mode 100644 index 10b9ea1..0000000 --- a/app/api/order/medipost-create/route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; -import { sendOrderToMedipost } from "~/lib/services/medipost.service"; - -export const POST = async (request: NextRequest) => { - const { medusaOrderId } = (await request.json()) as { medusaOrderId: string }; - await sendOrderToMedipost({ medusaOrderId }); - return NextResponse.json({ success: true }); -}; diff --git a/app/api/order/medipost-test-response/route.ts b/app/api/order/medipost-test-response/route.ts index 3abdd03..40cf4b2 100644 --- a/app/api/order/medipost-test-response/route.ts +++ b/app/api/order/medipost-test-response/route.ts @@ -19,7 +19,7 @@ export async function POST(request: Request) { const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id }); const orderedAnalysisElementsIds = await getOrderedAnalysisElementsIds({ medusaOrder }); - console.info(`Sending test response for order=${medusaOrderId} with ${orderedAnalysisElementsIds.length} ordered analysis elements`); + console.info(`Sending test response for order=${medusaOrderId} with ${orderedAnalysisElementsIds.length} (${maxItems ?? 'all'}) ordered analysis elements`); const idsToSend = typeof maxItems === 'number' ? orderedAnalysisElementsIds.slice(0, maxItems) : orderedAnalysisElementsIds; const messageXml = await composeOrderTestResponseXML({ person: { diff --git a/app/home/(user)/(dashboard)/analysis-results/page.tsx b/app/home/(user)/(dashboard)/analysis-results/page.tsx index 60c43d5..01bc50a 100644 --- a/app/home/(user)/(dashboard)/analysis-results/page.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/page.tsx @@ -11,10 +11,11 @@ import { loadUserAnalysis } from '../../_lib/server/load-user-analysis'; import Analysis from './_components/analysis'; import pathsConfig from '~/config/paths.config'; import { redirect } from 'next/navigation'; -import { getAnalysisOrders } from '~/lib/services/order.service'; +import { AnalysisOrder, getAnalysisOrders } from '~/lib/services/order.service'; import { getAnalysisElements } from '~/lib/services/analysis-element.service'; import { loadCurrentUserAccount } from '@/app/home/(user)/_lib/server/load-user-account'; import { createPageViewLog } from '~/lib/services/audit/pageView.service'; +import { ButtonTooltip } from '~/components/ui/button-tooltip'; export const generateMetadata = async () => { const i18n = await createI18nServerInstance(); @@ -45,25 +46,15 @@ async function AnalysisResultsPage() { action: 'VIEW_ANALYSIS_RESULTS', }); - const analysisElementIds = [ + const getAnalysisElementIds = (analysisOrders: AnalysisOrder[]) => [ ...new Set(analysisOrders?.flatMap((order) => order.analysis_element_ids).filter(Boolean) as number[]), ]; - const analysisElements = await getAnalysisElements({ ids: analysisElementIds }); - const analysisElementsWithResults = analysisResponseElements - ?.sort((a, b) => { - if (!a.response_time || !b.response_time) { - return 0; - } - return new Date(b.response_time).getTime() - new Date(a.response_time).getTime(); - }) - .map((results) => ({ results })) ?? []; - const analysisElementsWithoutResults = analysisElements - .filter((element) => !analysisElementsWithResults?.some(({ results }) => results.analysis_element_original_id === element.analysis_id_original)); - const hasNoAnalysisElements = analysisElementsWithResults.length === 0 && analysisElementsWithoutResults.length === 0; + const analysisElementIds = getAnalysisElementIds(analysisOrders); + const analysisElements = await getAnalysisElements({ ids: analysisElementIds }); return ( - +

@@ -83,22 +74,44 @@ async function AnalysisResultsPage() {

-
- {analysisElementsWithResults.map(({ results }) => { - const analysisElement = analysisElements.find((element) => element.analysis_id_original === results.analysis_element_original_id); - if (!analysisElement) { - return null; - } +
+ {analysisOrders.length > 0 && analysisElements.length > 0 ? analysisOrders.map((analysisOrder) => { + const analysisElementIds = getAnalysisElementIds([analysisOrder]); + const analysisElementsForOrder = analysisElements.filter((element) => analysisElementIds.includes(element.id)); return ( - +
+

+ +

+
+ + +
+
+ {analysisElementsForOrder.length > 0 ? analysisElementsForOrder.map((analysisElement) => { + const results = analysisResponseElements?.find((element) => element.analysis_element_original_id === analysisElement.analysis_id_original); + if (!results) { + return ( + + ); + } + return ( + + ); + }) : ( +
+ +
+ )} +
+
); - })} - {analysisElementsWithoutResults.map((element) => ( - - ))} - {hasNoAnalysisElements && ( + }) : (
- +
)}
diff --git a/components/select-analysis-package.tsx b/components/select-analysis-package.tsx index b4863cf..d6b74d2 100644 --- a/components/select-analysis-package.tsx +++ b/components/select-analysis-package.tsx @@ -1,6 +1,6 @@ "use client"; -import { use, useState } from 'react'; +import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; diff --git a/lib/services/medipost.service.ts b/lib/services/medipost.service.ts index e19e6db..4490c0b 100644 --- a/lib/services/medipost.service.ts +++ b/lib/services/medipost.service.ts @@ -36,7 +36,7 @@ import { uniqBy } from 'lodash'; import { Tables } from '@kit/supabase/database'; import { createAnalysisGroup } from './analysis-group.service'; import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; -import { getOrder, updateOrder } from './order.service'; +import { getOrder, updateOrderStatus } from './order.service'; import { getAnalysisElements, getAnalysisElementsAdmin } from './analysis-element.service'; import { getAnalyses } from './analyses.service'; import { getAccountAdmin } from './account.service'; @@ -218,24 +218,30 @@ export async function readPrivateMessageResponse({ privateMessage.messageId, ); const messageResponse = privateMessageContent?.Saadetis?.Vastus; + const medusaOrderId = privateMessageContent?.Saadetis?.Tellimus?.ValisTellimuseId; if (!messageResponse) { - throw new Error(`Private message response has no results yet`); + if (medusaOrderId === 'order_01K2JSJXR5XVNRWEAGB199RCKP') { + console.info("messageResponse", JSON.stringify(privateMessageContent, null, 2)); + } + throw new Error(`Private message response has no results yet for order=${medusaOrderId}`); } - console.info(`Private message content: ${JSON.stringify(privateMessageContent)}`); let order: Tables<{ schema: 'medreport' }, 'analysis_orders'>; try { - order = await getOrder({ medusaOrderId: messageResponse.ValisTellimuseId }); + order = await getOrder({ medusaOrderId }); } catch (e) { await deletePrivateMessage(privateMessage.messageId); - throw new Error(`Order not found by Medipost message ValisTellimuseId=${messageResponse.ValisTellimuseId}`); + throw new Error(`Order not found by Medipost message ValisTellimuseId=${medusaOrderId}`); } const status = await syncPrivateMessage({ messageResponse, order }); - if (status === 'COMPLETED') { - await updateOrder({ orderId: order.id, orderStatus: 'FULL_ANALYSIS_RESPONSE' }); + if (status.isPartial) { + await updateOrderStatus({ medusaOrderId, orderStatus: 'PARTIAL_ANALYSIS_RESPONSE' }); + messageIdProcessed = privateMessage.messageId; + } else if (status.isCompleted) { + await updateOrderStatus({ medusaOrderId, orderStatus: 'FULL_ANALYSIS_RESPONSE' }); await deletePrivateMessage(privateMessage.messageId); messageIdProcessed = privateMessage.messageId; } @@ -559,11 +565,11 @@ function getLatestMessage({ ); } -export async function syncPrivateMessage({ +async function syncPrivateMessage({ messageResponse, order, }: { - messageResponse: MedipostOrderResponse['Saadetis']['Vastus']; + messageResponse: NonNullable; order: Tables<{ schema: 'medreport' }, 'analysis_orders'>; }) { const supabase = getSupabaseServerAdminClient() @@ -606,6 +612,9 @@ export async function syncPrivateMessage({ Tables<{ schema: 'medreport' }, 'analysis_response_elements'>, 'id' | 'created_at' | 'updated_at' >[] = []; + + const analysisResponseId = analysisResponse[0]!.id; + for (const analysisGroup of analysisGroups) { const groupItems = toArray( analysisGroup.Uuring as ResponseUuringuGrupp['Uuring'], @@ -618,7 +627,7 @@ export async function syncPrivateMessage({ responses.push( ...elementAnalysisResponses.map((response) => ({ analysis_element_original_id: element.UuringId, - analysis_response_id: analysisResponse[0]!.id, + analysis_response_id: analysisResponseId, norm_lower: response.NormAlum?.['#text'] ?? null, norm_lower_included: response.NormAlum?.['@_kaasaarvatud'].toLowerCase() === 'jah', @@ -640,11 +649,11 @@ export async function syncPrivateMessage({ .schema('medreport') .from('analysis_response_elements') .delete() - .eq('analysis_response_id', analysisResponse[0].id); + .eq('analysis_response_id', analysisResponseId); if (deleteError) { throw new Error( - `Failed to clean up response elements for response id ${analysisResponse[0].id}`, + `Failed to clean up response elements for response id ${analysisResponseId}`, ); } @@ -655,12 +664,23 @@ export async function syncPrivateMessage({ if (elementInsertError) { throw new Error( - `Failed to insert order response elements for response id ${analysisResponse[0].id}`, + `Failed to insert order response elements for response id ${analysisResponseId}`, ); } - console.info("status", AnalysisOrderStatus[messageResponse.TellimuseOlek], messageResponse.TellimuseOlek); - return AnalysisOrderStatus[messageResponse.TellimuseOlek]; + const { data: allOrderResponseElements} = await supabase + .schema('medreport') + .from('analysis_response_elements') + .select('*') + .eq('analysis_response_id', analysisResponseId) + .throwOnError(); + const expectedOrderResponseElements = order.analysis_element_ids?.length ?? 0; + if (allOrderResponseElements.length !== expectedOrderResponseElements) { + return { isPartial: true }; + } + + const statusFromResponse = AnalysisOrderStatus[messageResponse.TellimuseOlek]; + return { isCompleted: statusFromResponse === 'COMPLETED' }; } export async function sendOrderToMedipost({ @@ -688,7 +708,7 @@ export async function sendOrderToMedipost({ }); await sendPrivateMessage(orderXml); - await updateOrder({ orderId: medreportOrder.id, orderStatus: 'PROCESSING' }); + await updateOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING' }); } export async function getOrderedAnalysisElementsIds({ @@ -720,7 +740,7 @@ export async function getOrderedAnalysisElementsIds({ countryCode, queryParams: { limit: 100, id: orderedPackageIds }, }); - console.info(`Order has ${orderedPackagesProducts.length} packages`); + console.info(`Order has ${orderedPackagesProducts.length} packages = ${JSON.stringify(orderedPackageIds, null, 2)}`); if (orderedPackagesProducts.length !== orderedPackageIds.length) { throw new Error(`Got ${orderedPackagesProducts.length} ordered packages products, expected ${orderedPackageIds.length}`); } diff --git a/lib/services/order.service.ts b/lib/services/order.service.ts index 998d564..3725e5c 100644 --- a/lib/services/order.service.ts +++ b/lib/services/order.service.ts @@ -56,6 +56,30 @@ export async function updateOrder({ .throwOnError(); } +export async function updateOrderStatus({ + orderId, + medusaOrderId, + orderStatus, +}: { + orderId?: number; + medusaOrderId?: string; + orderStatus: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status']; +}) { + const orderIdParam = orderId; + const medusaOrderIdParam = medusaOrderId; + if (!orderIdParam && !medusaOrderIdParam) { + throw new Error('Either orderId or medusaOrderId must be provided'); + } + await getSupabaseServerAdminClient() + .schema('medreport') + .rpc('update_analysis_order_status', { + order_id: orderIdParam ?? -1, + status_param: orderStatus, + medusa_order_id_param: medusaOrderIdParam ?? '', + }) + .throwOnError(); +} + export async function getOrder({ medusaOrderId, orderId, @@ -91,6 +115,6 @@ export async function getAnalysisOrders({ if (orderStatus) { query.eq('status', orderStatus); } - const orders = await query.throwOnError(); + const orders = await query.order('created_at', { ascending: false }).throwOnError(); return orders.data; } diff --git a/lib/types/medipost.ts b/lib/types/medipost.ts index 0794fa0..b47d985 100644 --- a/lib/types/medipost.ts +++ b/lib/types/medipost.ts @@ -202,7 +202,7 @@ export type MedipostOrderResponse = IMedipostResponseXMLBase & { SaadetisId: string; Email: string; }; - Vastus: { + Vastus?: { ValisTellimuseId: string; Asutus: { '@_tyyp': string; // TEOSTAJA @@ -246,6 +246,9 @@ export type MedipostOrderResponse = IMedipostResponseXMLBase & { TellimuseOlek: keyof typeof AnalysisOrderStatus; UuringuGrupp?: ResponseUuringuGrupp | ResponseUuringuGrupp[]; }; + Tellimus?: { + ValisTellimuseId: string; + } }; }; diff --git a/lib/utils.ts b/lib/utils.ts index 1eb8901..a9c34c4 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -18,3 +18,12 @@ export function toTitleCase(str?: string) { text.charAt(0).toUpperCase() + text.substring(1).toLowerCase(), ); } + +export function sortByDate(a: T[] | undefined, key: keyof T): T[] | undefined { + return a?.sort((a, b) => { + if (!a[key] || !b[key]) { + return 0; + } + return new Date(b[key] as string).getTime() - new Date(a[key] as string).getTime(); + }); +} diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 8398b9d..790b376 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -1819,6 +1819,22 @@ export type Database = { } Returns: undefined } + update_analysis_order_status: { + Args: { + order_id: number + medusa_order_id_param: string + status_param: Database["medreport"]["Enums"]["analysis_order_status"] + } + Returns: { + analysis_element_ids: number[] | null + analysis_ids: number[] | null + created_at: string + id: number + medusa_order_id: string + status: Database["medreport"]["Enums"]["analysis_order_status"] + user_id: string + } + } upsert_order: { Args: { target_account_id: string diff --git a/public/locales/en/analysis-results.json b/public/locales/en/analysis-results.json index ada8eac..9f6a491 100644 --- a/public/locales/en/analysis-results.json +++ b/public/locales/en/analysis-results.json @@ -5,10 +5,12 @@ "orderNewAnalysis": "Order new analyses", "waitingForResults": "Waiting for results", "noAnalysisElements": "No analysis orders found", + "noAnalysisOrders": "No analysis orders found", "analysisDate": "Analysis result date", "results": { "range": { "normal": "Normal range" } - } + }, + "orderTitle": "Order number {{orderNumber}}" } \ No newline at end of file diff --git a/public/locales/et/analysis-results.json b/public/locales/et/analysis-results.json index 0acc140..6334d60 100644 --- a/public/locales/et/analysis-results.json +++ b/public/locales/et/analysis-results.json @@ -5,10 +5,12 @@ "orderNewAnalysis": "Telli uued analüüsid", "waitingForResults": "Tulemuse ootel", "noAnalysisElements": "Veel ei ole tellitud analüüse", + "noAnalysisOrders": "Veel ei ole analüüside tellimusi", "analysisDate": "Analüüsi vastuse kuupäev", "results": { "range": { "normal": "Normaalne vahemik" } - } + }, + "orderTitle": "Tellimus {{orderNumber}}" } \ No newline at end of file