feat(MED-105): update sending test analysis results
This commit is contained in:
54
app/api/job/test-medipost-responses/route.ts
Normal file
54
app/api/job/test-medipost-responses/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getAnalysisOrdersAdmin } from "~/lib/services/order.service";
|
||||
import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipostTest.service";
|
||||
import { retrieveOrder } from "@lib/data";
|
||||
import { getAccountAdmin } from "~/lib/services/account.service";
|
||||
import { getOrderedAnalysisElementsIds } from "~/lib/services/medipost.service";
|
||||
import loadEnv from "../handler/load-env";
|
||||
import validateApiKey from "../handler/validate-api-key";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
loadEnv();
|
||||
|
||||
try {
|
||||
validateApiKey(request);
|
||||
} catch (e) {
|
||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const analysisOrders = await getAnalysisOrdersAdmin({ orderStatus: 'QUEUED' });
|
||||
|
||||
console.error(`Sending test responses for ${analysisOrders.length} analysis orders`);
|
||||
for (const medreportOrder of analysisOrders) {
|
||||
const medusaOrderId = medreportOrder.medusa_order_id;
|
||||
const medusaOrder = await retrieveOrder(medusaOrderId)
|
||||
|
||||
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`);
|
||||
const idsToSend = orderedAnalysisElementsIds;
|
||||
const messageXml = await composeOrderTestResponseXML({
|
||||
person: {
|
||||
idCode: account.personal_code!,
|
||||
firstName: account.name ?? '',
|
||||
lastName: account.last_name ?? '',
|
||||
phone: account.phone ?? '',
|
||||
},
|
||||
orderedAnalysisElementsIds: idsToSend.map(({ analysisElementId }) => analysisElementId),
|
||||
orderedAnalysesIds: [],
|
||||
orderId: medusaOrderId,
|
||||
orderCreatedAt: new Date(medreportOrder.created_at),
|
||||
});
|
||||
|
||||
console.info("SEND XML", messageXml);
|
||||
|
||||
try {
|
||||
await sendPrivateMessageTestResponse({ messageXml });
|
||||
} catch (error) {
|
||||
console.error("Error sending private message test response: ", error);
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { ArrowDown } from 'lucide-react';
|
||||
|
||||
import { cn } from '@kit/ui/utils';
|
||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
||||
|
||||
export enum AnalysisResultLevel {
|
||||
VERY_LOW = 0,
|
||||
@@ -17,11 +18,13 @@ const Level = ({
|
||||
color,
|
||||
isFirst = false,
|
||||
isLast = false,
|
||||
arrowLocation,
|
||||
}: {
|
||||
isActive?: boolean;
|
||||
color: 'destructive' | 'success' | 'warning' | 'gray-200';
|
||||
isFirst?: boolean;
|
||||
isLast?: boolean;
|
||||
arrowLocation?: number;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -32,7 +35,10 @@ const Level = ({
|
||||
})}
|
||||
>
|
||||
{isActive && (
|
||||
<div className="absolute top-[-14px] left-1/2 -translate-x-1/2 rounded-[10px] bg-white p-[2px]">
|
||||
<div
|
||||
className="absolute top-[-14px] left-1/2 -translate-x-1/2 rounded-[10px] bg-white p-[2px]"
|
||||
style={{ left: `${arrowLocation}%` }}
|
||||
>
|
||||
<ArrowDown strokeWidth={2} />
|
||||
</div>
|
||||
)}
|
||||
@@ -52,11 +58,33 @@ const AnalysisLevelBar = ({
|
||||
normLowerIncluded = true,
|
||||
normUpperIncluded = true,
|
||||
level,
|
||||
results,
|
||||
}: {
|
||||
normLowerIncluded?: boolean;
|
||||
normUpperIncluded?: boolean;
|
||||
level: AnalysisResultLevel;
|
||||
results: UserAnalysisElement;
|
||||
}) => {
|
||||
|
||||
const { norm_lower: lower, norm_upper: upper, response_value: value } = results;
|
||||
const arrowLocation = useMemo(() => {
|
||||
if (value < lower!) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (normLowerIncluded || normUpperIncluded) {
|
||||
return 50;
|
||||
}
|
||||
|
||||
const calculated = ((value - lower!) / (upper! - lower!)) * 100;
|
||||
|
||||
if (calculated > 100) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
return calculated;
|
||||
}, [value, upper, lower]);
|
||||
|
||||
return (
|
||||
<div className="mt-4 flex h-3 w-[35%] max-w-[360px] gap-1 sm:mt-0">
|
||||
{normLowerIncluded && (
|
||||
@@ -73,8 +101,9 @@ const AnalysisLevelBar = ({
|
||||
<Level
|
||||
isFirst={!normLowerIncluded}
|
||||
isLast={!normUpperIncluded}
|
||||
isActive={level === AnalysisResultLevel.NORMAL}
|
||||
color="success"
|
||||
color={level === AnalysisResultLevel.NORMAL ? "success" : "warning"}
|
||||
isActive
|
||||
arrowLocation={arrowLocation}
|
||||
/>
|
||||
|
||||
{normUpperIncluded && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
||||
import { format } from 'date-fns';
|
||||
@@ -39,11 +39,12 @@ const Analysis = ({
|
||||
const normUpper = results?.norm_upper || 0;
|
||||
|
||||
const [showTooltip, setShowTooltip] = useState(false);
|
||||
const isUnderNorm = value < normLower;
|
||||
const getAnalysisResultLevel = () => {
|
||||
const analysisResultLevel = useMemo(() => {
|
||||
if (!results) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isUnderNorm = value < normLower;
|
||||
if (isUnderNorm) {
|
||||
switch (status) {
|
||||
case AnalysisStatus.MEDIUM:
|
||||
@@ -60,7 +61,7 @@ const Analysis = ({
|
||||
default:
|
||||
return AnalysisResultLevel.NORMAL;
|
||||
}
|
||||
};
|
||||
}, [results, value, normLower]);
|
||||
|
||||
return (
|
||||
<div className="border-border flex flex-col items-center justify-between gap-2 rounded-lg border px-5 px-12 py-3 sm:h-[65px] sm:flex-row sm:gap-0">
|
||||
@@ -99,9 +100,10 @@ const Analysis = ({
|
||||
</div>
|
||||
</div>
|
||||
<AnalysisLevelBar
|
||||
results={results}
|
||||
normLowerIncluded={normLowerIncluded}
|
||||
normUpperIncluded={normUpperIncluded}
|
||||
level={getAnalysisResultLevel()!}
|
||||
level={analysisResultLevel!}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -82,6 +82,7 @@ async function AnalysisResultsPage() {
|
||||
</div>
|
||||
<div className="flex flex-col gap-8">
|
||||
{analysisOrders.length > 0 && analysisElements.length > 0 ? analysisOrders.map((analysisOrder) => {
|
||||
const analysisResponse = analysisResponses?.find((response) => response.analysis_order_id === analysisOrder.id);
|
||||
const analysisElementIds = getAnalysisElementIds([analysisOrder]);
|
||||
const analysisElementsForOrder = analysisElements.filter((element) => analysisElementIds.includes(element.id));
|
||||
return (
|
||||
@@ -98,7 +99,8 @@ async function AnalysisResultsPage() {
|
||||
</h5>
|
||||
<div className="flex flex-col gap-2">
|
||||
{analysisElementsForOrder.length > 0 ? analysisElementsForOrder.map((analysisElement) => {
|
||||
const results = analysisResponseElements?.find((element) => element.analysis_element_original_id === analysisElement.analysis_id_original);
|
||||
const results = analysisResponse?.elements.some((element) => element.analysis_element_original_id === analysisElement.analysis_id_original)
|
||||
&& analysisResponseElements?.find((element) => element.analysis_element_original_id === analysisElement.analysis_id_original);
|
||||
if (!results) {
|
||||
return (
|
||||
<Analysis key={`${analysisOrder.id}-${analysisElement.id}`} analysisElement={analysisElement} />
|
||||
|
||||
@@ -218,12 +218,9 @@ export async function readPrivateMessageResponse({
|
||||
privateMessage.messageId,
|
||||
);
|
||||
const messageResponse = privateMessageContent?.Saadetis?.Vastus;
|
||||
const medusaOrderId = privateMessageContent?.Saadetis?.Tellimus?.ValisTellimuseId;
|
||||
const medusaOrderId = privateMessageContent?.Saadetis?.Tellimus?.ValisTellimuseId || messageResponse?.ValisTellimuseId;
|
||||
|
||||
if (!messageResponse) {
|
||||
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}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function composeOrderTestResponseXML({
|
||||
// 1 – Järjekorras, 2 – Ootel, 3 - Töös, 4 – Lõpetatud,
|
||||
// 5 – Tagasi lükatud, 6 – Tühistatud.
|
||||
const orderStatus = 4;
|
||||
const orderNumber = 'TSU000001200';
|
||||
const orderNumber = orderId;
|
||||
|
||||
const allAnalysisElementsForGroups = analysisElements?.filter((element) => {
|
||||
return analysisGroups.some((group) => group.id === element.analysis_groups.id);
|
||||
@@ -153,7 +153,7 @@ export async function composeOrderTestResponseXML({
|
||||
|
||||
const lower = getRandomInt(0, 100);
|
||||
const upper = getRandomInt(lower + 1, 500);
|
||||
const result = getRandomInt(lower, upper);
|
||||
const result = getRandomInt(lower - Math.floor(lower * 0.1), upper + Math.floor(upper * 0.1));
|
||||
addedIds.add(relatedAnalysisElement.id);
|
||||
return (`
|
||||
<UuringuGrupp>
|
||||
@@ -175,7 +175,7 @@ export async function composeOrderTestResponseXML({
|
||||
<VastuseAeg>${formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')}</VastuseAeg>
|
||||
<NormYlem kaasaarvatud=\"EI\">${upper}</NormYlem>
|
||||
<NormAlum kaasaarvatud=\"EI\">${lower}</NormAlum>
|
||||
<NormiStaatus>0</NormiStaatus>
|
||||
<NormiStaatus>${result < lower ? 1 : (result > upper ? 1 : 0)}</NormiStaatus>
|
||||
<ProoviJarjenumber>1</ProoviJarjenumber>
|
||||
</UuringuVastus>
|
||||
</UuringuElement>
|
||||
|
||||
@@ -108,7 +108,33 @@ export async function getAnalysisOrders({
|
||||
}: {
|
||||
orderStatus?: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status'];
|
||||
} = {}) {
|
||||
const query = getSupabaseServerClient()
|
||||
const client = getSupabaseServerClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await client.auth.getUser();
|
||||
if (!user) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
const query = client
|
||||
.schema('medreport')
|
||||
.from('analysis_orders')
|
||||
.select('*')
|
||||
.eq("user_id", user.id)
|
||||
if (orderStatus) {
|
||||
query.eq('status', orderStatus);
|
||||
}
|
||||
const orders = await query.order('created_at', { ascending: false }).throwOnError();
|
||||
return orders.data;
|
||||
}
|
||||
|
||||
export async function getAnalysisOrdersAdmin({
|
||||
orderStatus,
|
||||
}: {
|
||||
orderStatus?: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status'];
|
||||
} = {}) {
|
||||
const query = getSupabaseServerAdminClient()
|
||||
.schema('medreport')
|
||||
.from('analysis_orders')
|
||||
.select('*')
|
||||
|
||||
@@ -261,7 +261,7 @@ export const AnalysisOrderStatus = {
|
||||
6: 'CANCELLED',
|
||||
} as const;
|
||||
export const NormStatus: Record<number, string> = {
|
||||
1: 'NORMAL',
|
||||
2: 'WARNING',
|
||||
3: 'REQUIRES_ATTENTION',
|
||||
0: 'NORMAL',
|
||||
1: 'WARNING',
|
||||
2: 'REQUIRES_ATTENTION',
|
||||
} as const;
|
||||
|
||||
19
schedule-setup/setup_send_analysis_test_results_cron.sql
Normal file
19
schedule-setup/setup_send_analysis_test_results_cron.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Enable required extensions for cron jobs and HTTP requests
|
||||
create extension if not exists pg_cron;
|
||||
create extension if not exists pg_net;
|
||||
|
||||
-- Schedule the test-medipost-responses job to run every 15 minutes
|
||||
select
|
||||
cron.schedule(
|
||||
'send-test-medipost-responses-every-15-minutes', -- Unique job name
|
||||
'*/15 * * * *', -- Cron schedule: every 15 minutes
|
||||
$$
|
||||
select
|
||||
net.http_post(
|
||||
url := 'https://test.medreport.ee/api/job/test-medipost-responses',
|
||||
headers := jsonb_build_object(
|
||||
'x-jobs-api-key', 'fd26ec26-70ed-11f0-9e95-431ac3b15a84'
|
||||
)
|
||||
) as request_id;
|
||||
$$
|
||||
);
|
||||
19
schedule-setup/setup_sync_analysis_results_cron.sql
Normal file
19
schedule-setup/setup_sync_analysis_results_cron.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Enable required extensions for cron jobs and HTTP requests
|
||||
create extension if not exists pg_cron;
|
||||
create extension if not exists pg_net;
|
||||
|
||||
-- Schedule the sync-analysis-results job to run every 15 minutes
|
||||
select
|
||||
cron.schedule(
|
||||
'sync-analysis-results-every-15-minutes', -- Unique job name
|
||||
'*/15 * * * *', -- Cron schedule: every 15 minutes
|
||||
$$
|
||||
select
|
||||
net.http_post(
|
||||
url := 'https://test.medreport.ee/api/job/sync-analysis-results',
|
||||
headers := jsonb_build_object(
|
||||
'x-jobs-api-key', 'fd26ec26-70ed-11f0-9e95-431ac3b15a84'
|
||||
)
|
||||
) as request_id;
|
||||
$$
|
||||
);
|
||||
Reference in New Issue
Block a user