From 44670965ae0cbae729421e4456d1b7a90b18ca4f Mon Sep 17 00:00:00 2001 From: k4rli Date: Sat, 16 Aug 2025 14:59:46 +0300 Subject: [PATCH 1/2] feat(MED-105): update email --- packages/email-templates/src/locales/en/synlab-email.json | 2 +- packages/email-templates/src/locales/et/synlab-email.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/email-templates/src/locales/en/synlab-email.json b/packages/email-templates/src/locales/en/synlab-email.json index ae86859..cde6548 100644 --- a/packages/email-templates/src/locales/en/synlab-email.json +++ b/packages/email-templates/src/locales/en/synlab-email.json @@ -3,7 +3,7 @@ "previewText": "Your Medreport order has been placed - {{analysisPackageName}}", "heading": "Your Medreport order has been placed - {{analysisPackageName}}", "hello": "Hello {{personName}},", - "lines1": "The order for {{analysisPackageName}} analysis package has been sent to the lab. Please go to the lab to collect the sample: Medreport - {{partnerLocationName}}", + "lines1": "The order for {{analysisPackageName}} analysis package has been sent to the lab. Please go to the lab to collect the sample: Synlab - {{partnerLocationName}}", "lines2": "If you are unable to go to the lab to collect the sample, you can go to any other suitable collection point - view locations and opening hours.", "lines3": "It is recommended to collect the sample in the morning (before 12:00) and not to eat or drink (water can be drunk).", "lines4": "At the collection point, select the order from the queue: the order from the doctor.", diff --git a/packages/email-templates/src/locales/et/synlab-email.json b/packages/email-templates/src/locales/et/synlab-email.json index 843aa87..fa16c20 100644 --- a/packages/email-templates/src/locales/et/synlab-email.json +++ b/packages/email-templates/src/locales/et/synlab-email.json @@ -3,7 +3,7 @@ "previewText": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}", "heading": "Teie Medreport tellimus on kinnitatud - {{analysisPackageName}}", "hello": "Tere {{personName}},", - "lines1": "Saatekiri {{analysisPackageName}} analüüsi uuringuteks on saadetud laborisse digitaalselt. Palun mine proove andma: Medreport - {{partnerLocationName}}", + "lines1": "Saatekiri {{analysisPackageName}} analüüsi uuringuteks on saadetud laborisse digitaalselt. Palun mine proove andma: Synlab - {{partnerLocationName}}", "lines2": "Kui Teil ei ole võimalik valitud asukohta minna proove andma, siis võite minna endale sobivasse proovivõtupunkti - vaata asukohti ja lahtiolekuaegasid.", "lines3": "Soovituslik on proove anda pigem hommikul (enne 12:00) ning söömata ja joomata (vett võib juua).", "lines4": "Proovivõtupunktis valige järjekorrasüsteemis: saatekirjad alt eriarsti saatekiri.", From d65a6e8a4ae33fdc5f4ff4794d8a5b67b7f3ea7e Mon Sep 17 00:00:00 2001 From: Karli Date: Mon, 18 Aug 2025 13:04:42 +0300 Subject: [PATCH 2/2] feat(MED-105): update sending test analysis results --- app/api/job/test-medipost-responses/route.ts | 54 +++++++++++++++++++ .../_components/analysis-level-bar.tsx | 37 +++++++++++-- .../analysis-results/_components/analysis.tsx | 12 +++-- .../(dashboard)/analysis-results/page.tsx | 4 +- lib/services/medipost.service.ts | 5 +- lib/services/medipostTest.service.ts | 6 +-- lib/services/order.service.ts | 28 +++++++++- lib/types/medipost.ts | 6 +-- .../setup_send_analysis_test_results_cron.sql | 19 +++++++ .../setup_sync_analysis_results_cron.sql | 19 +++++++ 10 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 app/api/job/test-medipost-responses/route.ts create mode 100644 schedule-setup/setup_send_analysis_test_results_cron.sql create mode 100644 schedule-setup/setup_sync_analysis_results_cron.sql diff --git a/app/api/job/test-medipost-responses/route.ts b/app/api/job/test-medipost-responses/route.ts new file mode 100644 index 0000000..2cf8fa7 --- /dev/null +++ b/app/api/job/test-medipost-responses/route.ts @@ -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 }); +} diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx index c092fe5..66ceeac 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis-level-bar.tsx @@ -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 (
{isActive && ( -
+
)} @@ -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 (
{normLowerIncluded && ( @@ -73,8 +101,9 @@ const AnalysisLevelBar = ({ {normUpperIncluded && ( diff --git a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx index 85d52e2..91af308 100644 --- a/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/_components/analysis.tsx @@ -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 (
@@ -99,9 +100,10 @@ const Analysis = ({
) : ( diff --git a/app/home/(user)/(dashboard)/analysis-results/page.tsx b/app/home/(user)/(dashboard)/analysis-results/page.tsx index d31cce5..27041df 100644 --- a/app/home/(user)/(dashboard)/analysis-results/page.tsx +++ b/app/home/(user)/(dashboard)/analysis-results/page.tsx @@ -82,6 +82,7 @@ async function AnalysisResultsPage() {
{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() {
{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 ( diff --git a/lib/services/medipost.service.ts b/lib/services/medipost.service.ts index 4490c0b..8508663 100644 --- a/lib/services/medipost.service.ts +++ b/lib/services/medipost.service.ts @@ -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}`); } diff --git a/lib/services/medipostTest.service.ts b/lib/services/medipostTest.service.ts index 192db4c..1a3b0d9 100644 --- a/lib/services/medipostTest.service.ts +++ b/lib/services/medipostTest.service.ts @@ -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 (` @@ -175,7 +175,7 @@ export async function composeOrderTestResponseXML({ ${formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')} ${upper} ${lower} - 0 + ${result < lower ? 1 : (result > upper ? 1 : 0)} 1 diff --git a/lib/services/order.service.ts b/lib/services/order.service.ts index 3725e5c..998be03 100644 --- a/lib/services/order.service.ts +++ b/lib/services/order.service.ts @@ -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('*') diff --git a/lib/types/medipost.ts b/lib/types/medipost.ts index b47d985..583fc33 100644 --- a/lib/types/medipost.ts +++ b/lib/types/medipost.ts @@ -261,7 +261,7 @@ export const AnalysisOrderStatus = { 6: 'CANCELLED', } as const; export const NormStatus: Record = { - 1: 'NORMAL', - 2: 'WARNING', - 3: 'REQUIRES_ATTENTION', + 0: 'NORMAL', + 1: 'WARNING', + 2: 'REQUIRES_ATTENTION', } as const; diff --git a/schedule-setup/setup_send_analysis_test_results_cron.sql b/schedule-setup/setup_send_analysis_test_results_cron.sql new file mode 100644 index 0000000..2562939 --- /dev/null +++ b/schedule-setup/setup_send_analysis_test_results_cron.sql @@ -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; + $$ + ); diff --git a/schedule-setup/setup_sync_analysis_results_cron.sql b/schedule-setup/setup_sync_analysis_results_cron.sql new file mode 100644 index 0000000..832f982 --- /dev/null +++ b/schedule-setup/setup_sync_analysis_results_cron.sql @@ -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; + $$ + );