|
|
|
|
@@ -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,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|