From 2b2a0b8bc4db0104989933b24d7396852fd33e7a Mon Sep 17 00:00:00 2001 From: k4rli Date: Wed, 27 Aug 2025 08:04:37 +0300 Subject: [PATCH] feat(MED-85): update dispatch order to medipost retry --- app/api/job/medipost-retry-dispatch/route.ts | 40 ++++++++++++++++++ lib/services/audit.service.ts | 12 +++++- lib/services/medipost.service.ts | 5 ++- lib/services/order.service.ts | 5 ++- packages/supabase/src/database.types.ts | 15 +++++++ ...20250825094541_medipost_dispatch_audit.sql | 28 ++++++++++++- ...20250825120858_medipost_retry_dispatch.sql | 42 +++++++++++++++++++ 7 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 app/api/job/medipost-retry-dispatch/route.ts create mode 100644 supabase/migrations/20250825120858_medipost_retry_dispatch.sql diff --git a/app/api/job/medipost-retry-dispatch/route.ts b/app/api/job/medipost-retry-dispatch/route.ts new file mode 100644 index 0000000..2258e2b --- /dev/null +++ b/app/api/job/medipost-retry-dispatch/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from "next/server"; +import loadEnv from "../handler/load-env"; +import validateApiKey from "../handler/validate-api-key"; +import { getOrderedAnalysisElementsIds, sendOrderToMedipost } from "~/lib/services/medipost.service"; +import { retrieveOrder } from "@lib/data/orders"; +import { getMedipostDispatchTries } from "~/lib/services/audit.service"; + +export const POST = async (request: NextRequest) => { + loadEnv(); + + const { medusaOrderId } = await request.json(); + + try { + validateApiKey(request); + } catch (e) { + return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' }); + } + + const tries = await getMedipostDispatchTries(medusaOrderId); + if (tries >= 3) { + return NextResponse.json({ + message: 'Order has been retried too many times', + }, { status: 400 }); + } + + try { + const medusaOrder = await retrieveOrder(medusaOrderId); + const orderedAnalysisElements = await getOrderedAnalysisElementsIds({ medusaOrder }); + await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements }); + console.info("Successfully sent order to medipost"); + return NextResponse.json({ + message: 'Successfully sent order to medipost', + }, { status: 200 }); + } catch (e) { + console.error("Error sending order to medipost", e); + return NextResponse.json({ + message: 'Failed to send order to medipost', + }, { status: 500 }); + } +}; diff --git a/lib/services/audit.service.ts b/lib/services/audit.service.ts index 5c102bf..880c037 100644 --- a/lib/services/audit.service.ts +++ b/lib/services/audit.service.ts @@ -4,6 +4,7 @@ import { RequestStatus } from '@/lib/types/audit'; import { ConnectedOnlineMethodName } from '@/lib/types/connected-online'; import { ExternalApi } from '@/lib/types/external'; import { MedipostAction } from '@/lib/types/medipost'; +import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client'; import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client'; export default async function logRequestResult( @@ -45,7 +46,7 @@ export async function logMedipostDispatch({ isMedipostError: boolean; errorMessage?: string; }) { - const { error } = await getSupabaseServerClient() + const { error } = await getSupabaseServerAdminClient() .schema('audit') .from('medipost_dispatch') .insert({ @@ -59,3 +60,12 @@ export async function logMedipostDispatch({ throw new Error('Failed to insert log entry, error: ' + error.message); } } + +export async function getMedipostDispatchTries(medusaOrderId: string) { + const { data } = await getSupabaseServerAdminClient() + .schema('medreport') + .rpc('get_medipost_dispatch_tries', { p_medusa_order_id: medusaOrderId }) + .throwOnError(); + + return data; +} diff --git a/lib/services/medipost.service.ts b/lib/services/medipost.service.ts index e809cdf..4b299d2 100644 --- a/lib/services/medipost.service.ts +++ b/lib/services/medipost.service.ts @@ -709,11 +709,12 @@ export async function sendOrderToMedipost({ try { await sendPrivateMessage(orderXml); } catch (e) { + const isMedipostError = e instanceof MedipostValidationError; await logMedipostDispatch({ medusaOrderId, isSuccess: false, - isMedipostError: e instanceof MedipostValidationError, - errorMessage: e instanceof MedipostValidationError ? e.response : undefined, + isMedipostError, + errorMessage: isMedipostError ? e.response : undefined, }); throw e; } diff --git a/lib/services/order.service.ts b/lib/services/order.service.ts index 998be03..8afb4c4 100644 --- a/lib/services/order.service.ts +++ b/lib/services/order.service.ts @@ -99,7 +99,10 @@ export async function getOrder({ throw new Error('Either medusaOrderId or orderId must be provided'); } - const { data: order } = await query.single().throwOnError(); + const { data: order, error } = await query.single(); + if (error) { + throw new Error(`Failed to get order by medusaOrderId=${medusaOrderId} or orderId=${orderId}, message=${error.message}, data=${JSON.stringify(order)}`); + } return order; } diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index f6a8221..af95d00 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -2003,6 +2003,21 @@ export type Database = { } Returns: Json } + medipost_retry_dispatch: { + Args: { + order_id: string + } + Returns: { + success: boolean + error: string | null + } + } + get_medipost_dispatch_tries: { + Args: { + p_medusa_order_id: string + } + Returns: number + } } Enums: { analysis_feedback_status: "STARTED" | "DRAFT" | "COMPLETED" diff --git a/supabase/migrations/20250825094541_medipost_dispatch_audit.sql b/supabase/migrations/20250825094541_medipost_dispatch_audit.sql index 97e65fd..c024626 100644 --- a/supabase/migrations/20250825094541_medipost_dispatch_audit.sql +++ b/supabase/migrations/20250825094541_medipost_dispatch_audit.sql @@ -5,8 +5,32 @@ CREATE TABLE "audit"."medipost_dispatch" ( "is_success" boolean not null, "error_message" text, "created_at" timestamp with time zone not null default now(), - "changed_by" uuid not null + "changed_by" uuid default auth.uid() ); -grant usage on schema audit to service_role; +grant usage on schema audit to authenticated; +grant select, insert, update, delete on table audit.medipost_dispatch to authenticated; +grant usage on schema medreport to service_role; + +alter table "audit"."medipost_dispatch" enable row level security; + +create policy "service_role_select" on "audit"."medipost_dispatch" for select to service_role using (true); +create policy "service_role_insert" on "audit"."medipost_dispatch" for insert to service_role with check (true); +create policy "service_role_update" on "audit"."medipost_dispatch" for update to service_role using (true); +create policy "service_role_delete" on "audit"."medipost_dispatch" for delete to service_role using (true); + +CREATE OR REPLACE FUNCTION medreport.get_medipost_dispatch_tries(p_medusa_order_id text) +returns integer +language plpgsql +security definer +as $function$ +declare + tries integer; +begin + select count(*) from audit.medipost_dispatch m where m.medusa_order_id = p_medusa_order_id and m.created_at > now() - interval '1 day' and m.is_success = false into tries; + return tries; +end; +$function$; + +grant execute on function medreport.get_medipost_dispatch_tries(text) to service_role; grant select, insert, update, delete on table audit.medipost_dispatch to service_role; diff --git a/supabase/migrations/20250825120858_medipost_retry_dispatch.sql b/supabase/migrations/20250825120858_medipost_retry_dispatch.sql new file mode 100644 index 0000000..73e6e27 --- /dev/null +++ b/supabase/migrations/20250825120858_medipost_retry_dispatch.sql @@ -0,0 +1,42 @@ +create extension if not exists pg_net; + +create or replace function medreport.medipost_retry_dispatch( + order_id text +) +returns jsonb +language plpgsql +as $function$ +declare + response_result record; +begin + select into response_result + net.http_post( + url := 'https://test.medreport.ee/api/job/medipost-retry-dispatch', + headers := jsonb_build_object( + 'Content-Type', 'application/json', + 'x-jobs-api-key', 'fd26ec26-70ed-11f0-9e95-431ac3b15a84' + ), + body := jsonb_build_object( + 'medusaOrderId', order_id + )::text + ) as request_id; + + return jsonb_build_object( + 'success', true + ); + +exception + when others then + return jsonb_build_object( + 'success', false + ); +end; +$function$; + +grant execute on function medreport.medipost_retry_dispatch(text) to service_role; + +comment on function medreport.medipost_retry_dispatch(text) is +'Manually trigger a medipost retry dispatch for a specific order ID. +Parameters: +- order_id: The medusa order ID to retry dispatch for +Returns: JSONB with success status and request details';