MED-82: add patient notification emails (#74)
* MED-82: add patient notification emails * remove console.log * clean up * remove extra paragraph from email
This commit is contained in:
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user