@@ -23,7 +23,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
'Successfully sent out open job notification emails to doctors.',
|
'Successfully sent out open job notification emails to doctors.',
|
||||||
);
|
);
|
||||||
await createNotificationLog({
|
await createNotificationLog({
|
||||||
action: NotificationAction.NEW_JOBS_ALERT,
|
action: NotificationAction.DOCTOR_NEW_JOBS,
|
||||||
status: 'SUCCESS',
|
status: 'SUCCESS',
|
||||||
});
|
});
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -39,7 +39,7 @@ export const POST = async (request: NextRequest) => {
|
|||||||
e,
|
e,
|
||||||
);
|
);
|
||||||
await createNotificationLog({
|
await createNotificationLog({
|
||||||
action: NotificationAction.NEW_JOBS_ALERT,
|
action: NotificationAction.DOCTOR_NEW_JOBS,
|
||||||
status: 'FAIL',
|
status: 'FAIL',
|
||||||
comment: e?.message,
|
comment: e?.message,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { getOrder } from "~/lib/services/order.service";
|
import { getAnalysisOrder } from "~/lib/services/order.service";
|
||||||
import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipostTest.service";
|
import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipostTest.service";
|
||||||
import { retrieveOrder } from "@lib/data";
|
import { retrieveOrder } from "@lib/data";
|
||||||
import { getAccountAdmin } from "~/lib/services/account.service";
|
import { getAccountAdmin } from "~/lib/services/account.service";
|
||||||
@@ -14,9 +14,9 @@ export async function POST(request: Request) {
|
|||||||
const { medusaOrderId } = await request.json();
|
const { medusaOrderId } = await request.json();
|
||||||
|
|
||||||
const medusaOrder = await retrieveOrder(medusaOrderId)
|
const medusaOrder = await retrieveOrder(medusaOrderId)
|
||||||
const medreportOrder = await getOrder({ medusaOrderId });
|
const analysisOrder = await getAnalysisOrder({ medusaOrderId });
|
||||||
|
|
||||||
const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id });
|
const account = await getAccountAdmin({ primaryOwnerUserId: analysisOrder.user_id });
|
||||||
const orderedAnalysisElementsIds = await getOrderedAnalysisIds({ medusaOrder });
|
const orderedAnalysisElementsIds = await getOrderedAnalysisIds({ 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} ordered analysis elements`);
|
||||||
@@ -30,7 +30,7 @@ export async function POST(request: Request) {
|
|||||||
orderedAnalysisElementsIds: orderedAnalysisElementsIds.map(({ analysisElementId }) => analysisElementId).filter(Boolean) as number[],
|
orderedAnalysisElementsIds: orderedAnalysisElementsIds.map(({ analysisElementId }) => analysisElementId).filter(Boolean) as number[],
|
||||||
orderedAnalysesIds: orderedAnalysisElementsIds.map(({ analysisId }) => analysisId).filter(Boolean) as number[],
|
orderedAnalysesIds: orderedAnalysisElementsIds.map(({ analysisId }) => analysisId).filter(Boolean) as number[],
|
||||||
orderId: medusaOrderId,
|
orderId: medusaOrderId,
|
||||||
orderCreatedAt: new Date(medreportOrder.created_at),
|
orderCreatedAt: new Date(analysisOrder.created_at),
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ export default async function AnalysisResultsPage({
|
|||||||
}) {
|
}) {
|
||||||
const account = await loadCurrentUserAccount();
|
const account = await loadCurrentUserAccount();
|
||||||
|
|
||||||
const { id: analysisResponseId } = await params;
|
const { id: analysisOrderId } = await params;
|
||||||
|
|
||||||
const analysisResponse = await loadUserAnalysis(Number(analysisResponseId));
|
const analysisResponse = await loadUserAnalysis(Number(analysisOrderId));
|
||||||
|
|
||||||
if (!account?.id || !analysisResponse) {
|
if (!account?.id || !analysisResponse) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ import { loadCurrentUserAccount } from "@/app/home/(user)/_lib/server/load-user-
|
|||||||
import { listProductTypes } from "@lib/data/products";
|
import { listProductTypes } from "@lib/data/products";
|
||||||
import { placeOrder, retrieveCart } from "@lib/data/cart";
|
import { placeOrder, retrieveCart } from "@lib/data/cart";
|
||||||
import { createI18nServerInstance } from "~/lib/i18n/i18n.server";
|
import { createI18nServerInstance } from "~/lib/i18n/i18n.server";
|
||||||
import { createOrder } from '~/lib/services/order.service';
|
import { createAnalysisOrder, getAnalysisOrder } from '~/lib/services/order.service';
|
||||||
import { getOrderedAnalysisIds, sendOrderToMedipost } from '~/lib/services/medipost.service';
|
import { getOrderedAnalysisIds, sendOrderToMedipost } from '~/lib/services/medipost.service';
|
||||||
import { createNotificationsApi } from '@kit/notifications/api';
|
import { createNotificationsApi } from '@kit/notifications/api';
|
||||||
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
import { AccountWithParams } from '@kit/accounts/api';
|
import type { AccountWithParams } from '@kit/accounts/api';
|
||||||
|
import type { StoreOrder } from '@medusajs/types';
|
||||||
|
|
||||||
const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages';
|
const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages';
|
||||||
|
const ANALYSIS_TYPE_HANDLE = 'synlab-analysis';
|
||||||
const MONTONIO_PAID_STATUS = 'PAID';
|
const MONTONIO_PAID_STATUS = 'PAID';
|
||||||
|
|
||||||
const env = () => z
|
const env = () => z
|
||||||
@@ -38,14 +40,12 @@ const sendEmail = async ({
|
|||||||
account,
|
account,
|
||||||
email,
|
email,
|
||||||
analysisPackageName,
|
analysisPackageName,
|
||||||
personName,
|
|
||||||
partnerLocationName,
|
partnerLocationName,
|
||||||
language,
|
language,
|
||||||
}: {
|
}: {
|
||||||
account: AccountWithParams,
|
account: Pick<AccountWithParams, 'name' | 'id'>,
|
||||||
email: string,
|
email: string,
|
||||||
analysisPackageName: string,
|
analysisPackageName: string,
|
||||||
personName: string,
|
|
||||||
partnerLocationName: string,
|
partnerLocationName: string,
|
||||||
language: string,
|
language: string,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -58,7 +58,7 @@ const sendEmail = async ({
|
|||||||
|
|
||||||
const { html, subject } = await renderSynlabAnalysisPackageEmail({
|
const { html, subject } = await renderSynlabAnalysisPackageEmail({
|
||||||
analysisPackageName,
|
analysisPackageName,
|
||||||
personName,
|
personName: account.name,
|
||||||
partnerLocationName,
|
partnerLocationName,
|
||||||
language,
|
language,
|
||||||
});
|
});
|
||||||
@@ -83,9 +83,7 @@ const sendEmail = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processMontonioCallback(orderToken: string) {
|
async function decodeOrderToken(orderToken: string) {
|
||||||
const { language } = await createI18nServerInstance();
|
|
||||||
|
|
||||||
const secretKey = process.env.MONTONIO_SECRET_KEY as string;
|
const secretKey = process.env.MONTONIO_SECRET_KEY as string;
|
||||||
|
|
||||||
const decoded = jwt.verify(orderToken, secretKey, {
|
const decoded = jwt.verify(orderToken, secretKey, {
|
||||||
@@ -96,50 +94,115 @@ export async function processMontonioCallback(orderToken: string) {
|
|||||||
throw new Error("Payment not successful");
|
throw new Error("Payment not successful");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCartByOrderToken(decoded: MontonioOrderToken) {
|
||||||
|
const [, , cartId] = decoded.merchantReferenceDisplay.split(':');
|
||||||
|
if (!cartId) {
|
||||||
|
throw new Error("Cart ID not found");
|
||||||
|
}
|
||||||
|
const cart = await retrieveCart(cartId);
|
||||||
|
if (!cart) {
|
||||||
|
throw new Error("Cart not found");
|
||||||
|
}
|
||||||
|
return cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getOrderResultParameters(medusaOrder: StoreOrder) {
|
||||||
|
const { productTypes } = await listProductTypes();
|
||||||
|
const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE);
|
||||||
|
const analysisType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_TYPE_HANDLE);
|
||||||
|
|
||||||
|
const analysisPackageOrderItem = medusaOrder.items?.find(({ product_type_id }) => product_type_id === analysisPackagesType?.id);
|
||||||
|
const analysisItems = medusaOrder.items?.filter(({ product_type_id }) => product_type_id === analysisType?.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
medusaOrderId: medusaOrder.id,
|
||||||
|
email: medusaOrder.email,
|
||||||
|
analysisPackageOrder: analysisPackageOrderItem
|
||||||
|
? {
|
||||||
|
partnerLocationName: analysisPackageOrderItem?.metadata?.partner_location_name as string ?? '',
|
||||||
|
analysisPackageName: analysisPackageOrderItem?.title ?? '',
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
analysisItemsOrder: Array.isArray(analysisItems) && analysisItems.length > 0
|
||||||
|
? analysisItems.map(({ product }) => ({
|
||||||
|
analysisName: product?.title ?? '',
|
||||||
|
analysisId: product?.metadata?.analysisIdOriginal as string ?? '',
|
||||||
|
}))
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendAnalysisPackageOrderEmail({
|
||||||
|
account,
|
||||||
|
email,
|
||||||
|
analysisPackageOrder,
|
||||||
|
}: {
|
||||||
|
account: AccountWithParams,
|
||||||
|
email: string,
|
||||||
|
analysisPackageOrder: {
|
||||||
|
partnerLocationName: string,
|
||||||
|
analysisPackageName: string,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
const { language } = await createI18nServerInstance();
|
||||||
|
const { analysisPackageName, partnerLocationName } = analysisPackageOrder;
|
||||||
|
try {
|
||||||
|
await sendEmail({
|
||||||
|
account: { id: account.id, name: account.name },
|
||||||
|
email,
|
||||||
|
analysisPackageName,
|
||||||
|
partnerLocationName,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to send email", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function processMontonioCallback(orderToken: string) {
|
||||||
const account = await loadCurrentUserAccount();
|
const account = await loadCurrentUserAccount();
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error("Account not found in context");
|
throw new Error("Account not found in context");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [, , cartId] = decoded.merchantReferenceDisplay.split(':');
|
const decoded = await decodeOrderToken(orderToken);
|
||||||
if (!cartId) {
|
const cart = await getCartByOrderToken(decoded);
|
||||||
throw new Error("Cart ID not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const cart = await retrieveCart(cartId);
|
const medusaOrder = await placeOrder(cart.id, { revalidateCacheTags: false });
|
||||||
if (!cart) {
|
|
||||||
throw new Error("Cart not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const medusaOrder = await placeOrder(cartId, { revalidateCacheTags: false });
|
|
||||||
const orderedAnalysisElements = await getOrderedAnalysisIds({ medusaOrder });
|
const orderedAnalysisElements = await getOrderedAnalysisIds({ medusaOrder });
|
||||||
const orderId = await createOrder({ medusaOrder, orderedAnalysisElements });
|
|
||||||
|
|
||||||
const { productTypes } = await listProductTypes();
|
try {
|
||||||
const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE);
|
const existingAnalysisOrder = await getAnalysisOrder({ medusaOrderId: medusaOrder.id });
|
||||||
const analysisPackageOrderItem = medusaOrder.items?.find(({ product_type_id }) => product_type_id === analysisPackagesType?.id);
|
console.info(`Analysis order already exists for medusaOrderId=${medusaOrder.id}, orderId=${existingAnalysisOrder.id}`);
|
||||||
|
return { success: true, orderId: existingAnalysisOrder.id };
|
||||||
|
} catch {
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
const orderResult = {
|
const orderId = await createAnalysisOrder({ medusaOrder, orderedAnalysisElements });
|
||||||
medusaOrderId: medusaOrder.id,
|
const orderResult = await getOrderResultParameters(medusaOrder);
|
||||||
email: medusaOrder.email,
|
|
||||||
partnerLocationName: analysisPackageOrderItem?.metadata?.partner_location_name as string ?? '',
|
|
||||||
analysisPackageName: analysisPackageOrderItem?.title ?? '',
|
|
||||||
orderedAnalysisElements,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { medusaOrderId, email, partnerLocationName, analysisPackageName } = orderResult;
|
const { medusaOrderId, email, analysisPackageOrder, analysisItemsOrder } = orderResult;
|
||||||
const personName = account.name;
|
|
||||||
|
|
||||||
if (email && analysisPackageName) {
|
if (email) {
|
||||||
try {
|
if (analysisPackageOrder) {
|
||||||
await sendEmail({ account, email, analysisPackageName, personName, partnerLocationName, language });
|
await sendAnalysisPackageOrderEmail({ account, email, analysisPackageOrder });
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error("Failed to send email", error);
|
console.info(`Order has no analysis package, skipping email.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analysisItemsOrder) {
|
||||||
|
// @TODO send email for separate analyses
|
||||||
|
console.warn(`Order has analysis items, but no email template exists yet`);
|
||||||
|
} else {
|
||||||
|
console.info(`Order has no analysis items, skipping email.`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// @TODO send email for separate analyses
|
console.error("Missing email to send order result email", orderResult);
|
||||||
console.error("Missing email or analysisPackageName", orderResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements });
|
await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements });
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { PageBody, PageHeader } from '@kit/ui/page';
|
|||||||
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
import { getOrder } from '~/lib/services/order.service';
|
import { getAnalysisOrder } from '~/lib/services/order.service';
|
||||||
import { retrieveOrder } from '@lib/data/orders';
|
import { retrieveOrder } from '@lib/data/orders';
|
||||||
import { pathsConfig } from '@kit/shared/config';
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ async function OrderConfirmedPage(props: {
|
|||||||
}) {
|
}) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
const order = await getOrder({ orderId: Number(params.orderId) }).catch(() => null);
|
const order = await getAnalysisOrder({ orderId: Number(params.orderId) }).catch(() => null);
|
||||||
if (!order) {
|
if (!order) {
|
||||||
redirect(pathsConfig.app.myOrders);
|
redirect(pathsConfig.app.myOrders);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { PageBody, PageHeader } from '@kit/ui/page';
|
|||||||
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '@/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
import { getOrder } from '~/lib/services/order.service';
|
import { getAnalysisOrder } from '~/lib/services/order.service';
|
||||||
import { retrieveOrder } from '@lib/data/orders';
|
import { retrieveOrder } from '@lib/data/orders';
|
||||||
import { pathsConfig } from '@kit/shared/config';
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ async function OrderConfirmedPage(props: {
|
|||||||
}) {
|
}) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
const order = await getOrder({ orderId: Number(params.orderId) }).catch(() => null);
|
const order = await getAnalysisOrder({ orderId: Number(params.orderId) }).catch(() => null);
|
||||||
if (!order) {
|
if (!order) {
|
||||||
redirect(pathsConfig.app.myOrders);
|
redirect(pathsConfig.app.myOrders);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { Tables } from '@/packages/supabase/src/database.types';
|
import type { Tables } from '@/packages/supabase/src/database.types';
|
||||||
|
|
||||||
import { AccountWithParams } from '@kit/accounts/api';
|
|
||||||
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';
|
||||||
|
|
||||||
@@ -26,6 +25,19 @@ export async function getAccount(id: string): Promise<AccountWithMemberships> {
|
|||||||
return data as unknown as AccountWithMemberships;
|
return data as unknown as AccountWithMemberships;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserContactAdmin(userId: string) {
|
||||||
|
const { data } = await getSupabaseServerAdminClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('accounts')
|
||||||
|
.select('name, last_name, email, preferred_locale')
|
||||||
|
.eq('primary_owner_user_id', userId)
|
||||||
|
.eq('is_personal_account', true)
|
||||||
|
.single()
|
||||||
|
.throwOnError();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAccountAdmin({
|
export async function getAccountAdmin({
|
||||||
primaryOwnerUserId,
|
primaryOwnerUserId,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ import { Database } from '@kit/supabase/database';
|
|||||||
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
|
|
||||||
export enum NotificationAction {
|
export enum NotificationAction {
|
||||||
DOCTOR_FEEDBACK_RECEIVED = 'DOCTOR_FEEDBACK_RECEIVED',
|
DOCTOR_NEW_JOBS = 'DOCTOR_NEW_JOBS',
|
||||||
NEW_JOBS_ALERT = 'NEW_JOBS_ALERT',
|
DOCTOR_PATIENT_RESULTS_RECEIVED = 'DOCTOR_PATIENT_RESULTS_RECEIVED',
|
||||||
PATIENT_RESULTS_RECEIVED_ALERT = 'PATIENT_RESULTS_RECEIVED_ALERT',
|
PATIENT_DOCTOR_FEEDBACK_RECEIVED = 'PATIENT_DOCTOR_FEEDBACK_RECEIVED',
|
||||||
|
PATIENT_ORDER_PROCESSING = 'PATIENT_ORDER_PROCESSING',
|
||||||
|
PATIENT_FIRST_RESULTS_RECEIVED = 'PATIENT_FIRST_RESULTS_RECEIVED',
|
||||||
|
PATIENT_FULL_RESULTS_RECEIVED = 'PATIENT_FULL_RESULTS_RECEIVED',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createNotificationLog = async ({
|
export const createNotificationLog = async ({
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export const createPageViewLog = async ({
|
|||||||
account_id: accountId,
|
account_id: accountId,
|
||||||
action,
|
action,
|
||||||
changed_by: user.id,
|
changed_by: user.id,
|
||||||
extra_data: extraData,
|
|
||||||
})
|
})
|
||||||
.throwOnError();
|
.throwOnError();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type EmailTemplate = {
|
|||||||
subject: string;
|
subject: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type EmailRenderer<T = any> = (params: T) => Promise<EmailTemplate>;
|
export type EmailRenderer<T = any> = (params: T) => Promise<EmailTemplate>;
|
||||||
|
|
||||||
export const sendEmailFromTemplate = async <T>(
|
export const sendEmailFromTemplate = async <T>(
|
||||||
renderer: EmailRenderer<T>,
|
renderer: EmailRenderer<T>,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { XMLParser } from 'fast-xml-parser';
|
|||||||
import { Tables } from '@kit/supabase/database';
|
import { Tables } from '@kit/supabase/database';
|
||||||
import { createAnalysisGroup } from './analysis-group.service';
|
import { createAnalysisGroup } from './analysis-group.service';
|
||||||
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
|
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
|
||||||
import { getOrder, updateOrderStatus } from './order.service';
|
import { getAnalysisOrder, updateAnalysisOrderStatus } from './order.service';
|
||||||
import { getAnalysisElements, getAnalysisElementsAdmin } from './analysis-element.service';
|
import { getAnalysisElements, getAnalysisElementsAdmin } from './analysis-element.service';
|
||||||
import { getAnalyses } from './analyses.service';
|
import { getAnalyses } from './analyses.service';
|
||||||
import { getAccountAdmin } from './account.service';
|
import { getAccountAdmin } from './account.service';
|
||||||
@@ -242,7 +242,7 @@ export async function readPrivateMessageResponse({
|
|||||||
|
|
||||||
let order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
let order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
||||||
try {
|
try {
|
||||||
order = await getOrder({ medusaOrderId });
|
order = await getAnalysisOrder({ medusaOrderId });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await deletePrivateMessage(privateMessage.messageId);
|
await deletePrivateMessage(privateMessage.messageId);
|
||||||
throw new Error(`Order not found by Medipost message ValisTellimuseId=${medusaOrderId}`);
|
throw new Error(`Order not found by Medipost message ValisTellimuseId=${medusaOrderId}`);
|
||||||
@@ -251,11 +251,11 @@ export async function readPrivateMessageResponse({
|
|||||||
const status = await syncPrivateMessage({ messageResponse, order });
|
const status = await syncPrivateMessage({ messageResponse, order });
|
||||||
|
|
||||||
if (status.isPartial) {
|
if (status.isPartial) {
|
||||||
await updateOrderStatus({ medusaOrderId, orderStatus: 'PARTIAL_ANALYSIS_RESPONSE' });
|
await updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'PARTIAL_ANALYSIS_RESPONSE' });
|
||||||
hasAnalysisResponse = true;
|
hasAnalysisResponse = true;
|
||||||
hasPartialAnalysisResponse = true;
|
hasPartialAnalysisResponse = true;
|
||||||
} else if (status.isCompleted) {
|
} else if (status.isCompleted) {
|
||||||
await updateOrderStatus({ medusaOrderId, orderStatus: 'FULL_ANALYSIS_RESPONSE' });
|
await updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'FULL_ANALYSIS_RESPONSE' });
|
||||||
await deletePrivateMessage(privateMessage.messageId);
|
await deletePrivateMessage(privateMessage.messageId);
|
||||||
hasAnalysisResponse = true;
|
hasAnalysisResponse = true;
|
||||||
hasFullAnalysisResponse = true;
|
hasFullAnalysisResponse = true;
|
||||||
@@ -588,7 +588,7 @@ export async function sendOrderToMedipost({
|
|||||||
medusaOrderId: string;
|
medusaOrderId: string;
|
||||||
orderedAnalysisElements: OrderedAnalysisElement[];
|
orderedAnalysisElements: OrderedAnalysisElement[];
|
||||||
}) {
|
}) {
|
||||||
const medreportOrder = await getOrder({ medusaOrderId });
|
const medreportOrder = await getAnalysisOrder({ medusaOrderId });
|
||||||
const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id });
|
const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id });
|
||||||
|
|
||||||
const orderedAnalysesIds = orderedAnalysisElements
|
const orderedAnalysesIds = orderedAnalysisElements
|
||||||
@@ -668,7 +668,7 @@ export async function sendOrderToMedipost({
|
|||||||
hasAnalysisResults: false,
|
hasAnalysisResults: false,
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
});
|
});
|
||||||
await updateOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING' });
|
await updateAnalysisOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING' });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrderedAnalysisIds({
|
export async function getOrderedAnalysisIds({
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { StoreOrder } from '@medusajs/types';
|
|||||||
|
|
||||||
export type AnalysisOrder = Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
export type AnalysisOrder = Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
||||||
|
|
||||||
export async function createOrder({
|
export async function createAnalysisOrder({
|
||||||
medusaOrder,
|
medusaOrder,
|
||||||
orderedAnalysisElements,
|
orderedAnalysisElements,
|
||||||
}: {
|
}: {
|
||||||
@@ -38,7 +38,7 @@ export async function createOrder({
|
|||||||
return orderResult.data.id;
|
return orderResult.data.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateOrder({
|
export async function updateAnalysisOrder({
|
||||||
orderId,
|
orderId,
|
||||||
orderStatus,
|
orderStatus,
|
||||||
}: {
|
}: {
|
||||||
@@ -56,7 +56,7 @@ export async function updateOrder({
|
|||||||
.throwOnError();
|
.throwOnError();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateOrderStatus({
|
export async function updateAnalysisOrderStatus({
|
||||||
orderId,
|
orderId,
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
orderStatus,
|
orderStatus,
|
||||||
@@ -80,7 +80,7 @@ export async function updateOrderStatus({
|
|||||||
.throwOnError();
|
.throwOnError();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getOrder({
|
export async function getAnalysisOrder({
|
||||||
medusaOrderId,
|
medusaOrderId,
|
||||||
orderId,
|
orderId,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const getUser = (request: NextRequest, response: NextResponse) => {
|
|||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest) {
|
||||||
const secureHeaders = await createResponseWithSecureHeaders();
|
const secureHeaders = await createResponseWithSecureHeaders();
|
||||||
const response = NextResponse.next(secureHeaders);
|
const response = NextResponse.next(secureHeaders);
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const lang = url.searchParams.get('lang');
|
||||||
|
|
||||||
// set a unique request ID for each request
|
// set a unique request ID for each request
|
||||||
// this helps us log and trace requests
|
// this helps us log and trace requests
|
||||||
@@ -35,6 +37,10 @@ export async function middleware(request: NextRequest) {
|
|||||||
// apply CSRF protection for mutating requests
|
// apply CSRF protection for mutating requests
|
||||||
const csrfResponse = await withCsrfMiddleware(request, response);
|
const csrfResponse = await withCsrfMiddleware(request, response);
|
||||||
|
|
||||||
|
if (lang) {
|
||||||
|
csrfResponse.cookies.set('lang', lang);
|
||||||
|
}
|
||||||
|
|
||||||
// handle patterns for specific routes
|
// handle patterns for specific routes
|
||||||
const handlePattern = matchUrlPattern(request.url);
|
const handlePattern = matchUrlPattern(request.url);
|
||||||
|
|
||||||
@@ -176,6 +182,14 @@ function getPatterns() {
|
|||||||
return NextResponse.redirect(
|
return NextResponse.redirect(
|
||||||
new URL(pathsConfig.app.home, req.nextUrl.origin).href,
|
new URL(pathsConfig.app.home, req.nextUrl.origin).href,
|
||||||
);
|
);
|
||||||
|
} else if (
|
||||||
|
!['test', 'localhost'].some((pathString) =>
|
||||||
|
process.env.NEXT_PUBLIC_SITE_URL?.includes(pathString),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL('https://medreport.ee', req.nextUrl.origin).href,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||||
"cssnano": "^7.0.7",
|
"cssnano": "^7.0.7",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"pino-pretty": "^13.0.0",
|
"pino-pretty": "13.0.0",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"supabase": "^2.30.4",
|
"supabase": "^2.30.4",
|
||||||
"tailwindcss": "4.1.7",
|
"tailwindcss": "4.1.7",
|
||||||
|
|||||||
@@ -1,20 +1,7 @@
|
|||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import {
|
|
||||||
renderAllResultsReceivedEmail,
|
|
||||||
renderFirstResultsReceivedEmail,
|
|
||||||
} from '@kit/email-templates';
|
|
||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
import {
|
|
||||||
getAssignedDoctorAccount,
|
|
||||||
getDoctorAccounts,
|
|
||||||
} from '../../../../../lib/services/account.service';
|
|
||||||
import {
|
|
||||||
NotificationAction,
|
|
||||||
createNotificationLog,
|
|
||||||
} from '../../../../../lib/services/audit/notificationEntries.service';
|
|
||||||
import { sendEmailFromTemplate } from '../../../../../lib/services/mailer.service';
|
|
||||||
import { RecordChange, Tables } from '../record-change.type';
|
import { RecordChange, Tables } from '../record-change.type';
|
||||||
|
|
||||||
export function createDatabaseWebhookRouterService(
|
export function createDatabaseWebhookRouterService(
|
||||||
@@ -113,58 +100,13 @@ class DatabaseWebhookRouterService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let action;
|
const { createAnalysisOrderWebhooksService } = await import(
|
||||||
try {
|
'@kit/notifications/webhooks/analysis-order-notifications.service'
|
||||||
const data = {
|
);
|
||||||
analysisOrderId: record.id,
|
|
||||||
language: 'et',
|
|
||||||
};
|
|
||||||
|
|
||||||
if (record.status === 'PARTIAL_ANALYSIS_RESPONSE') {
|
const service = createAnalysisOrderWebhooksService();
|
||||||
action = NotificationAction.NEW_JOBS_ALERT;
|
|
||||||
|
|
||||||
const doctorAccounts = await getDoctorAccounts();
|
return service.handleStatusChangeWebhook(record);
|
||||||
const doctorEmails: string[] = doctorAccounts
|
|
||||||
.map(({ email }) => email)
|
|
||||||
.filter((email): email is string => !!email);
|
|
||||||
|
|
||||||
await sendEmailFromTemplate(
|
|
||||||
renderFirstResultsReceivedEmail,
|
|
||||||
data,
|
|
||||||
doctorEmails,
|
|
||||||
);
|
|
||||||
} else if (record.status === 'FULL_ANALYSIS_RESPONSE') {
|
|
||||||
action = NotificationAction.PATIENT_RESULTS_RECEIVED_ALERT;
|
|
||||||
const doctorAccount = await getAssignedDoctorAccount(record.id);
|
|
||||||
const assignedDoctorEmail = doctorAccount?.email;
|
|
||||||
|
|
||||||
if (!assignedDoctorEmail) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await sendEmailFromTemplate(
|
|
||||||
renderAllResultsReceivedEmail,
|
|
||||||
data,
|
|
||||||
assignedDoctorEmail,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action) {
|
|
||||||
await createNotificationLog({
|
|
||||||
action,
|
|
||||||
status: 'SUCCESS',
|
|
||||||
relatedRecordId: record.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
if (action)
|
|
||||||
await createNotificationLog({
|
|
||||||
action,
|
|
||||||
status: 'FAIL',
|
|
||||||
comment: e?.message,
|
|
||||||
relatedRecordId: record.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,33 +49,28 @@ export async function renderAccountDeleteEmail(props: Props) {
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{previewText}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:hello`, {
|
{t(`${namespace}:hello`, {
|
||||||
displayName: props.userDisplayName,
|
displayName: props.userDisplayName,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:paragraph1`, {
|
{t(`${namespace}:paragraph1`, {
|
||||||
productName: props.productName,
|
productName: props.productName,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:paragraph2`)}
|
{t(`${namespace}:paragraph2`)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:paragraph3`, {
|
{t(`${namespace}:paragraph3`, {
|
||||||
productName: props.productName,
|
productName: props.productName,
|
||||||
})}
|
})}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:paragraph4`, {
|
{t(`${namespace}:paragraph4`, {
|
||||||
productName: props.productName,
|
productName: props.productName,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
Preview,
|
Preview,
|
||||||
Tailwind,
|
Tailwind,
|
||||||
Text,
|
Text,
|
||||||
render
|
render,
|
||||||
} from '@react-email/components';
|
} from '@react-email/components';
|
||||||
|
|
||||||
import { BodyStyle } from '../components/body-style';
|
import { BodyStyle } from '../components/body-style';
|
||||||
@@ -46,11 +46,10 @@ export async function renderAllResultsReceivedEmail({
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{previewText}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:hello`)}
|
{t(`${namespace}:hello`)}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -62,7 +61,6 @@ export async function renderAllResultsReceivedEmail({
|
|||||||
>
|
>
|
||||||
{t(`${namespace}:linkText`)}
|
{t(`${namespace}:linkText`)}
|
||||||
</EmailButton>
|
</EmailButton>
|
||||||
|
|
||||||
<Text>
|
<Text>
|
||||||
{t(`${namespace}:ifLinksDisabled`)}{' '}
|
{t(`${namespace}:ifLinksDisabled`)}{' '}
|
||||||
{`${process.env.NEXT_PUBLIC_SITE_URL}/doctor/analysis/${analysisOrderId}`}
|
{`${process.env.NEXT_PUBLIC_SITE_URL}/doctor/analysis/${analysisOrderId}`}
|
||||||
|
|||||||
@@ -55,23 +55,19 @@ export async function renderCompanyOfferEmail({
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{previewText}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:companyName`)} {companyData.companyName}
|
{t(`${namespace}:companyName`)} {companyData.companyName}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:contactPerson`)} {companyData.contactPerson}
|
{t(`${namespace}:contactPerson`)} {companyData.contactPerson}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:email`)} {companyData.email}
|
{t(`${namespace}:email`)} {companyData.email}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:phone`)} {companyData.phone || 'N/A'}
|
{t(`${namespace}:phone`)} {companyData.phone || 'N/A'}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
Head,
|
Head,
|
||||||
Html,
|
Html,
|
||||||
|
Link,
|
||||||
Preview,
|
Preview,
|
||||||
Tailwind,
|
Tailwind,
|
||||||
Text,
|
Text,
|
||||||
@@ -11,7 +12,6 @@ import {
|
|||||||
import { BodyStyle } from '../components/body-style';
|
import { BodyStyle } from '../components/body-style';
|
||||||
import CommonFooter from '../components/common-footer';
|
import CommonFooter from '../components/common-footer';
|
||||||
import { EmailContent } from '../components/content';
|
import { EmailContent } from '../components/content';
|
||||||
import { EmailButton } from '../components/email-button';
|
|
||||||
import { EmailHeader } from '../components/header';
|
import { EmailHeader } from '../components/header';
|
||||||
import { EmailHeading } from '../components/heading';
|
import { EmailHeading } from '../components/heading';
|
||||||
import { EmailWrapper } from '../components/wrapper';
|
import { EmailWrapper } from '../components/wrapper';
|
||||||
@@ -20,12 +20,10 @@ import { initializeEmailI18n } from '../lib/i18n';
|
|||||||
export async function renderDoctorSummaryReceivedEmail({
|
export async function renderDoctorSummaryReceivedEmail({
|
||||||
language,
|
language,
|
||||||
recipientName,
|
recipientName,
|
||||||
orderNr,
|
|
||||||
analysisOrderId,
|
analysisOrderId,
|
||||||
}: {
|
}: {
|
||||||
language?: string;
|
language: string;
|
||||||
recipientName: string;
|
recipientName: string;
|
||||||
orderNr: string;
|
|
||||||
analysisOrderId: number;
|
analysisOrderId: number;
|
||||||
}) {
|
}) {
|
||||||
const namespace = 'doctor-summary-received-email';
|
const namespace = 'doctor-summary-received-email';
|
||||||
@@ -35,13 +33,9 @@ export async function renderDoctorSummaryReceivedEmail({
|
|||||||
namespace: [namespace, 'common'],
|
namespace: [namespace, 'common'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const previewText = t(`${namespace}:previewText`, {
|
const previewText = t(`${namespace}:previewText`);
|
||||||
orderNr,
|
|
||||||
});
|
|
||||||
|
|
||||||
const subject = t(`${namespace}:subject`, {
|
const subject = t(`${namespace}:subject`);
|
||||||
orderNr,
|
|
||||||
});
|
|
||||||
|
|
||||||
const html = await render(
|
const html = await render(
|
||||||
<Html>
|
<Html>
|
||||||
@@ -54,29 +48,26 @@ export async function renderDoctorSummaryReceivedEmail({
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{previewText}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:hello`, {
|
{t(`common:helloName`, { name: recipientName })}
|
||||||
displayName: recipientName,
|
|
||||||
})}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
|
||||||
{t(`${namespace}:summaryReceivedForOrder`, { orderNr })}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<EmailButton
|
|
||||||
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
|
||||||
>
|
|
||||||
{t(`${namespace}:linkText`, { orderNr })}
|
|
||||||
</EmailButton>
|
|
||||||
<Text>
|
<Text>
|
||||||
{t(`${namespace}:ifButtonDisabled`)}{' '}
|
{t(`${namespace}:p1`)}{' '}
|
||||||
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
<Link
|
||||||
|
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
|
>
|
||||||
|
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
<Text>{t(`${namespace}:p2`)}</Text>
|
||||||
|
<Text>{t(`${namespace}:p3`)}</Text>
|
||||||
|
<Text>{t(`${namespace}:p4`)}</Text>
|
||||||
|
|
||||||
<CommonFooter t={t} />
|
<CommonFooter t={t} />
|
||||||
</EmailContent>
|
</EmailContent>
|
||||||
</EmailWrapper>
|
</EmailWrapper>
|
||||||
|
|||||||
@@ -46,11 +46,10 @@ export async function renderFirstResultsReceivedEmail({
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{previewText}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:hello`)}
|
{t(`${namespace}:hello`)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -74,20 +74,17 @@ export async function renderInviteEmail(props: Props) {
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{heading}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{heading}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{hello}
|
{hello}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
className="text-[16px] leading-[24px] text-[#242424]"
|
className="text-[16px] leading-[24px] text-[#242424]"
|
||||||
dangerouslySetInnerHTML={{ __html: mainText }}
|
dangerouslySetInnerHTML={{ __html: mainText }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{props.teamLogo && (
|
{props.teamLogo && (
|
||||||
<Section>
|
<Section>
|
||||||
<Row>
|
<Row>
|
||||||
@@ -102,20 +99,16 @@ export async function renderInviteEmail(props: Props) {
|
|||||||
</Row>
|
</Row>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
|
<Section className="mt-[32px] mb-[32px] text-center">
|
||||||
<Section className="mb-[32px] mt-[32px] text-center">
|
|
||||||
<CtaButton href={props.link}>{joinTeam}</CtaButton>
|
<CtaButton href={props.link}>{joinTeam}</CtaButton>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:copyPasteLink`)}{' '}
|
{t(`${namespace}:copyPasteLink`)}{' '}
|
||||||
<Link href={props.link} className="text-blue-600 no-underline">
|
<Link href={props.link} className="text-blue-600 no-underline">
|
||||||
{props.link}
|
{props.link}
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Hr className="mx-0 my-[26px] w-full border border-solid border-[#eaeaea]" />
|
<Hr className="mx-0 my-[26px] w-full border border-solid border-[#eaeaea]" />
|
||||||
|
|
||||||
<Text className="text-[12px] leading-[24px] text-[#666666]">
|
<Text className="text-[12px] leading-[24px] text-[#666666]">
|
||||||
{t(`${namespace}:invitationIntendedFor`, {
|
{t(`${namespace}:invitationIntendedFor`, {
|
||||||
invitedUserEmail: props.invitedUserEmail,
|
invitedUserEmail: props.invitedUserEmail,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
Preview,
|
Preview,
|
||||||
Tailwind,
|
Tailwind,
|
||||||
Text,
|
Text,
|
||||||
render
|
render,
|
||||||
} from '@react-email/components';
|
} from '@react-email/components';
|
||||||
|
|
||||||
import { BodyStyle } from '../components/body-style';
|
import { BodyStyle } from '../components/body-style';
|
||||||
@@ -50,11 +50,10 @@ export async function renderNewJobsAvailableEmail({
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{previewText}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:hello`)}
|
{t(`${namespace}:hello`)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Head,
|
||||||
|
Html,
|
||||||
|
Preview,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
render,
|
||||||
|
} from '@react-email/components';
|
||||||
|
|
||||||
|
import { BodyStyle } from '../components/body-style';
|
||||||
|
import CommonFooter from '../components/common-footer';
|
||||||
|
import { EmailContent } from '../components/content';
|
||||||
|
import { EmailHeader } from '../components/header';
|
||||||
|
import { EmailHeading } from '../components/heading';
|
||||||
|
import { EmailWrapper } from '../components/wrapper';
|
||||||
|
import { initializeEmailI18n } from '../lib/i18n';
|
||||||
|
|
||||||
|
export async function renderOrderProcessingEmail({
|
||||||
|
language,
|
||||||
|
recipientName,
|
||||||
|
partnerLocation,
|
||||||
|
isUrine,
|
||||||
|
}: {
|
||||||
|
language: string;
|
||||||
|
recipientName: string;
|
||||||
|
partnerLocation: string;
|
||||||
|
isUrine?: boolean;
|
||||||
|
}) {
|
||||||
|
const namespace = 'order-processing-email';
|
||||||
|
|
||||||
|
const { t } = await initializeEmailI18n({
|
||||||
|
language,
|
||||||
|
namespace: [namespace, 'common'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewText = t(`${namespace}:previewText`);
|
||||||
|
|
||||||
|
const subject = t(`${namespace}:subject`);
|
||||||
|
|
||||||
|
const p2 = t(`${namespace}:p2`);
|
||||||
|
const p4 = t(`${namespace}:p4`);
|
||||||
|
const p1Urine = t(`${namespace}:p1Urine`);
|
||||||
|
|
||||||
|
const html = await render(
|
||||||
|
<Html>
|
||||||
|
<Head>
|
||||||
|
<BodyStyle />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
|
||||||
|
<Tailwind>
|
||||||
|
<Body>
|
||||||
|
<EmailWrapper>
|
||||||
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
|
{t(`common:helloName`, { name: recipientName })}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-[16px] leading-[24px] font-semibold text-[#242424]">
|
||||||
|
{t(`${namespace}:heading`)}
|
||||||
|
</Text>
|
||||||
|
<Text>{t(`${namespace}:p1`, { partnerLocation })}</Text>
|
||||||
|
<Text dangerouslySetInnerHTML={{ __html: p2 }}></Text>
|
||||||
|
<Text>{t(`${namespace}:p3`)}</Text>
|
||||||
|
<Text dangerouslySetInnerHTML={{ __html: p4 }}></Text>
|
||||||
|
{isUrine && (
|
||||||
|
<>
|
||||||
|
<Text dangerouslySetInnerHTML={{ __html: p1Urine }}></Text>
|
||||||
|
<Text>{t(`${namespace}:p2Urine`)}</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Text>{t(`${namespace}:p5`)}</Text>
|
||||||
|
<Text>{t(`${namespace}:p6`)}</Text>
|
||||||
|
<CommonFooter t={t} />
|
||||||
|
</EmailContent>
|
||||||
|
</EmailWrapper>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -60,18 +60,17 @@ export async function renderOtpEmail(props: Props) {
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{heading}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{heading}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] text-[#242424]">{mainText}</Text>
|
<Text className="text-[16px] text-[#242424]">{mainText}</Text>
|
||||||
|
|
||||||
<Text className="text-[16px] text-[#242424]">{otpText}</Text>
|
<Text className="text-[16px] text-[#242424]">{otpText}</Text>
|
||||||
|
|
||||||
<Section className="mb-[16px] mt-[16px] text-center">
|
<Section className="mt-[16px] mb-[16px] text-center">
|
||||||
<Button className={'w-full rounded bg-neutral-950 text-center'}>
|
<Button className={'w-full rounded bg-neutral-950 text-center'}>
|
||||||
<Text className="text-[16px] font-semibold leading-[16px] text-white">
|
<Text className="text-[16px] leading-[16px] font-semibold text-white">
|
||||||
{props.otp}
|
{props.otp}
|
||||||
</Text>
|
</Text>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Head,
|
||||||
|
Html,
|
||||||
|
Link,
|
||||||
|
Preview,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
render,
|
||||||
|
} from '@react-email/components';
|
||||||
|
|
||||||
|
import { BodyStyle } from '../components/body-style';
|
||||||
|
import CommonFooter from '../components/common-footer';
|
||||||
|
import { EmailContent } from '../components/content';
|
||||||
|
import { EmailHeader } from '../components/header';
|
||||||
|
import { EmailHeading } from '../components/heading';
|
||||||
|
import { EmailWrapper } from '../components/wrapper';
|
||||||
|
import { initializeEmailI18n } from '../lib/i18n';
|
||||||
|
|
||||||
|
export async function renderPatientFirstResultsReceivedEmail({
|
||||||
|
language,
|
||||||
|
recipientName,
|
||||||
|
analysisOrderId,
|
||||||
|
}: {
|
||||||
|
language: string;
|
||||||
|
recipientName: string;
|
||||||
|
analysisOrderId: number;
|
||||||
|
}) {
|
||||||
|
const namespace = 'patient-first-results-received-email';
|
||||||
|
|
||||||
|
const { t } = await initializeEmailI18n({
|
||||||
|
language,
|
||||||
|
namespace: [namespace, 'common'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewText = t(`${namespace}:previewText`);
|
||||||
|
|
||||||
|
const subject = t(`${namespace}:subject`);
|
||||||
|
|
||||||
|
const html = await render(
|
||||||
|
<Html>
|
||||||
|
<Head>
|
||||||
|
<BodyStyle />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
|
||||||
|
<Tailwind>
|
||||||
|
<Body>
|
||||||
|
<EmailWrapper>
|
||||||
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
|
{t(`common:helloName`, { name: recipientName })}
|
||||||
|
</Text>
|
||||||
|
<Text>
|
||||||
|
{t(`${namespace}:p1`)}{' '}
|
||||||
|
<Link
|
||||||
|
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
|
>
|
||||||
|
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
<Text>{t(`${namespace}:p2`)}</Text>
|
||||||
|
<Text>{t(`${namespace}:p3`)}</Text>
|
||||||
|
<Text>{t(`${namespace}:p4`)}</Text>
|
||||||
|
<CommonFooter t={t} />
|
||||||
|
</EmailContent>
|
||||||
|
</EmailWrapper>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Head,
|
||||||
|
Html,
|
||||||
|
Link,
|
||||||
|
Preview,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
render,
|
||||||
|
} from '@react-email/components';
|
||||||
|
|
||||||
|
import { BodyStyle } from '../components/body-style';
|
||||||
|
import CommonFooter from '../components/common-footer';
|
||||||
|
import { EmailContent } from '../components/content';
|
||||||
|
import { EmailHeader } from '../components/header';
|
||||||
|
import { EmailHeading } from '../components/heading';
|
||||||
|
import { EmailWrapper } from '../components/wrapper';
|
||||||
|
import { initializeEmailI18n } from '../lib/i18n';
|
||||||
|
|
||||||
|
export async function renderPatientFullResultsReceivedEmail({
|
||||||
|
language,
|
||||||
|
recipientName,
|
||||||
|
analysisOrderId,
|
||||||
|
}: {
|
||||||
|
language: string;
|
||||||
|
recipientName: string;
|
||||||
|
analysisOrderId: number;
|
||||||
|
}) {
|
||||||
|
const namespace = 'patient-full-results-received-email';
|
||||||
|
|
||||||
|
const { t } = await initializeEmailI18n({
|
||||||
|
language,
|
||||||
|
namespace: [namespace, 'common'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewText = t(`${namespace}:previewText`);
|
||||||
|
|
||||||
|
const subject = t(`${namespace}:subject`);
|
||||||
|
|
||||||
|
const html = await render(
|
||||||
|
<Html>
|
||||||
|
<Head>
|
||||||
|
<BodyStyle />
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
|
||||||
|
<Tailwind>
|
||||||
|
<Body>
|
||||||
|
<EmailWrapper>
|
||||||
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{previewText}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
|
{t(`common:helloName`, { name: recipientName })}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text>
|
||||||
|
{t(`${namespace}:p1`)}{' '}
|
||||||
|
<Link
|
||||||
|
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
|
>
|
||||||
|
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/analysis-results/${analysisOrderId}`}
|
||||||
|
</Link>
|
||||||
|
</Text>
|
||||||
|
<Text>{t(`${namespace}:p2`)}</Text>
|
||||||
|
<Text>{t(`${namespace}:p3`)}</Text>
|
||||||
|
|
||||||
|
<CommonFooter t={t} />
|
||||||
|
</EmailContent>
|
||||||
|
</EmailWrapper>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
html,
|
||||||
|
subject,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ export async function renderSynlabAnalysisPackageEmail(props: Props) {
|
|||||||
const previewText = t(`${namespace}:previewText`, {
|
const previewText = t(`${namespace}:previewText`, {
|
||||||
analysisPackageName: props.analysisPackageName,
|
analysisPackageName: props.analysisPackageName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const subject = t(`${namespace}:subject`, {
|
const subject = t(`${namespace}:subject`, {
|
||||||
analysisPackageName: props.analysisPackageName,
|
analysisPackageName: props.analysisPackageName,
|
||||||
});
|
});
|
||||||
@@ -70,15 +70,13 @@ export async function renderSynlabAnalysisPackageEmail(props: Props) {
|
|||||||
<Tailwind>
|
<Tailwind>
|
||||||
<Body>
|
<Body>
|
||||||
<EmailWrapper>
|
<EmailWrapper>
|
||||||
<EmailHeader>
|
|
||||||
<EmailHeading>{heading}</EmailHeading>
|
|
||||||
</EmailHeader>
|
|
||||||
|
|
||||||
<EmailContent>
|
<EmailContent>
|
||||||
|
<EmailHeader>
|
||||||
|
<EmailHeading>{heading}</EmailHeading>
|
||||||
|
</EmailHeader>
|
||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{hello}
|
{hello}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{lines.map((line, index) => (
|
{lines.map((line, index) => (
|
||||||
<Text
|
<Text
|
||||||
key={index}
|
key={index}
|
||||||
@@ -86,7 +84,6 @@ export async function renderSynlabAnalysisPackageEmail(props: Props) {
|
|||||||
dangerouslySetInnerHTML={{ __html: line }}
|
dangerouslySetInnerHTML={{ __html: line }}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<CommonFooter t={t} />
|
<CommonFooter t={t} />
|
||||||
</EmailContent>
|
</EmailContent>
|
||||||
</EmailWrapper>
|
</EmailWrapper>
|
||||||
|
|||||||
@@ -7,3 +7,6 @@ export * from './emails/doctor-summary-received.email';
|
|||||||
export * from './emails/new-jobs-available.email';
|
export * from './emails/new-jobs-available.email';
|
||||||
export * from './emails/first-results-received.email';
|
export * from './emails/first-results-received.email';
|
||||||
export * from './emails/all-results-received.email';
|
export * from './emails/all-results-received.email';
|
||||||
|
export * from './emails/order-processing.email';
|
||||||
|
export * from './emails/patient-first-results-received.email';
|
||||||
|
export * from './emails/patient-full-results-received.email';
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"subject": "Doctor feedback to order {{orderNr}} received",
|
"subject": "Doctor's summary has arrived",
|
||||||
"previewText": "A doctor has submitted feedback on your analysis results.",
|
"previewText": "The doctor has prepared a summary of the test results.",
|
||||||
"hello": "Hello {{displayName}},",
|
"p1": "The doctor's summary has arrived:",
|
||||||
"summaryReceivedForOrder": "A doctor has submitted feedback to your analysis results from order {{orderNr}}.",
|
"p2": "It is recommended to have a comprehensive health check-up regularly, at least once a year, if you wish to maintain an active and fulfilling lifestyle.",
|
||||||
"linkText": "View summary",
|
"p3": "MedReport makes it easy, convenient, and fast to view health data in one place and order health check-ups.",
|
||||||
"ifButtonDisabled": "If clicking the button does not work, copy this link to your browser's url field:"
|
"p4": "SYNLAB customer support phone: 17123"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"subject": "The referral has been sent to the laboratory. Please go to give samples.",
|
||||||
|
"heading": "Thank you for your order!",
|
||||||
|
"previewText": "The referral for tests has been sent to the laboratory.",
|
||||||
|
"p1": "The referral for tests has been sent to the laboratory digitally. Please go to give samples: {{partnerLocation}}.",
|
||||||
|
"p2": "If you are unable to go to the selected location to give samples, you may visit any other sampling point convenient for you - <a href='https://medreport.ee/et/verevotupunktid'>see locations and opening hours</a>.",
|
||||||
|
"p3": "It is recommended to give samples preferably in the morning (before 12:00) and on an empty stomach without drinking or eating (you may drink water).",
|
||||||
|
"p4": "At the sampling point, please choose in the queue system: under <strong>referrals</strong> select <strong>specialist referral</strong>.",
|
||||||
|
"p5": "If you have any additional questions, please do not hesitate to contact us.",
|
||||||
|
"p6": "SYNLAB customer support phone: 17123",
|
||||||
|
"p1Urine": "The tests include a <strong>urine test</strong>. For the urine test, please collect the first morning urine.",
|
||||||
|
"p2Urine": "You can buy a sample container at the pharmacy and bring the sample with you (procedure performed at home), or ask for one at the sampling point (procedure performed in the point’s restroom)."
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"subject": "The first ordered test results have arrived",
|
||||||
|
"previewText": "The first test results have arrived.",
|
||||||
|
"p1": "The first test results have arrived:",
|
||||||
|
"p2": "We will send the next notification once all test results have been received in the system.",
|
||||||
|
"p3": "If you have any additional questions, please feel free to contact us.",
|
||||||
|
"p4": "SYNLAB customer support phone: 17123"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"subject": "All ordered test results have arrived. Awaiting doctor's summary.",
|
||||||
|
"previewText": "All test results have arrived.",
|
||||||
|
"p1": "All test results have arrived:",
|
||||||
|
"p2": "We will send the next notification once the doctor's summary has been prepared.",
|
||||||
|
"p3": "SYNLAB customer support phone: 17123"
|
||||||
|
}
|
||||||
@@ -4,5 +4,7 @@
|
|||||||
"lines2": "E-mail: <a href=\"mailto:info@medreport.ee\">info@medreport.ee</a>",
|
"lines2": "E-mail: <a href=\"mailto:info@medreport.ee\">info@medreport.ee</a>",
|
||||||
"lines3": "Klienditugi: <a href=\"tel:+37258871517\">+372 5887 1517</a>",
|
"lines3": "Klienditugi: <a href=\"tel:+37258871517\">+372 5887 1517</a>",
|
||||||
"lines4": "<a href=\"https://www.medreport.ee\">www.medreport.ee</a>"
|
"lines4": "<a href=\"https://www.medreport.ee\">www.medreport.ee</a>"
|
||||||
}
|
},
|
||||||
|
"helloName": "Tere, {{name}}",
|
||||||
|
"hello": "Tere"
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"subject": "Saabus arsti kokkuvõtte tellimusele {{orderNr}}",
|
"subject": "Arsti kokkuvõte on saabunud",
|
||||||
"previewText": "Arst on saatnud kokkuvõtte sinu analüüsitulemustele.",
|
"previewText": "Arst on koostanud kokkuvõte analüüsitulemustele.",
|
||||||
"hello": "Tere, {{displayName}}",
|
"p1": "Arsti kokkuvõte on saabunud:",
|
||||||
"summaryReceivedForOrder": "Arst on koostanud selgitava kokkuvõtte sinu tellitud analüüsidele.",
|
"p2": "Põhjalikul terviseuuringul on soovituslik käia regulaarselt, aga vähemalt üks kord aastas, kui soovite säilitada aktiivset ja täisväärtuslikku elustiili.",
|
||||||
"linkText": "Vaata kokkuvõtet",
|
"p3": "MedReport aitab lihtsalt, mugavalt ja kiirelt terviseandmeid ühest kohast vaadata ning tellida terviseuuringuid.",
|
||||||
"ifButtonDisabled": "Kui nupule vajutamine ei toimi, kopeeri see link oma brauserisse:"
|
"p4": "SYNLAB klienditoe telefon: 17123"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"subject": "Saatekiri on saadetud laborisse. Palun mine proove andma.",
|
||||||
|
"heading": "Täname tellimuse eest!",
|
||||||
|
"previewText": "Saatekiri uuringute tegemiseks on saadetud laborisse.",
|
||||||
|
"p1": "Saatekiri uuringute tegemiseks on saadetud laborisse digitaalselt. Palun mine proove andma: {{partnerLocation}}.",
|
||||||
|
"p2": "Kui Teil ei ole võimalik valitud asukohta minna proove andma, siis võite minna endale sobivasse proovivõtupunkti - <a href='https://medreport.ee/et/verevotupunktid'>vaata asukohti ja lahtiolekuaegasid</a>.",
|
||||||
|
"p3": "Soovituslik on proove anda pigem hommikul (enne 12:00) ning söömata ja joomata (vett võib juua).",
|
||||||
|
"p4": "Proovivõtupunktis valige järjekorrasüsteemis: <strong>saatekirjad</strong> alt <strong>eriarsti saatekiri</strong>",
|
||||||
|
"p5": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
|
||||||
|
"p6": "SYNLAB klienditoe telefon: 17123",
|
||||||
|
"p1Urine": "Analüüsides on ette nähtud <strong>uriinianalüüs</strong>. Uriinianalüüsiks võta hommikune esmane uriin.",
|
||||||
|
"p2Urine": "Proovitopsi võib soetada apteegist ja analüüsi kaasa võtta (teostada protseduur kodus) või küsida proovivõtupunktist (teostada protseduur proovipunkti wc-s)."
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"subject": "Saabusid tellitud uuringute esimesed tulemused",
|
||||||
|
"previewText": "Esimesed uuringute tulemused on saabunud.",
|
||||||
|
"p1": "Esimesed uuringute tulemused on saabunud:",
|
||||||
|
"p2": "Saadame järgmise teavituse, kui kõik uuringute vastused on saabunud süsteemi.",
|
||||||
|
"p3": "Juhul kui tekkis lisaküsimusi, siis võtke julgelt ühendust.",
|
||||||
|
"p4": "SYNLAB klienditoe telefon: 17123"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"subject": "Kõikide tellitud uuringute tulemused on saabunud. Ootab arsti kokkuvõtet.",
|
||||||
|
"previewText": "Kõikide uuringute tulemused on saabunud.",
|
||||||
|
"p1": "Kõikide uuringute tulemused on saabunud:",
|
||||||
|
"p2": "Saadame järgmise teavituse kui arsti kokkuvõte on koostatud.",
|
||||||
|
"p3": "SYNLAB klienditoe telefon: 17123"
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"subject": "Получено заключение врача по заказу {{orderNr}}",
|
"subject": "Заключение врача готово",
|
||||||
"previewText": "Врач отправил заключение по вашим результатам анализа.",
|
"previewText": "Врач подготовил заключение по результатам анализов.",
|
||||||
"hello": "Здравствуйте, {{displayName}}",
|
"p1": "Заключение врача готово:",
|
||||||
"summaryReceivedForOrder": "Врач подготовил пояснительное заключение по заказанным вами анализам.",
|
"p2": "Рекомендуется проходить комплексное обследование регулярно, но как минимум один раз в год, если вы хотите сохранить активный и полноценный образ жизни.",
|
||||||
"linkText": "Посмотреть заключение",
|
"p3": "MedReport позволяет легко, удобно и быстро просматривать медицинские данные в одном месте и заказывать обследования.",
|
||||||
"ifButtonDisabled": "Если кнопка не работает, скопируйте эту ссылку в ваш браузер:"
|
"p4": "Телефон службы поддержки SYNLAB: 17123"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"subject": "Направление отправлено в лабораторию. Пожалуйста, сдайте анализы.",
|
||||||
|
"heading": "Спасибо за заказ!",
|
||||||
|
"previewText": "Направление на обследование отправлено в лабораторию.",
|
||||||
|
"p1": "Направление на обследование было отправлено в лабораторию в цифровом виде. Пожалуйста, сдайте анализы: {{partnerLocation}}.",
|
||||||
|
"p2": "Если у вас нет возможности прийти в выбранный пункт сдачи анализов, вы можете обратиться в любой удобный для вас пункт – <a href='https://medreport.ee/et/verevotupunktid'>посмотреть адреса и часы работы</a>.",
|
||||||
|
"p3": "Рекомендуется сдавать анализы утром (до 12:00) натощак, без еды и напитков (разрешается пить воду).",
|
||||||
|
"p4": "В пункте сдачи анализов выберите в системе очереди: в разделе <strong>направления</strong> → <strong>направление от специалиста</strong>.",
|
||||||
|
"p5": "Если у вас возникли дополнительные вопросы, пожалуйста, свяжитесь с нами.",
|
||||||
|
"p6": "Телефон службы поддержки SYNLAB: 17123",
|
||||||
|
"p1Urine": "В обследование входит <strong>анализ мочи</strong>. Для анализа необходимо собрать первую утреннюю мочу.",
|
||||||
|
"p2Urine": "Контейнер можно приобрести в аптеке и принести образец с собой (процедура проводится дома) или взять контейнер в пункте сдачи (процедура проводится в туалете пункта)."
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"subject": "Поступили первые результаты заказанных исследований",
|
||||||
|
"previewText": "Первые результаты исследований поступили.",
|
||||||
|
"p1": "Первые результаты исследований поступили:",
|
||||||
|
"p2": "Мы отправим следующее уведомление, когда все результаты исследований будут получены в системе.",
|
||||||
|
"p3": "Если у вас возникнут дополнительные вопросы, пожалуйста, свяжитесь с нами.",
|
||||||
|
"p4": "Телефон службы поддержки SYNLAB: 17123"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"subject": "Все заказанные результаты исследований поступили. Ожидается заключение врача.",
|
||||||
|
"previewText": "Все результаты исследований поступили.",
|
||||||
|
"p1": "Все результаты исследований поступили:",
|
||||||
|
"p2": "Мы отправим следующее уведомление, когда заключение врача будет подготовлено.",
|
||||||
|
"p3": "Телефон службы поддержки SYNLAB: 17123"
|
||||||
|
}
|
||||||
@@ -126,7 +126,7 @@ export const giveFeedbackAction = doctorAction(
|
|||||||
|
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
await createNotificationLog({
|
await createNotificationLog({
|
||||||
action: NotificationAction.DOCTOR_FEEDBACK_RECEIVED,
|
action: NotificationAction.PATIENT_DOCTOR_FEEDBACK_RECEIVED,
|
||||||
status: 'SUCCESS',
|
status: 'SUCCESS',
|
||||||
relatedRecordId: analysisOrderId,
|
relatedRecordId: analysisOrderId,
|
||||||
});
|
});
|
||||||
@@ -136,7 +136,7 @@ export const giveFeedbackAction = doctorAction(
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
await createNotificationLog({
|
await createNotificationLog({
|
||||||
action: NotificationAction.DOCTOR_FEEDBACK_RECEIVED,
|
action: NotificationAction.PATIENT_DOCTOR_FEEDBACK_RECEIVED,
|
||||||
status: 'FAIL',
|
status: 'FAIL',
|
||||||
comment: e?.message,
|
comment: e?.message,
|
||||||
relatedRecordId: analysisOrderId,
|
relatedRecordId: analysisOrderId,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import z from 'zod/v3';
|
|
||||||
|
|
||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
|
import z from 'zod';
|
||||||
|
|
||||||
export const doctorJobSelectSchema = z.object({
|
export const doctorJobSelectSchema = z.object({
|
||||||
userId: z.string().uuid(),
|
userId: z.uuid(),
|
||||||
analysisOrderId: z.number(),
|
analysisOrderId: z.number(),
|
||||||
});
|
});
|
||||||
export type DoctorJobSelect = z.infer<typeof doctorJobSelectSchema>;
|
export type DoctorJobSelect = z.infer<typeof doctorJobSelectSchema>;
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
"exports": {
|
"exports": {
|
||||||
"./api": "./src/server/api.ts",
|
"./api": "./src/server/api.ts",
|
||||||
"./components": "./src/components/index.ts",
|
"./components": "./src/components/index.ts",
|
||||||
"./hooks": "./src/hooks/index.ts"
|
"./hooks": "./src/hooks/index.ts",
|
||||||
|
"./webhooks/*": "./src/server/services/webhooks/*.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kit/eslint-config": "workspace:*",
|
"@kit/eslint-config": "workspace:*",
|
||||||
|
|||||||
@@ -0,0 +1,273 @@
|
|||||||
|
import {
|
||||||
|
renderAllResultsReceivedEmail,
|
||||||
|
renderFirstResultsReceivedEmail,
|
||||||
|
renderOrderProcessingEmail,
|
||||||
|
renderPatientFirstResultsReceivedEmail,
|
||||||
|
renderPatientFullResultsReceivedEmail,
|
||||||
|
} from '@kit/email-templates';
|
||||||
|
import { getLogger } from '@kit/shared/logger';
|
||||||
|
import { getFullName } from '@kit/shared/utils';
|
||||||
|
import { Database } from '@kit/supabase/database';
|
||||||
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAssignedDoctorAccount,
|
||||||
|
getDoctorAccounts,
|
||||||
|
getUserContactAdmin,
|
||||||
|
} from '~/lib/services/account.service';
|
||||||
|
import {
|
||||||
|
NotificationAction,
|
||||||
|
createNotificationLog,
|
||||||
|
} from '~/lib/services/audit/notificationEntries.service';
|
||||||
|
import {
|
||||||
|
EmailRenderer,
|
||||||
|
sendEmailFromTemplate,
|
||||||
|
} from '~/lib/services/mailer.service';
|
||||||
|
|
||||||
|
type AnalysisOrder = Database['medreport']['Tables']['analysis_orders']['Row'];
|
||||||
|
|
||||||
|
export function createAnalysisOrderWebhooksService() {
|
||||||
|
return new AnalysisOrderWebhooksService();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnalysisOrderWebhooksService {
|
||||||
|
private readonly namespace = 'analysis_orders.webhooks';
|
||||||
|
|
||||||
|
async handleStatusChangeWebhook(analysisOrder: AnalysisOrder) {
|
||||||
|
const logger = await getLogger();
|
||||||
|
|
||||||
|
const ctx = {
|
||||||
|
analysisOrderId: analysisOrder.id,
|
||||||
|
namespace: this.namespace,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(ctx, 'Received status change update. Processing...');
|
||||||
|
let actions: NotificationAction[] = [];
|
||||||
|
try {
|
||||||
|
if (analysisOrder.status === 'PROCESSING') {
|
||||||
|
actions = [NotificationAction.PATIENT_ORDER_PROCESSING];
|
||||||
|
await this.sendProcessingNotification(analysisOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analysisOrder.status === 'PARTIAL_ANALYSIS_RESPONSE') {
|
||||||
|
actions = [
|
||||||
|
NotificationAction.PATIENT_FIRST_RESULTS_RECEIVED,
|
||||||
|
NotificationAction.DOCTOR_NEW_JOBS,
|
||||||
|
];
|
||||||
|
|
||||||
|
await this.sendPartialAnalysisResultsNotifications(analysisOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (analysisOrder.status === 'FULL_ANALYSIS_RESPONSE') {
|
||||||
|
actions = [
|
||||||
|
NotificationAction.DOCTOR_PATIENT_RESULTS_RECEIVED,
|
||||||
|
NotificationAction.PATIENT_FULL_RESULTS_RECEIVED,
|
||||||
|
];
|
||||||
|
await this.sendFullAnalysisResultsNotifications(analysisOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions.length) {
|
||||||
|
return logger.info(ctx, 'Status change notifications sent.');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(ctx, 'Status change processed. No notifications to send.');
|
||||||
|
} catch (e: any) {
|
||||||
|
if (actions.length)
|
||||||
|
await Promise.all(
|
||||||
|
actions.map((action) =>
|
||||||
|
createNotificationLog({
|
||||||
|
action,
|
||||||
|
status: 'FAIL',
|
||||||
|
comment: e?.message,
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
logger.error(
|
||||||
|
ctx,
|
||||||
|
`Error while processing status change: ${JSON.stringify(e)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendProcessingNotification(analysisOrder: AnalysisOrder) {
|
||||||
|
const logger = await getLogger();
|
||||||
|
const supabase = getSupabaseServerAdminClient();
|
||||||
|
|
||||||
|
const userContact = await getUserContactAdmin(analysisOrder.user_id);
|
||||||
|
|
||||||
|
if (!userContact?.email) {
|
||||||
|
await createNotificationLog({
|
||||||
|
action: NotificationAction.PATIENT_ORDER_PROCESSING,
|
||||||
|
status: 'FAIL',
|
||||||
|
comment: 'No email found for ' + analysisOrder.user_id,
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
});
|
||||||
|
logger.warn(
|
||||||
|
{ analysisOrderId: analysisOrder.id, namespace: this.namespace },
|
||||||
|
'No email found ',
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [{ data: medusaOrder }, { data: analysisElements }] =
|
||||||
|
await Promise.all([
|
||||||
|
supabase
|
||||||
|
.from('order')
|
||||||
|
.select('id,metadata')
|
||||||
|
.eq('id', analysisOrder.medusa_order_id)
|
||||||
|
.single()
|
||||||
|
.throwOnError(),
|
||||||
|
supabase
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_elements')
|
||||||
|
.select('materialGroups:material_groups')
|
||||||
|
.in('id', analysisOrder.analysis_element_ids ?? [])
|
||||||
|
.throwOnError(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let isUrine = false;
|
||||||
|
for (const analysisElement of analysisElements ?? []) {
|
||||||
|
logger.info({ group: analysisElement.materialGroups ?? [] });
|
||||||
|
|
||||||
|
const containsUrineSample = (analysisElement.materialGroups ?? [])?.some(
|
||||||
|
(element) =>
|
||||||
|
(element as { Materjal?: { MaterjaliNimi: string } })?.Materjal
|
||||||
|
?.MaterjaliNimi === 'Uriin',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (containsUrineSample) {
|
||||||
|
isUrine = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderMetadata = medusaOrder.metadata as {
|
||||||
|
partner_location_name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
await sendEmailFromTemplate(
|
||||||
|
renderOrderProcessingEmail,
|
||||||
|
{
|
||||||
|
language: userContact.preferred_locale ?? 'et',
|
||||||
|
recipientName: getFullName(userContact.name, userContact.last_name),
|
||||||
|
partnerLocation: orderMetadata.partner_location_name ?? 'SYNLAB',
|
||||||
|
isUrine,
|
||||||
|
},
|
||||||
|
userContact.email,
|
||||||
|
);
|
||||||
|
|
||||||
|
return createNotificationLog({
|
||||||
|
action: NotificationAction.PATIENT_ORDER_PROCESSING,
|
||||||
|
status: 'SUCCESS',
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendPatientUpdateNotification(
|
||||||
|
analysisOrder: AnalysisOrder,
|
||||||
|
template: EmailRenderer,
|
||||||
|
action: NotificationAction,
|
||||||
|
) {
|
||||||
|
const logger = await getLogger();
|
||||||
|
|
||||||
|
const userContact = await getUserContactAdmin(analysisOrder.user_id);
|
||||||
|
|
||||||
|
if (userContact?.email) {
|
||||||
|
await sendEmailFromTemplate(
|
||||||
|
template,
|
||||||
|
{
|
||||||
|
analysisOrderId: analysisOrder.id,
|
||||||
|
recipientName: getFullName(userContact.name, userContact.last_name),
|
||||||
|
language: userContact.preferred_locale ?? 'et',
|
||||||
|
},
|
||||||
|
userContact.email,
|
||||||
|
);
|
||||||
|
await createNotificationLog({
|
||||||
|
action,
|
||||||
|
status: 'SUCCESS',
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
});
|
||||||
|
logger.info(
|
||||||
|
{ analysisOrderId: analysisOrder.id, namespace: this.namespace },
|
||||||
|
'Sent notification email',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await createNotificationLog({
|
||||||
|
action,
|
||||||
|
status: 'FAIL',
|
||||||
|
comment: 'No email found for ' + analysisOrder.user_id,
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
});
|
||||||
|
logger.warn(
|
||||||
|
{ analysisOrderId: analysisOrder.id, namespace: this.namespace },
|
||||||
|
'No email found ',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendPartialAnalysisResultsNotifications(analysisOrder: AnalysisOrder) {
|
||||||
|
const logger = await getLogger();
|
||||||
|
|
||||||
|
await this.sendPatientUpdateNotification(
|
||||||
|
analysisOrder,
|
||||||
|
renderPatientFirstResultsReceivedEmail,
|
||||||
|
NotificationAction.PATIENT_FIRST_RESULTS_RECEIVED,
|
||||||
|
);
|
||||||
|
|
||||||
|
const doctorAccounts = await getDoctorAccounts();
|
||||||
|
const doctorEmails: string[] = doctorAccounts
|
||||||
|
.map(({ email }) => email)
|
||||||
|
.filter((email): email is string => !!email);
|
||||||
|
|
||||||
|
await sendEmailFromTemplate(
|
||||||
|
renderFirstResultsReceivedEmail,
|
||||||
|
{
|
||||||
|
analysisOrderId: analysisOrder.id,
|
||||||
|
language: 'et',
|
||||||
|
},
|
||||||
|
doctorEmails,
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
{ analysisOrderId: analysisOrder.id, namespace: this.namespace },
|
||||||
|
'Sent out partial analysis results notifications for doctors',
|
||||||
|
);
|
||||||
|
|
||||||
|
await createNotificationLog({
|
||||||
|
action: NotificationAction.DOCTOR_NEW_JOBS,
|
||||||
|
status: 'SUCCESS',
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendFullAnalysisResultsNotifications(analysisOrder: AnalysisOrder) {
|
||||||
|
await this.sendPatientUpdateNotification(
|
||||||
|
analysisOrder,
|
||||||
|
renderPatientFullResultsReceivedEmail,
|
||||||
|
NotificationAction.PATIENT_FULL_RESULTS_RECEIVED,
|
||||||
|
);
|
||||||
|
|
||||||
|
const doctorAccount = await getAssignedDoctorAccount(analysisOrder.id);
|
||||||
|
const assignedDoctorEmail = doctorAccount?.email;
|
||||||
|
|
||||||
|
if (!assignedDoctorEmail) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendEmailFromTemplate(
|
||||||
|
renderAllResultsReceivedEmail,
|
||||||
|
{
|
||||||
|
analysisOrderId: analysisOrder.id,
|
||||||
|
language: 'et',
|
||||||
|
},
|
||||||
|
assignedDoctorEmail,
|
||||||
|
);
|
||||||
|
|
||||||
|
return createNotificationLog({
|
||||||
|
action: NotificationAction.DOCTOR_PATIENT_RESULTS_RECEIVED,
|
||||||
|
status: 'SUCCESS',
|
||||||
|
relatedRecordId: analysisOrder.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@ export function LanguageSelector({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
localStorage.setItem('lang', locale);
|
||||||
return i18n.changeLanguage(locale);
|
return i18n.changeLanguage(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
@@ -220,8 +220,8 @@ importers:
|
|||||||
specifier: ^16.5.0
|
specifier: ^16.5.0
|
||||||
version: 16.6.1
|
version: 16.6.1
|
||||||
pino-pretty:
|
pino-pretty:
|
||||||
specifier: ^13.0.0
|
specifier: 13.0.0
|
||||||
version: 13.1.1
|
version: 13.0.0
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.5.3
|
specifier: ^3.5.3
|
||||||
version: 3.6.2
|
version: 3.6.2
|
||||||
@@ -478,10 +478,10 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@keystatic/core':
|
'@keystatic/core':
|
||||||
specifier: 0.5.47
|
specifier: 0.5.47
|
||||||
version: 0.5.47(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 0.5.47(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@keystatic/next':
|
'@keystatic/next':
|
||||||
specifier: ^5.0.4
|
specifier: ^5.0.4
|
||||||
version: 5.0.4(@keystatic/core@0.5.47(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 5.0.4(@keystatic/core@0.5.47(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@markdoc/markdoc':
|
'@markdoc/markdoc':
|
||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.4(@types/react@19.1.4)(react@19.1.0)
|
version: 0.5.4(@types/react@19.1.4)(react@19.1.0)
|
||||||
@@ -1272,7 +1272,7 @@ importers:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@sentry/nextjs':
|
'@sentry/nextjs':
|
||||||
specifier: ^9.19.0
|
specifier: ^9.19.0
|
||||||
version: 9.46.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.101.3)
|
version: 9.46.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.101.3)
|
||||||
import-in-the-middle:
|
import-in-the-middle:
|
||||||
specifier: 1.13.2
|
specifier: 1.13.2
|
||||||
version: 1.13.2
|
version: 1.13.2
|
||||||
@@ -8893,8 +8893,8 @@ packages:
|
|||||||
pino-abstract-transport@2.0.0:
|
pino-abstract-transport@2.0.0:
|
||||||
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
|
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
|
||||||
|
|
||||||
pino-pretty@13.1.1:
|
pino-pretty@13.0.0:
|
||||||
resolution: {integrity: sha512-TNNEOg0eA0u+/WuqH0MH0Xui7uqVk9D74ESOpjtebSQYbNWJk/dIxCXIxFsNfeN53JmtWqYHP2OrIZjT/CBEnA==}
|
resolution: {integrity: sha512-cQBBIVG3YajgoUjo1FdKVRX6t9XPxwB9lcNJVD5GCnNM4Y6T12YYx8c6zEejxQsU0wrg9TwmDulcE9LR7qcJqA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
pino-std-serializers@7.0.0:
|
pino-std-serializers@7.0.0:
|
||||||
@@ -9595,8 +9595,8 @@ packages:
|
|||||||
scroll-into-view-if-needed@3.1.0:
|
scroll-into-view-if-needed@3.1.0:
|
||||||
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
|
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
|
||||||
|
|
||||||
secure-json-parse@4.0.0:
|
secure-json-parse@2.7.0:
|
||||||
resolution: {integrity: sha512-dxtLJO6sc35jWidmLxo7ij+Eg48PM/kleBsxpC8QJE0qJICe+KawkDQmvCMZUr9u7WKVHgMW6vy3fQ7zMiFZMA==}
|
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
|
||||||
|
|
||||||
selderee@0.11.0:
|
selderee@0.11.0:
|
||||||
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
||||||
@@ -9820,10 +9820,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
strip-json-comments@5.0.3:
|
|
||||||
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
|
|
||||||
engines: {node: '>=14.16'}
|
|
||||||
|
|
||||||
stripe@18.5.0:
|
stripe@18.5.0:
|
||||||
resolution: {integrity: sha512-Hp+wFiEQtCB0LlNgcFh5uVyKznpDjzyUZ+CNVEf+I3fhlYvh7rZruIg+jOwzJRCpy0ZTPMjlzm7J2/M2N6d+DA==}
|
resolution: {integrity: sha512-Hp+wFiEQtCB0LlNgcFh5uVyKznpDjzyUZ+CNVEf+I3fhlYvh7rZruIg+jOwzJRCpy0ZTPMjlzm7J2/M2N6d+DA==}
|
||||||
engines: {node: '>=12.*'}
|
engines: {node: '>=12.*'}
|
||||||
@@ -11461,7 +11457,7 @@ snapshots:
|
|||||||
|
|
||||||
'@juggle/resize-observer@3.4.0': {}
|
'@juggle/resize-observer@3.4.0': {}
|
||||||
|
|
||||||
'@keystar/ui@0.7.19(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@keystar/ui@0.7.19(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.6
|
'@babel/runtime': 7.27.6
|
||||||
'@emotion/css': 11.13.5
|
'@emotion/css': 11.13.5
|
||||||
@@ -11554,18 +11550,18 @@ snapshots:
|
|||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
next: 15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
next: 15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@keystatic/core@0.5.47(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@keystatic/core@0.5.47(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.6
|
'@babel/runtime': 7.27.6
|
||||||
'@braintree/sanitize-url': 6.0.4
|
'@braintree/sanitize-url': 6.0.4
|
||||||
'@emotion/weak-memoize': 0.3.1
|
'@emotion/weak-memoize': 0.3.1
|
||||||
'@floating-ui/react': 0.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@floating-ui/react': 0.24.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@internationalized/string': 3.2.7
|
'@internationalized/string': 3.2.7
|
||||||
'@keystar/ui': 0.7.19(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@keystar/ui': 0.7.19(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@markdoc/markdoc': 0.4.0(@types/react@19.1.4)(react@19.1.0)
|
'@markdoc/markdoc': 0.4.0(@types/react@19.1.4)(react@19.1.0)
|
||||||
'@react-aria/focus': 3.20.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@react-aria/focus': 3.20.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@react-aria/i18n': 3.12.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@react-aria/i18n': 3.12.10(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@@ -11636,13 +11632,13 @@ snapshots:
|
|||||||
- next
|
- next
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@keystatic/next@5.0.4(@keystatic/core@0.5.47(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
'@keystatic/next@5.0.4(@keystatic/core@0.5.47(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.27.6
|
'@babel/runtime': 7.27.6
|
||||||
'@keystatic/core': 0.5.47(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
'@keystatic/core': 0.5.47(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
'@types/react': 19.1.4
|
'@types/react': 19.1.4
|
||||||
chokidar: 3.6.0
|
chokidar: 3.6.0
|
||||||
next: 15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
next: 15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
server-only: 0.0.1
|
server-only: 0.0.1
|
||||||
@@ -17236,7 +17232,7 @@ snapshots:
|
|||||||
|
|
||||||
'@sentry/core@9.46.0': {}
|
'@sentry/core@9.46.0': {}
|
||||||
|
|
||||||
'@sentry/nextjs@9.46.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.101.3)':
|
'@sentry/nextjs@9.46.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.101.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
'@opentelemetry/semantic-conventions': 1.34.0
|
'@opentelemetry/semantic-conventions': 1.34.0
|
||||||
@@ -17249,7 +17245,7 @@ snapshots:
|
|||||||
'@sentry/vercel-edge': 9.46.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))
|
'@sentry/vercel-edge': 9.46.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))
|
||||||
'@sentry/webpack-plugin': 3.5.0(webpack@5.101.3)
|
'@sentry/webpack-plugin': 3.5.0(webpack@5.101.3)
|
||||||
chalk: 3.0.0
|
chalk: 3.0.0
|
||||||
next: 15.3.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
next: 15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
resolve: 1.22.8
|
resolve: 1.22.8
|
||||||
rollup: 4.35.0
|
rollup: 4.35.0
|
||||||
stacktrace-parser: 0.1.11
|
stacktrace-parser: 0.1.11
|
||||||
@@ -21273,6 +21269,31 @@ snapshots:
|
|||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
|
||||||
|
next@15.5.2(@babel/core@7.28.3)(@opentelemetry/api@1.9.0)(babel-plugin-macros@3.1.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
'@next/env': 15.5.2
|
||||||
|
'@swc/helpers': 0.5.15
|
||||||
|
caniuse-lite: 1.0.30001723
|
||||||
|
postcss: 8.4.31
|
||||||
|
react: 19.1.0
|
||||||
|
react-dom: 19.1.0(react@19.1.0)
|
||||||
|
styled-jsx: 5.1.6(@babel/core@7.28.3)(babel-plugin-macros@3.1.0)(react@19.1.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@next/swc-darwin-arm64': 15.5.2
|
||||||
|
'@next/swc-darwin-x64': 15.5.2
|
||||||
|
'@next/swc-linux-arm64-gnu': 15.5.2
|
||||||
|
'@next/swc-linux-arm64-musl': 15.5.2
|
||||||
|
'@next/swc-linux-x64-gnu': 15.5.2
|
||||||
|
'@next/swc-linux-x64-musl': 15.5.2
|
||||||
|
'@next/swc-win32-arm64-msvc': 15.5.2
|
||||||
|
'@next/swc-win32-x64-msvc': 15.5.2
|
||||||
|
'@opentelemetry/api': 1.9.0
|
||||||
|
babel-plugin-react-compiler: 19.1.0-rc.2
|
||||||
|
sharp: 0.34.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@babel/core'
|
||||||
|
- babel-plugin-macros
|
||||||
|
|
||||||
no-case@3.0.4:
|
no-case@3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
lower-case: 2.0.2
|
lower-case: 2.0.2
|
||||||
@@ -21506,7 +21527,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
split2: 4.2.0
|
split2: 4.2.0
|
||||||
|
|
||||||
pino-pretty@13.1.1:
|
pino-pretty@13.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
colorette: 2.0.20
|
colorette: 2.0.20
|
||||||
dateformat: 4.6.3
|
dateformat: 4.6.3
|
||||||
@@ -21518,9 +21539,9 @@ snapshots:
|
|||||||
on-exit-leak-free: 2.1.2
|
on-exit-leak-free: 2.1.2
|
||||||
pino-abstract-transport: 2.0.0
|
pino-abstract-transport: 2.0.0
|
||||||
pump: 3.0.3
|
pump: 3.0.3
|
||||||
secure-json-parse: 4.0.0
|
secure-json-parse: 2.7.0
|
||||||
sonic-boom: 4.2.0
|
sonic-boom: 4.2.0
|
||||||
strip-json-comments: 5.0.3
|
strip-json-comments: 3.1.1
|
||||||
|
|
||||||
pino-std-serializers@7.0.0: {}
|
pino-std-serializers@7.0.0: {}
|
||||||
|
|
||||||
@@ -22474,7 +22495,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
compute-scroll-into-view: 3.1.1
|
compute-scroll-into-view: 3.1.1
|
||||||
|
|
||||||
secure-json-parse@4.0.0: {}
|
secure-json-parse@2.7.0: {}
|
||||||
|
|
||||||
selderee@0.11.0:
|
selderee@0.11.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -22796,8 +22817,6 @@ snapshots:
|
|||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
strip-json-comments@5.0.3: {}
|
|
||||||
|
|
||||||
stripe@18.5.0(@types/node@24.3.0):
|
stripe@18.5.0(@types/node@24.3.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
qs: 6.14.0
|
qs: 6.14.0
|
||||||
|
|||||||
@@ -64,7 +64,8 @@
|
|||||||
"orderDate": "Order date",
|
"orderDate": "Order date",
|
||||||
"orderNumber": "Order number",
|
"orderNumber": "Order number",
|
||||||
"orderStatus": "Order status",
|
"orderStatus": "Order status",
|
||||||
"paymentStatus": "Payment status"
|
"paymentStatus": "Payment status",
|
||||||
|
"discount": "Discount"
|
||||||
},
|
},
|
||||||
"montonioCallback": {
|
"montonioCallback": {
|
||||||
"title": "Montonio checkout",
|
"title": "Montonio checkout",
|
||||||
|
|||||||
@@ -68,7 +68,8 @@
|
|||||||
"orderDate": "Tellimuse kuupäev",
|
"orderDate": "Tellimuse kuupäev",
|
||||||
"orderNumber": "Tellimuse number",
|
"orderNumber": "Tellimuse number",
|
||||||
"orderStatus": "Tellimuse olek",
|
"orderStatus": "Tellimuse olek",
|
||||||
"paymentStatus": "Makse olek"
|
"paymentStatus": "Makse olek",
|
||||||
|
"discount": "Soodus"
|
||||||
},
|
},
|
||||||
"montonioCallback": {
|
"montonioCallback": {
|
||||||
"title": "Montonio makseprotsess",
|
"title": "Montonio makseprotsess",
|
||||||
|
|||||||
@@ -28,7 +28,13 @@
|
|||||||
"label": "Добавить промокод",
|
"label": "Добавить промокод",
|
||||||
"apply": "Применить",
|
"apply": "Применить",
|
||||||
"subtitle": "Если хотите, можете добавить промокод",
|
"subtitle": "Если хотите, можете добавить промокод",
|
||||||
"placeholder": "Введите промокод"
|
"placeholder": "Введите промокод",
|
||||||
|
"remove": "Удалить промокод",
|
||||||
|
"appliedCodes": "Примененные промокоды:",
|
||||||
|
"removeError": "Не удалось удалить промокод",
|
||||||
|
"removeSuccess": "Промокод удален",
|
||||||
|
"addError": "Не удалось применить промокод",
|
||||||
|
"addSuccess": "Промокод применен"
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"synlabAnalyses": {
|
"synlabAnalyses": {
|
||||||
@@ -61,7 +67,8 @@
|
|||||||
"orderDate": "Дата заказа",
|
"orderDate": "Дата заказа",
|
||||||
"orderNumber": "Номер заказа",
|
"orderNumber": "Номер заказа",
|
||||||
"orderStatus": "Статус заказа",
|
"orderStatus": "Статус заказа",
|
||||||
"paymentStatus": "Статус оплаты"
|
"paymentStatus": "Статус оплаты",
|
||||||
|
"discount": "Скидка"
|
||||||
},
|
},
|
||||||
"montonioCallback": {
|
"montonioCallback": {
|
||||||
"title": "Процесс оплаты Montonio",
|
"title": "Процесс оплаты Montonio",
|
||||||
|
|||||||
@@ -20,12 +20,24 @@ export const getAnalysisElementMedusaProductIds = (products: Pick<StoreProduct,
|
|||||||
.flatMap((product) => {
|
.flatMap((product) => {
|
||||||
const value = (product as Product)?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"');
|
const value = (product as Product)?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"');
|
||||||
const value_variant = (product as Product)?.variant?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"');
|
const value_variant = (product as Product)?.variant?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"');
|
||||||
|
|
||||||
|
const result: string[] = [];
|
||||||
try {
|
try {
|
||||||
return [...JSON.parse(value as string), ...JSON.parse(value_variant as string)];
|
if (value) {
|
||||||
|
result.push(...JSON.parse(value as string));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse analysisElementMedusaProductIds from analysis package variant, possibly invalid format", e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (value_variant) {
|
||||||
|
result.push(...JSON.parse(value_variant as string));
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to parse analysisElementMedusaProductIds from analysis package, possibly invalid format", e);
|
console.error("Failed to parse analysisElementMedusaProductIds from analysis package, possibly invalid format", e);
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
})
|
})
|
||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user