feat: create email template for TTO reservation confirmation

feat: implement order notifications service with TTO reservation confirmation handling

feat: create migration for TTO booking email webhook trigger
This commit is contained in:
Danel Kungla
2025-09-30 16:05:43 +03:00
parent 4003284f3a
commit 72f6f2b716
56 changed files with 3692 additions and 294 deletions

View File

@@ -1,9 +1,12 @@
import { sdk } from '@lib/config';
import {
renderAllResultsReceivedEmail,
renderFirstResultsReceivedEmail,
renderOrderProcessingEmail,
renderPatientFirstResultsReceivedEmail,
renderPatientFullResultsReceivedEmail,
renderTtoReservationConfirmationEmail,
} from '@kit/email-templates';
import { getLogger } from '@kit/shared/logger';
import { getFullName } from '@kit/shared/utils';
@@ -25,13 +28,15 @@ import {
} from '~/lib/services/mailer.service';
type AnalysisOrder = Database['medreport']['Tables']['analysis_orders']['Row'];
type TtoReservation =
Database['medreport']['Tables']['connected_online_reservation']['Row'];
export function createAnalysisOrderWebhooksService() {
return new AnalysisOrderWebhooksService();
export function createOrderWebhooksService() {
return new OrderWebhooksService();
}
class AnalysisOrderWebhooksService {
private readonly namespace = 'analysis_orders.webhooks';
class OrderWebhooksService {
private readonly namespace = 'orders.webhooks';
async handleStatusChangeWebhook(analysisOrder: AnalysisOrder) {
const logger = await getLogger();
@@ -90,6 +95,128 @@ class AnalysisOrderWebhooksService {
}
}
async handleTtoReservationConfirmationWebhook(
ttoReservation: TtoReservation,
) {
const logger = await getLogger();
const ctx = {
ttoReservationId: ttoReservation.id,
namespace: this.namespace,
};
try {
const userContact = await getUserContactAdmin(ttoReservation.user_id);
if (!userContact.email) {
await createNotificationLog({
action: NotificationAction.TTO_ORDER_CONFIRMATION,
status: 'FAIL',
comment: 'No email found for ' + ttoReservation.user_id,
relatedRecordId: ttoReservation.id,
});
logger.warn(ctx, 'No email found ');
return;
}
const supabaseClient = getSupabaseServerAdminClient();
if (!ttoReservation.medusa_cart_line_item_id) {
await createNotificationLog({
action: NotificationAction.TTO_ORDER_CONFIRMATION,
status: 'FAIL',
comment: 'No cart item id found for ' + ttoReservation.user_id,
relatedRecordId: ttoReservation.id,
});
logger.warn(ctx, 'No cart item id found ');
return;
}
const [
{ data: cartItem },
{ data: location },
{ data: serviceProvider },
] = await Promise.all([
supabaseClient
.from('cart_line_item')
.select('cart_id,title')
.eq('id', ttoReservation.medusa_cart_line_item_id)
.single(),
supabaseClient
.schema('medreport')
.from('connected_online_locations')
.select('name,address')
.eq('sync_id', ttoReservation.location_sync_id || 0)
.single(),
supabaseClient
.schema('medreport')
.from('connected_online_providers')
.select('email,phone_number,name')
.eq('id', ttoReservation.clinic_id)
.single(),
]);
if (!cartItem) {
await createNotificationLog({
action: NotificationAction.TTO_ORDER_CONFIRMATION,
status: 'FAIL',
comment: 'No medusa cart item found for ' + ttoReservation.user_id,
relatedRecordId: ttoReservation.medusa_cart_line_item_id,
});
logger.warn(ctx, 'No cart item found ');
return;
}
const [{ data: orderCart }] = await Promise.all([
supabaseClient
.from('order_cart')
.select('order_id')
.eq('cart_id', cartItem.cart_id)
.single(),
]);
if (!orderCart) {
await createNotificationLog({
action: NotificationAction.TTO_ORDER_CONFIRMATION,
status: 'FAIL',
comment: 'No medusa order cart found for ' + ttoReservation.user_id,
relatedRecordId: cartItem.cart_id,
});
logger.warn(ctx, 'No order cart found ');
return;
}
await sendEmailFromTemplate(
renderTtoReservationConfirmationEmail,
{
language: userContact.preferred_locale ?? 'et',
recipientName: getFullName(userContact.name, userContact.last_name),
startTime: ttoReservation.start_time,
orderName: cartItem.title,
locationName: location?.name,
locationAddress: location?.address,
orderId: orderCart.order_id,
serviceProviderName: serviceProvider?.name,
serviceProviderEmail: serviceProvider?.email,
serviceProviderPhone: serviceProvider?.phone_number,
},
userContact.email,
);
return createNotificationLog({
action: NotificationAction.TTO_ORDER_CONFIRMATION,
status: 'SUCCESS',
relatedRecordId: orderCart.order_id,
});
} catch (e: any) {
createNotificationLog({
action: NotificationAction.TTO_ORDER_CONFIRMATION,
status: 'FAIL',
comment: e?.message,
relatedRecordId: ttoReservation.id,
});
logger.error(
ctx,
`Error while processing tto reservation: ${JSON.stringify(e)}`,
);
}
}
async sendProcessingNotification(analysisOrder: AnalysisOrder) {
const logger = await getLogger();
const supabase = getSupabaseServerAdminClient();