@@ -7,10 +7,18 @@ import { sendEmailFromTemplate } from '~/lib/services/mailer.service';
|
|||||||
export default async function sendOpenJobsEmails() {
|
export default async function sendOpenJobsEmails() {
|
||||||
const analysisResponseIds = await getOpenJobAnalysisResponseIds();
|
const analysisResponseIds = await getOpenJobAnalysisResponseIds();
|
||||||
|
|
||||||
|
if (analysisResponseIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const doctorAccounts = await getDoctorAccounts();
|
const doctorAccounts = await getDoctorAccounts();
|
||||||
const doctorEmails: string[] = doctorAccounts
|
const doctorEmails = doctorAccounts
|
||||||
.map(({ email }) => email)
|
.map(({ email }) => email)
|
||||||
.filter((email): email is string => !!email);
|
.filter((email) => !!email);
|
||||||
|
|
||||||
|
if (doctorEmails !== null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
await sendEmailFromTemplate(
|
await sendEmailFromTemplate(
|
||||||
renderNewJobsAvailableEmail,
|
renderNewJobsAvailableEmail,
|
||||||
@@ -20,4 +28,6 @@ export default async function sendOpenJobsEmails() {
|
|||||||
},
|
},
|
||||||
doctorEmails,
|
doctorEmails,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return doctorAccounts.filter((email) => !!email).map(({ id }) => id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api';
|
||||||
|
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
|
||||||
|
|
||||||
import { readPrivateMessageResponse } from '~/lib/services/medipost/medipostPrivateMessage.service';
|
import { readPrivateMessageResponse } from '~/lib/services/medipost/medipostPrivateMessage.service';
|
||||||
|
|
||||||
type ProcessedMessage = {
|
type ProcessedMessage = {
|
||||||
@@ -16,6 +19,8 @@ type GroupedResults = {
|
|||||||
|
|
||||||
export default async function syncAnalysisResults() {
|
export default async function syncAnalysisResults() {
|
||||||
console.info('Syncing analysis results');
|
console.info('Syncing analysis results');
|
||||||
|
const supabase = getSupabaseServerClient();
|
||||||
|
const api = createUserAnalysesApi(supabase);
|
||||||
|
|
||||||
const processedMessages: ProcessedMessage[] = [];
|
const processedMessages: ProcessedMessage[] = [];
|
||||||
const excludedMessageIds: string[] = [];
|
const excludedMessageIds: string[] = [];
|
||||||
@@ -25,6 +30,12 @@ export default async function syncAnalysisResults() {
|
|||||||
processedMessages.push(result as ProcessedMessage);
|
processedMessages.push(result as ProcessedMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await api.sendAnalysisResultsNotification({
|
||||||
|
hasFullAnalysisResponse: result.hasFullAnalysisResponse,
|
||||||
|
hasPartialAnalysisResponse: result.hasAnalysisResponse,
|
||||||
|
analysisOrderId: result.analysisOrderId,
|
||||||
|
});
|
||||||
|
|
||||||
if (!result.messageId) {
|
if (!result.messageId) {
|
||||||
console.info('No more messages to process');
|
console.info('No more messages to process');
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -19,13 +19,14 @@ export const POST = async (request: NextRequest) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendOpenJobsEmails();
|
const doctors = await sendOpenJobsEmails();
|
||||||
console.info(
|
console.info(
|
||||||
'Successfully sent out open job notification emails to doctors.',
|
'Successfully sent out open job notification emails to doctors',
|
||||||
);
|
);
|
||||||
await createNotificationLog({
|
await createNotificationLog({
|
||||||
action: NotificationAction.DOCTOR_NEW_JOBS,
|
action: NotificationAction.DOCTOR_NEW_JOBS,
|
||||||
status: 'SUCCESS',
|
status: 'SUCCESS',
|
||||||
|
comment: `doctors that received email: ${doctors}`,
|
||||||
});
|
});
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const MembershipConfirmationNotification: React.FC<{
|
|||||||
descriptionKey="account:membershipConfirmation:successDescription"
|
descriptionKey="account:membershipConfirmation:successDescription"
|
||||||
buttonProps={{
|
buttonProps={{
|
||||||
buttonTitleKey: 'account:membershipConfirmation:successButton',
|
buttonTitleKey: 'account:membershipConfirmation:successButton',
|
||||||
href: pathsConfig.app.home,
|
href: pathsConfig.app.selectPackage,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ async function SelectPackagePage() {
|
|||||||
<Scale className="size-4 stroke-[1.5px]" />
|
<Scale className="size-4 stroke-[1.5px]" />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
countryCode={countryCode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SelectAnalysisPackages
|
<SelectAnalysisPackages
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ async function getAssignedOrderIds() {
|
|||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
.from('doctor_analysis_feedback')
|
.from('doctor_analysis_feedback')
|
||||||
.select('analysis_order_id')
|
.select('analysis_order_id')
|
||||||
|
.not('status', 'is', 'COMPLETED')
|
||||||
.not('doctor_user_id', 'is', null)
|
.not('doctor_user_id', 'is', null)
|
||||||
.throwOnError();
|
.throwOnError();
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
import type { PostgrestError } from '@supabase/supabase-js';
|
import type { PostgrestError } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import { GetMessageListResponse, MedipostAction } from '@/lib/types/medipost';
|
import { GetMessageListResponse, MedipostAction } from '@/lib/types/medipost';
|
||||||
import { createNotificationsApi } from '@/packages/features/notifications/src/server/api';
|
|
||||||
import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api';
|
import { createUserAnalysesApi } from '@/packages/features/user-analyses/src/server/api';
|
||||||
import { pathsConfig } from '@/packages/shared/src/config';
|
|
||||||
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
|
import { AnalysisOrderStatus } from '@/packages/shared/src/types/medipost-analysis';
|
||||||
import type {
|
import type {
|
||||||
MedipostOrderResponse,
|
MedipostOrderResponse,
|
||||||
@@ -18,7 +16,6 @@ import axios from 'axios';
|
|||||||
import { toArray } from '@kit/shared/utils';
|
import { toArray } from '@kit/shared/utils';
|
||||||
import { Tables } from '@kit/supabase/database';
|
import { Tables } from '@kit/supabase/database';
|
||||||
|
|
||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
|
||||||
import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element';
|
import type { AnalysisResponseElement } from '~/lib/types/analysis-response-element';
|
||||||
import type { AnalysisOrder } from '~/lib/types/order';
|
import type { AnalysisOrder } from '~/lib/types/order';
|
||||||
|
|
||||||
@@ -123,7 +120,7 @@ export async function canCreateAnalysisResponseElement({
|
|||||||
|
|
||||||
if (existingAnalysisResponseElement.response_value && !responseValue) {
|
if (existingAnalysisResponseElement.response_value && !responseValue) {
|
||||||
log(
|
log(
|
||||||
`Analysis response element id=${analysisElementOriginalId} already exists for order with response value ${existingAnalysisResponseElement.response_value} but new response has no value`,
|
`Analysis response element id=${analysisElementOriginalId} ${existingAnalysisResponseElement.response_value} but new response has no value`,
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -271,7 +268,6 @@ export async function syncPrivateMessage({
|
|||||||
order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
order: Tables<{ schema: 'medreport' }, 'analysis_orders'>;
|
||||||
}) {
|
}) {
|
||||||
const supabase = getSupabaseServerAdminClient();
|
const supabase = getSupabaseServerAdminClient();
|
||||||
const { t } = await createI18nServerInstance();
|
|
||||||
|
|
||||||
const orderStatus = AnalysisOrderStatus[TellimuseOlek];
|
const orderStatus = AnalysisOrderStatus[TellimuseOlek];
|
||||||
|
|
||||||
@@ -304,7 +300,6 @@ export async function syncPrivateMessage({
|
|||||||
log,
|
log,
|
||||||
});
|
});
|
||||||
|
|
||||||
let newElementsAdded = 0;
|
|
||||||
for (const element of newElements) {
|
for (const element of newElements) {
|
||||||
try {
|
try {
|
||||||
await upsertAnalysisResponseElement({
|
await upsertAnalysisResponseElement({
|
||||||
@@ -313,7 +308,6 @@ export async function syncPrivateMessage({
|
|||||||
analysis_response_id: analysisResponseId,
|
analysis_response_id: analysisResponseId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
newElementsAdded++;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(
|
log(
|
||||||
`Failed to create order response element for response id ${analysisResponseId}, element id '${element.analysis_element_original_id}' (order id: ${order.id})`,
|
`Failed to create order response element for response id ${analysisResponseId}, element id '${element.analysis_element_original_id}' (order id: ${order.id})`,
|
||||||
@@ -322,16 +316,6 @@ export async function syncPrivateMessage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`Added ${newElementsAdded} new elements`);
|
|
||||||
|
|
||||||
if (newElementsAdded !== 0) {
|
|
||||||
await createNotificationsApi(supabase).createNotification({
|
|
||||||
account_id: analysisOrder.user_id,
|
|
||||||
body: t('analysis-results:notification.body'),
|
|
||||||
link: `${pathsConfig.app.analysisResults}/${order.id}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return (await hasAllAnalysisResponseElements({ analysisResponseId, order }))
|
return (await hasAllAnalysisResponseElements({ analysisResponseId, order }))
|
||||||
? { isCompleted: orderStatus === 'COMPLETED' }
|
? { isCompleted: orderStatus === 'COMPLETED' }
|
||||||
: { isPartial: true };
|
: { isPartial: true };
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"subject": "Teid on kutsutud tiimi",
|
||||||
|
"heading": "Liitu tiimiga {{teamName}}",
|
||||||
|
"hello": "Tere {{invitedUserEmail}},",
|
||||||
|
"mainText": "<strong>{{inviter}}</strong> on kutsunud teid ühinema tiimiga <strong>{{teamName}}</strong> platvormil <strong>{{productName}}</strong>.",
|
||||||
|
"joinTeam": "Liitu {{teamName}}",
|
||||||
|
"copyPasteLink": "või kopeeri ja kleebi see URL teie brauseris:",
|
||||||
|
"invitationIntendedFor": "See kutse on mõeldud {{invitedUserEmail}} omanikule."
|
||||||
|
}
|
||||||
@@ -214,7 +214,7 @@ class AccountsApi {
|
|||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
.from('accounts_memberships')
|
.from('accounts_memberships')
|
||||||
.select('account_id', { count: 'exact', head: true })
|
.select('account_id', { count: 'exact', head: true })
|
||||||
.eq('account_id', accountId);
|
.eq('user_id', accountId);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ async function enrichAnalysisData(analysisResponses?: AnalysisResponseBase[]) {
|
|||||||
.in('primary_owner_user_id', userIds),
|
.in('primary_owner_user_id', userIds),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (!analysisResponseElements || analysisResponseElements?.length === 0) {
|
||||||
|
console.info(`${analysisResponseIds} has no response elements`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const doctorUserIds =
|
const doctorUserIds =
|
||||||
doctorFeedbackItems
|
doctorFeedbackItems
|
||||||
?.map((item) => item.doctor_user_id)
|
?.map((item) => item.doctor_user_id)
|
||||||
@@ -285,7 +290,6 @@ export async function getOpenResponses({
|
|||||||
`,
|
`,
|
||||||
{ count: 'exact' },
|
{ count: 'exact' },
|
||||||
)
|
)
|
||||||
.neq('order_status', 'ON_HOLD')
|
|
||||||
.order('created_at', { ascending: false });
|
.order('created_at', { ascending: false });
|
||||||
|
|
||||||
if (assignedIds.length > 0) {
|
if (assignedIds.length > 0) {
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
import { createNotificationsApi } from '@kit/notifications/api';
|
||||||
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
import { getLogger } from '@kit/shared/logger';
|
import { getLogger } from '@kit/shared/logger';
|
||||||
import type { UuringuVastus } from '@kit/shared/types/medipost-analysis';
|
import type { UuringuVastus } from '@kit/shared/types/medipost-analysis';
|
||||||
import { toArray } from '@kit/shared/utils';
|
import { toArray } from '@kit/shared/utils';
|
||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
|
|
||||||
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AnalysisOrder,
|
AnalysisOrder,
|
||||||
AnalysisOrderStatus,
|
AnalysisOrderStatus,
|
||||||
@@ -488,6 +492,39 @@ class UserAnalysesApi {
|
|||||||
})
|
})
|
||||||
.throwOnError();
|
.throwOnError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendAnalysisResultsNotification({
|
||||||
|
hasFullAnalysisResponse,
|
||||||
|
hasPartialAnalysisResponse,
|
||||||
|
analysisOrderId,
|
||||||
|
}: {
|
||||||
|
hasFullAnalysisResponse: boolean;
|
||||||
|
hasPartialAnalysisResponse: boolean;
|
||||||
|
analysisOrderId?: number;
|
||||||
|
}) {
|
||||||
|
if (!analysisOrderId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { data, error: userError } = await this.client.auth.getUser();
|
||||||
|
if (userError) {
|
||||||
|
throw userError;
|
||||||
|
}
|
||||||
|
const { user } = data;
|
||||||
|
const notificationsApi = createNotificationsApi(this.client);
|
||||||
|
const { t } = await createI18nServerInstance();
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
`Order ${analysisOrderId} got new responses -> Sending new notification`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (hasFullAnalysisResponse || hasPartialAnalysisResponse) {
|
||||||
|
await notificationsApi.createNotification({
|
||||||
|
account_id: user.id,
|
||||||
|
body: t('analysis-results:notification.body'),
|
||||||
|
link: `${pathsConfig.app.analysisResults}/${analysisOrderId}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createUserAnalysesApi(client: SupabaseClient<Database>) {
|
export function createUserAnalysesApi(client: SupabaseClient<Database>) {
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "@kit/tsconfig/base.json",
|
"extends": "../../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||||
"paths": {
|
|
||||||
"~/lib/utils": ["../../../lib/utils.ts"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"include": ["*.ts", "*.tsx", "src"],
|
"include": ["*.ts", "*.tsx", "src"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
"title": "Kinkekaart või sooduskood",
|
"title": "Kinkekaart või sooduskood",
|
||||||
"label": "Lisa promo kood",
|
"label": "Lisa promo kood",
|
||||||
"apply": "Rakenda",
|
"apply": "Rakenda",
|
||||||
"subtitle": "Kui soovid, võid lisada promo koodi",
|
"subtitle": "Kui soovid, võid lisada promokoodi",
|
||||||
"placeholder": "Sisesta promo kood",
|
"placeholder": "Sisesta promokood",
|
||||||
"remove": "Eemalda promo kood",
|
"remove": "Eemalda promokood",
|
||||||
"appliedCodes": "Rakendatud sooduskoodid:",
|
"appliedCodes": "Rakendatud sooduskoodid:",
|
||||||
"removeError": "Sooduskoodi eemaldamine ebaõnnestus",
|
"removeError": "Sooduskoodi eemaldamine ebaõnnestus",
|
||||||
"removeSuccess": "Sooduskood eemaldatud",
|
"removeSuccess": "Sooduskood eemaldatud",
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
"myActions": "Minu toimingud",
|
"myActions": "Minu toimingud",
|
||||||
"healthPackageComparison": {
|
"healthPackageComparison": {
|
||||||
"label": "Tervisepakettide võrdlus",
|
"label": "Tervisepakettide võrdlus",
|
||||||
"description": "Alljärgnevalt on antud eelinfo (sugu, vanus ja kehamassiindeks) põhjal tehtud personaalne terviseauditi valik. Tabelis on võimalik soovitatud terviseuuringute paketile lisada üksikuid uuringuid juurde."
|
"description": "Alljärgnevalt on antud eelinfo (sugu, vanus ja kehamassiindeks) põhjal tehtud personaalne terviseauditi valik. Terviseuuringute paketile on võimalik lisada üksikuid uuringuid juurde."
|
||||||
},
|
},
|
||||||
"routes": {
|
"routes": {
|
||||||
"home": "Avaleht",
|
"home": "Avaleht",
|
||||||
|
|||||||
Reference in New Issue
Block a user