feat(MED-131): update analyses sync to medusa store
This commit is contained in:
220
app/api/job/handler/sync-analysis-groups-store.ts
Normal file
220
app/api/job/handler/sync-analysis-groups-store.ts
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
import Medusa from "@medusajs/js-sdk"
|
||||||
|
import type { AdminProductCategory } from "@medusajs/types";
|
||||||
|
import { listProductTypes } from "@lib/data/products";
|
||||||
|
import { getAnalysisElements } from "~/lib/services/analysis-element.service";
|
||||||
|
import { getAnalysisGroups } from "~/lib/services/analysis-group.service";
|
||||||
|
import { createMedusaSyncFailEntry, createMedusaSyncSuccessEntry } from "~/lib/services/analyses.service";
|
||||||
|
|
||||||
|
const SYNLAB_SERVICES_CATEGORY_HANDLE = 'synlab-services';
|
||||||
|
const SYNLAB_ANALYSIS_PRODUCT_TYPE_HANDLE = 'synlab-analysis';
|
||||||
|
|
||||||
|
const BASE_ANALYSIS_PRODUCT_HANDLE = 'analysis-base';
|
||||||
|
|
||||||
|
const getAdminSdk = () => {
|
||||||
|
const medusaBackendUrl = process.env.MEDUSA_BACKEND_PUBLIC_URL!;
|
||||||
|
const medusaPublishableApiKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!;
|
||||||
|
const key = process.env.MEDUSA_SECRET_API_KEY!;
|
||||||
|
|
||||||
|
if (!medusaBackendUrl || !medusaPublishableApiKey) {
|
||||||
|
throw new Error('Medusa environment variables not set');
|
||||||
|
}
|
||||||
|
return new Medusa({
|
||||||
|
baseUrl: medusaBackendUrl,
|
||||||
|
debug: process.env.NODE_ENV === 'development',
|
||||||
|
apiKey: key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createProductCategories({
|
||||||
|
medusa,
|
||||||
|
}: {
|
||||||
|
medusa: Medusa;
|
||||||
|
}) {
|
||||||
|
const existingProductCategories = await medusa.admin.productCategory.list();
|
||||||
|
const parentCategory = existingProductCategories.product_categories.find(({ handle }) => handle === SYNLAB_SERVICES_CATEGORY_HANDLE);
|
||||||
|
|
||||||
|
if (!parentCategory) {
|
||||||
|
throw new Error('Parent category not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const analysisGroups = await getAnalysisGroups();
|
||||||
|
if (!analysisGroups) {
|
||||||
|
throw new Error('Analysis groups not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdCategories: AdminProductCategory[] = [];
|
||||||
|
for (const analysisGroup of analysisGroups) {
|
||||||
|
console.info(`Processing analysis group '${analysisGroup.name}'`);
|
||||||
|
|
||||||
|
const isExisting = existingProductCategories.product_categories.find(({ name }) => name === analysisGroup.name);
|
||||||
|
const isNewlyCreated = createdCategories.find(({ name }) => name === analysisGroup.name);
|
||||||
|
if (isExisting || isNewlyCreated) {
|
||||||
|
console.info(`Analysis group '${analysisGroup.name}' already exists`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResponse = await medusa.admin.productCategory.create({
|
||||||
|
name: analysisGroup.name,
|
||||||
|
handle: analysisGroup.name,
|
||||||
|
parent_category_id: parentCategory.id,
|
||||||
|
is_active: true,
|
||||||
|
metadata: {
|
||||||
|
analysisGroupOriginalId: analysisGroup.original_id,
|
||||||
|
analysisGroupId: analysisGroup.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.info(`Successfully created category, id=${createResponse.product_category.id}`);
|
||||||
|
createdCategories.push(createResponse.product_category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case a reset is needed
|
||||||
|
*/
|
||||||
|
async function deleteProducts({
|
||||||
|
medusa,
|
||||||
|
}: {
|
||||||
|
medusa: Medusa;
|
||||||
|
}) {
|
||||||
|
const { product_categories: allCategories } = await medusa.admin.productCategory.list();
|
||||||
|
const { products: existingProducts } = await medusa.admin.product.list({
|
||||||
|
category_id: allCategories.map(({ id }) => id),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const product of existingProducts) {
|
||||||
|
await medusa.admin.product.delete(product.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAnalysisPackagesType() {
|
||||||
|
const { productTypes } = await listProductTypes();
|
||||||
|
const analysisPackagesType = productTypes.find(({ metadata }) => metadata?.handle === SYNLAB_ANALYSIS_PRODUCT_TYPE_HANDLE);
|
||||||
|
if (!analysisPackagesType) {
|
||||||
|
throw new Error('Synlab analysis packages type not found');
|
||||||
|
}
|
||||||
|
return analysisPackagesType;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProductDefaultFields({
|
||||||
|
medusa,
|
||||||
|
}: {
|
||||||
|
medusa: Medusa;
|
||||||
|
}) {
|
||||||
|
const baseProductsResponse = await medusa.admin.product.list({ handle: BASE_ANALYSIS_PRODUCT_HANDLE })
|
||||||
|
const baseProduct = baseProductsResponse.products[0];
|
||||||
|
if (!baseProduct) {
|
||||||
|
throw new Error('Base product not found');
|
||||||
|
}
|
||||||
|
const defaultSalesChannels = baseProduct.sales_channels;
|
||||||
|
if (!Array.isArray(defaultSalesChannels)) {
|
||||||
|
throw new Error('Base analysis product has no required sales channels');
|
||||||
|
}
|
||||||
|
const defaultProductOption = baseProduct.options;
|
||||||
|
if (!Array.isArray(defaultProductOption)) {
|
||||||
|
throw new Error('Base analysis product has no required options');
|
||||||
|
}
|
||||||
|
const defaultProductVariant = baseProduct.variants?.[0];
|
||||||
|
if (!defaultProductVariant) {
|
||||||
|
throw new Error('Base analysis product has no required variant');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
defaultSalesChannels,
|
||||||
|
defaultProductOption,
|
||||||
|
defaultProductVariant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createProducts({
|
||||||
|
medusa,
|
||||||
|
}: {
|
||||||
|
medusa: Medusa;
|
||||||
|
}) {
|
||||||
|
const { product_categories: allCategories } = await medusa.admin.productCategory.list();
|
||||||
|
|
||||||
|
const [
|
||||||
|
{ products: existingProducts },
|
||||||
|
analysisElements,
|
||||||
|
analysisPackagesType,
|
||||||
|
{
|
||||||
|
defaultSalesChannels,
|
||||||
|
defaultProductOption,
|
||||||
|
defaultProductVariant,
|
||||||
|
}
|
||||||
|
] = await Promise.all([
|
||||||
|
medusa.admin.product.list({
|
||||||
|
category_id: allCategories.map(({ id }) => id),
|
||||||
|
}),
|
||||||
|
getAnalysisElements(),
|
||||||
|
getAnalysisPackagesType(),
|
||||||
|
getProductDefaultFields({ medusa }),
|
||||||
|
])
|
||||||
|
|
||||||
|
for (const analysisElement of analysisElements) {
|
||||||
|
const { analysis_id_original: originalId } = analysisElement;
|
||||||
|
const isExisting = existingProducts.find(({ metadata }) => metadata?.analysisIdOriginal === originalId);
|
||||||
|
if (isExisting) {
|
||||||
|
console.info(`Analysis element '${analysisElement.analysis_name_lab}' already exists`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const { analysis_name_lab: name } = analysisElement;
|
||||||
|
if (!name) {
|
||||||
|
console.error(`Analysis element '${originalId}' has no name`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const category = allCategories.find(({ metadata }) => metadata?.analysisGroupId === analysisElement.parent_analysis_group_id);
|
||||||
|
if (!category) {
|
||||||
|
console.error(`Category not found for analysis element '${name}'`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createResponse = await medusa.admin.product.create({
|
||||||
|
title: name,
|
||||||
|
handle: `analysis-element-${analysisElement.id}`,
|
||||||
|
categories: [{ id: category.id }],
|
||||||
|
options: defaultProductOption.map(({ id, title, values }) => ({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
values: values?.map(({ value }) => value) ?? [],
|
||||||
|
})),
|
||||||
|
metadata: {
|
||||||
|
analysisIdOriginal: originalId,
|
||||||
|
},
|
||||||
|
is_giftcard: false,
|
||||||
|
discountable: false,
|
||||||
|
status: 'published',
|
||||||
|
sales_channels: defaultSalesChannels.map(({ id }) => ({ id })),
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
title: defaultProductVariant.title!,
|
||||||
|
prices: defaultProductVariant.prices!,
|
||||||
|
manage_inventory: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type_id: analysisPackagesType.id,
|
||||||
|
});
|
||||||
|
console.info(`Successfully created product, id=${createResponse.product.id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function syncAnalysisGroupsStore() {
|
||||||
|
const medusa = getAdminSdk();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createProductCategories({ medusa });
|
||||||
|
|
||||||
|
// await deleteProducts({ medusa });
|
||||||
|
// return;
|
||||||
|
|
||||||
|
await createProducts({ medusa });
|
||||||
|
|
||||||
|
await createMedusaSyncSuccessEntry();
|
||||||
|
} catch (e) {
|
||||||
|
await createMedusaSyncFailEntry(JSON.stringify(e));
|
||||||
|
console.error(e);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to sync analyses to Medusa, error: ${JSON.stringify(e)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,95 +1,42 @@
|
|||||||
import { createClient as createCustomClient } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { XMLParser } from 'fast-xml-parser';
|
import { XMLParser } from 'fast-xml-parser';
|
||||||
import { IMedipostResponse_GetPublicMessageList } from './types';
|
import fs from 'fs';
|
||||||
|
import { createAnalysisGroup } from '~/lib/services/analysis-group.service';
|
||||||
function getLatestMessage(messages: IMedipostResponse_GetPublicMessageList['messages']): IMedipostResponse_GetPublicMessageList['messages'][number] | null {
|
import { IMedipostPublicMessageDataParsed } from '~/lib/services/medipost.types';
|
||||||
if (!messages?.length) {
|
import { createAnalysis, createNoDataReceivedEntry, createNoNewDataReceivedEntry, createSyncFailEntry, createSyncSuccessEntry } from '~/lib/services/analyses.service';
|
||||||
return null;
|
import { getLastCheckedDate } from '~/lib/services/sync-entries.service';
|
||||||
}
|
import { createAnalysisElement } from '~/lib/services/analysis-element.service';
|
||||||
|
import { createCodes } from '~/lib/services/codes.service';
|
||||||
return messages.reduce((prev, current) =>
|
import { getLatestPublicMessageListItem } from '~/lib/services/medipost.service';
|
||||||
Number(prev.messageId) > Number(current.messageId) ? prev : current,
|
import type { ICode } from '~/lib/types/code';
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function toArray<T>(input?: T | T[] | null): T[] {
|
function toArray<T>(input?: T | T[] | null): T[] {
|
||||||
if (!input) return [];
|
if (!input) return [];
|
||||||
return Array.isArray(input) ? input : [input];
|
return Array.isArray(input) ? input : [input];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WRITE_XML_TO_FILE = false as boolean;
|
||||||
|
|
||||||
export default async function syncAnalysisGroups() {
|
export default async function syncAnalysisGroups() {
|
||||||
const baseUrl = process.env.MEDIPOST_URL;
|
const baseUrl = process.env.MEDIPOST_URL;
|
||||||
const user = process.env.MEDIPOST_USER;
|
const user = process.env.MEDIPOST_USER;
|
||||||
const password = process.env.MEDIPOST_PASSWORD;
|
const password = process.env.MEDIPOST_PASSWORD;
|
||||||
const sender = process.env.MEDIPOST_MESSAGE_SENDER;
|
const sender = process.env.MEDIPOST_MESSAGE_SENDER;
|
||||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
||||||
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
||||||
|
|
||||||
if (
|
if (!baseUrl || !user || !password || !sender) {
|
||||||
!baseUrl ||
|
|
||||||
!supabaseUrl ||
|
|
||||||
!supabaseServiceRoleKey ||
|
|
||||||
!user ||
|
|
||||||
!password ||
|
|
||||||
!sender
|
|
||||||
) {
|
|
||||||
throw new Error('Could not access all necessary environment variables');
|
throw new Error('Could not access all necessary environment variables');
|
||||||
}
|
}
|
||||||
|
|
||||||
const supabase = createCustomClient(supabaseUrl, supabaseServiceRoleKey, {
|
|
||||||
auth: {
|
|
||||||
persistSession: false,
|
|
||||||
autoRefreshToken: false,
|
|
||||||
detectSessionInUrl: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.info('Getting latest public message id');
|
console.info('Getting latest public message id');
|
||||||
const { data: lastChecked } = await supabase
|
const lastCheckedDate = await getLastCheckedDate();
|
||||||
.schema('audit')
|
|
||||||
.from('sync_entries')
|
|
||||||
.select('created_at')
|
|
||||||
.eq('status', 'SUCCESS')
|
|
||||||
.order('created_at')
|
|
||||||
.limit(1);
|
|
||||||
const lastEntry = lastChecked?.[0];
|
|
||||||
const lastCheckedDate = lastEntry
|
|
||||||
? format(lastEntry.created_at, 'yyyy-MM-dd HH:mm:ss')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
console.info('Getting public message list');
|
const latestMessage = await getLatestPublicMessageListItem();
|
||||||
const { data, status } = await axios.get<IMedipostResponse_GetPublicMessageList>(baseUrl, {
|
if (!latestMessage) {
|
||||||
params: {
|
|
||||||
Action: 'GetPublicMessageList',
|
|
||||||
User: user,
|
|
||||||
Password: password,
|
|
||||||
//Sender: sender,
|
|
||||||
// ...(lastCheckedDate && { LastChecked: lastCheckedDate }),
|
|
||||||
MessageType: 'Teenus',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data || status !== 200 || data.code !== 0) {
|
|
||||||
console.error("Failed to get public message list, status: ", status, data);
|
|
||||||
throw new Error('Failed to get public message list');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data.messages?.length) {
|
|
||||||
console.info('No new data received');
|
console.info('No new data received');
|
||||||
return supabase.schema('audit').from('sync_entries').insert({
|
await createNoNewDataReceivedEntry();
|
||||||
operation: 'ANALYSES_SYNC',
|
return;
|
||||||
comment: 'No new data received',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
changed_by_role: 'service_role',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET PUBLIC MESSAGE WITH GIVEN ID
|
|
||||||
const latestMessage = getLatestMessage(data?.messages)!;
|
|
||||||
console.info('Getting public message with id: ', latestMessage.messageId);
|
console.info('Getting public message with id: ', latestMessage.messageId);
|
||||||
|
|
||||||
const { data: publicMessageData } = await axios.get(baseUrl, {
|
const { data: publicMessageData } = await axios.get(baseUrl, {
|
||||||
@@ -104,8 +51,12 @@ export default async function syncAnalysisGroups() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (WRITE_XML_TO_FILE) {
|
||||||
|
fs.writeFileSync('public-messages-list-response.xml', publicMessageData);
|
||||||
|
}
|
||||||
|
|
||||||
const parser = new XMLParser({ ignoreAttributes: false });
|
const parser = new XMLParser({ ignoreAttributes: false });
|
||||||
const parsed = parser.parse(publicMessageData);
|
const parsed: IMedipostPublicMessageDataParsed = parser.parse(publicMessageData);
|
||||||
|
|
||||||
if (parsed.ANSWER?.CODE && parsed.ANSWER?.CODE !== 0) {
|
if (parsed.ANSWER?.CODE && parsed.ANSWER?.CODE !== 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -121,36 +72,19 @@ export default async function syncAnalysisGroups() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!parsed || !analysisGroups.length) {
|
if (!parsed || !analysisGroups.length) {
|
||||||
return supabase.schema('audit').from('sync_entries').insert({
|
console.info('No analysis groups data received');
|
||||||
operation: 'ANALYSES_SYNC',
|
await createNoDataReceivedEntry();
|
||||||
comment: 'No data received',
|
return;
|
||||||
status: 'FAIL',
|
|
||||||
changed_by_role: 'service_role',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const codes: any[] = [];
|
const codes: ICode[] = [];
|
||||||
for (const analysisGroup of analysisGroups) {
|
for (const analysisGroup of analysisGroups) {
|
||||||
// SAVE ANALYSIS GROUP
|
// SAVE ANALYSIS GROUP
|
||||||
const { data: insertedAnalysisGroup, error } = await supabase
|
const analysisGroupId = await createAnalysisGroup({
|
||||||
.schema('medreport')
|
id: analysisGroup.UuringuGruppId,
|
||||||
.from('analysis_groups')
|
name: analysisGroup.UuringuGruppNimi,
|
||||||
.upsert(
|
order: analysisGroup.UuringuGruppJarjekord,
|
||||||
{
|
});
|
||||||
original_id: analysisGroup.UuringuGruppId,
|
|
||||||
name: analysisGroup.UuringuGruppNimi,
|
|
||||||
order: analysisGroup.UuringuGruppJarjekord,
|
|
||||||
},
|
|
||||||
{ onConflict: 'original_id', ignoreDuplicates: false },
|
|
||||||
)
|
|
||||||
.select('id');
|
|
||||||
|
|
||||||
if (error || !insertedAnalysisGroup[0]?.id) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to insert analysis group (id: ${analysisGroup.UuringuGruppId}), error: ${error?.message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const analysisGroupId = insertedAnalysisGroup[0].id;
|
|
||||||
|
|
||||||
const analysisGroupCodes = toArray(analysisGroup.Kood);
|
const analysisGroupCodes = toArray(analysisGroup.Kood);
|
||||||
codes.push(
|
codes.push(
|
||||||
@@ -170,31 +104,11 @@ export default async function syncAnalysisGroups() {
|
|||||||
for (const item of analysisGroupItems) {
|
for (const item of analysisGroupItems) {
|
||||||
const analysisElement = item.UuringuElement;
|
const analysisElement = item.UuringuElement;
|
||||||
|
|
||||||
const { data: insertedAnalysisElement, error } = await supabase
|
const insertedAnalysisElementId = await createAnalysisElement({
|
||||||
.schema('medreport')
|
analysisElement,
|
||||||
.from('analysis_elements')
|
analysisGroupId,
|
||||||
.upsert(
|
materialGroups: toArray(item.MaterjalideGrupp),
|
||||||
{
|
});
|
||||||
analysis_id_oid: analysisElement.UuringIdOID,
|
|
||||||
analysis_id_original: analysisElement.UuringId,
|
|
||||||
tehik_short_loinc: analysisElement.TLyhend,
|
|
||||||
tehik_loinc_name: analysisElement.KNimetus,
|
|
||||||
analysis_name_lab: analysisElement.UuringNimi,
|
|
||||||
order: analysisElement.Jarjekord,
|
|
||||||
parent_analysis_group_id: analysisGroupId,
|
|
||||||
material_groups: toArray(item.MaterjalideGrupp),
|
|
||||||
},
|
|
||||||
{ onConflict: 'analysis_id_original', ignoreDuplicates: false },
|
|
||||||
)
|
|
||||||
.select('id');
|
|
||||||
|
|
||||||
if (error || !insertedAnalysisElement[0]?.id) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to insert analysis element (id: ${analysisElement.UuringId}), error: ${error?.message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertedAnalysisElementId = insertedAnalysisElement[0].id;
|
|
||||||
|
|
||||||
if (analysisElement.Kood) {
|
if (analysisElement.Kood) {
|
||||||
const analysisElementCodes = toArray(analysisElement.Kood);
|
const analysisElementCodes = toArray(analysisElement.Kood);
|
||||||
@@ -214,30 +128,8 @@ export default async function syncAnalysisGroups() {
|
|||||||
const analyses = analysisElement.UuringuElement;
|
const analyses = analysisElement.UuringuElement;
|
||||||
if (analyses?.length) {
|
if (analyses?.length) {
|
||||||
for (const analysis of analyses) {
|
for (const analysis of analyses) {
|
||||||
const { data: insertedAnalysis, error } = await supabase
|
const insertedAnalysisId = await createAnalysis(analysis, analysisGroupId);
|
||||||
.schema('medreport')
|
|
||||||
.from('analyses')
|
|
||||||
.upsert(
|
|
||||||
{
|
|
||||||
analysis_id_oid: analysis.UuringIdOID,
|
|
||||||
analysis_id_original: analysis.UuringId,
|
|
||||||
tehik_short_loinc: analysis.TLyhend,
|
|
||||||
tehik_loinc_name: analysis.KNimetus,
|
|
||||||
analysis_name_lab: analysis.UuringNimi,
|
|
||||||
order: analysis.Jarjekord,
|
|
||||||
parent_analysis_element_id: insertedAnalysisElementId,
|
|
||||||
},
|
|
||||||
{ onConflict: 'analysis_id_original', ignoreDuplicates: false },
|
|
||||||
)
|
|
||||||
.select('id');
|
|
||||||
|
|
||||||
if (error || !insertedAnalysis[0]?.id) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to insert analysis (id: ${analysis.UuringId}) error: ${error?.message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertedAnalysisId = insertedAnalysis[0].id;
|
|
||||||
if (analysis.Kood) {
|
if (analysis.Kood) {
|
||||||
const analysisCodes = toArray(analysis.Kood);
|
const analysisCodes = toArray(analysis.Kood);
|
||||||
|
|
||||||
@@ -259,24 +151,12 @@ export default async function syncAnalysisGroups() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.info('Inserting codes');
|
console.info('Inserting codes');
|
||||||
await supabase.schema('medreport').from('codes').upsert(codes);
|
await createCodes(codes);
|
||||||
|
|
||||||
console.info('Inserting sync entry');
|
console.info('Inserting sync entry');
|
||||||
await supabase.schema('audit').from('sync_entries').insert({
|
await createSyncSuccessEntry();
|
||||||
operation: 'ANALYSES_SYNC',
|
|
||||||
status: 'SUCCESS',
|
|
||||||
changed_by_role: 'service_role',
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await supabase
|
await createSyncFailEntry(JSON.stringify(e));
|
||||||
.schema('audit')
|
|
||||||
.from('sync_entries')
|
|
||||||
.insert({
|
|
||||||
operation: 'ANALYSES_SYNC',
|
|
||||||
status: 'FAIL',
|
|
||||||
comment: JSON.stringify(e),
|
|
||||||
changed_by_role: 'service_role',
|
|
||||||
});
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to sync public message data, error: ${JSON.stringify(e)}`,
|
`Failed to sync public message data, error: ${JSON.stringify(e)}`,
|
||||||
|
|||||||
27
app/api/job/sync-analysis-groups-store/route.ts
Normal file
27
app/api/job/sync-analysis-groups-store/route.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import loadEnv from "../handler/load-env";
|
||||||
|
import validateApiKey from "../handler/validate-api-key";
|
||||||
|
import syncAnalysisGroupsStore from "../handler/sync-analysis-groups-store";
|
||||||
|
|
||||||
|
export const GET = async (request: NextRequest) => {
|
||||||
|
loadEnv();
|
||||||
|
|
||||||
|
try {
|
||||||
|
validateApiKey(request);
|
||||||
|
} catch (e) {
|
||||||
|
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await syncAnalysisGroupsStore();
|
||||||
|
console.info("Successfully synced analysis groups store");
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Successfully synced analysis groups store',
|
||||||
|
}, { status: 200 });
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error syncing analysis groups store", e);
|
||||||
|
return NextResponse.json({
|
||||||
|
message: 'Failed to sync analysis groups store',
|
||||||
|
}, { status: 500 });
|
||||||
|
}
|
||||||
|
};
|
||||||
99
lib/services/analyses.service.ts
Normal file
99
lib/services/analyses.service.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
|
import type { IUuringElement } from "./medipost.types";
|
||||||
|
|
||||||
|
export const createAnalysis = async (
|
||||||
|
analysis: IUuringElement,
|
||||||
|
insertedAnalysisElementId: number,
|
||||||
|
) => {
|
||||||
|
const { data: insertedAnalysis, error } = await getSupabaseServerAdminClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analyses')
|
||||||
|
.upsert(
|
||||||
|
{
|
||||||
|
analysis_id_oid: analysis.UuringIdOID,
|
||||||
|
analysis_id_original: analysis.UuringId,
|
||||||
|
tehik_short_loinc: analysis.TLyhend,
|
||||||
|
tehik_loinc_name: analysis.KNimetus,
|
||||||
|
analysis_name_lab: analysis.UuringNimi,
|
||||||
|
order: analysis.Jarjekord,
|
||||||
|
parent_analysis_element_id: insertedAnalysisElementId,
|
||||||
|
},
|
||||||
|
{ onConflict: 'analysis_id_original', ignoreDuplicates: false },
|
||||||
|
)
|
||||||
|
.select('id');
|
||||||
|
const insertedAnalysisId = insertedAnalysis?.[0]?.id as number;
|
||||||
|
|
||||||
|
if (error || !insertedAnalysisId) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to insert analysis (id: ${analysis.UuringId}) error: ${error?.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return insertedAnalysisId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSyncEntry = async ({
|
||||||
|
operation,
|
||||||
|
status,
|
||||||
|
comment,
|
||||||
|
}: {
|
||||||
|
operation: 'ANALYSES_SYNC' | 'ANALYSIS_GROUPS_SYNC' | 'ANALYSES_MEDUSA_SYNC';
|
||||||
|
status: 'SUCCESS' | 'FAIL';
|
||||||
|
comment?: string;
|
||||||
|
}) => {
|
||||||
|
await getSupabaseServerAdminClient()
|
||||||
|
.schema('audit').from('sync_entries')
|
||||||
|
.insert({
|
||||||
|
operation,
|
||||||
|
status,
|
||||||
|
changed_by_role: 'service_role',
|
||||||
|
comment,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNoNewDataReceivedEntry = async () => {
|
||||||
|
await createSyncEntry({
|
||||||
|
operation: 'ANALYSES_SYNC',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
comment: 'No new data received',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createNoDataReceivedEntry = async () => {
|
||||||
|
await createSyncEntry({
|
||||||
|
operation: 'ANALYSES_SYNC',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
comment: 'No data received',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createSyncFailEntry = async (error: string) => {
|
||||||
|
await createSyncEntry({
|
||||||
|
operation: 'ANALYSES_SYNC',
|
||||||
|
status: 'FAIL',
|
||||||
|
comment: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createSyncSuccessEntry = async () => {
|
||||||
|
await createSyncEntry({
|
||||||
|
operation: 'ANALYSES_SYNC',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createMedusaSyncFailEntry = async (error: string) => {
|
||||||
|
await createSyncEntry({
|
||||||
|
operation: 'ANALYSES_MEDUSA_SYNC',
|
||||||
|
status: 'FAIL',
|
||||||
|
comment: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const createMedusaSyncSuccessEntry = async () => {
|
||||||
|
await createSyncEntry({
|
||||||
|
operation: 'ANALYSES_MEDUSA_SYNC',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,18 +7,21 @@ export type AnalysisElement = Tables<{ schema: 'medreport' }, 'analysis_elements
|
|||||||
analysis_groups: Tables<{ schema: 'medreport' }, 'analysis_groups'>;
|
analysis_groups: Tables<{ schema: 'medreport' }, 'analysis_groups'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getAnalysisElements({
|
export async function getAnalysisElements({ originalIds }: {
|
||||||
originalIds,
|
originalIds?: string[]
|
||||||
}: {
|
} = {}) {
|
||||||
originalIds: string[]
|
const query = getSupabaseServerClient()
|
||||||
}) {
|
|
||||||
const { data: analysisElements } = await getSupabaseServerClient()
|
|
||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
.from('analysis_elements')
|
.from('analysis_elements')
|
||||||
.select(`*, analysis_groups(*)`)
|
.select(`*, analysis_groups(*)`)
|
||||||
.in('analysis_id_original', [...new Set(originalIds)])
|
|
||||||
.order('order', { ascending: true });
|
.order('order', { ascending: true });
|
||||||
|
|
||||||
|
if (Array.isArray(originalIds)) {
|
||||||
|
query.in('analysis_id_original', [...new Set(originalIds)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: analysisElements } = await query;
|
||||||
|
|
||||||
return analysisElements ?? [];
|
return analysisElements ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
lib/services/analysis-group.service.ts
Normal file
40
lib/services/analysis-group.service.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { getSupabaseServerAdminClient } from "@kit/supabase/server-admin-client";
|
||||||
|
|
||||||
|
export const createAnalysisGroup = async (
|
||||||
|
analysisGroup: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
order: number;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
const { data: insertedAnalysisGroup, error } = await getSupabaseServerAdminClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_groups')
|
||||||
|
.upsert(
|
||||||
|
{
|
||||||
|
original_id: analysisGroup.id,
|
||||||
|
name: analysisGroup.name,
|
||||||
|
order: analysisGroup.order,
|
||||||
|
},
|
||||||
|
{ onConflict: 'original_id', ignoreDuplicates: false },
|
||||||
|
)
|
||||||
|
.select('id');
|
||||||
|
const analysisGroupId = insertedAnalysisGroup?.[0]?.id as number;
|
||||||
|
|
||||||
|
if (error || !analysisGroupId) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to insert analysis group (id: ${analysisGroup.id}), error: ${error?.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return analysisGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAnalysisGroups = async () => {
|
||||||
|
const { data: analysisGroups } = await getSupabaseServerAdminClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_groups')
|
||||||
|
.select('*');
|
||||||
|
|
||||||
|
return analysisGroups;
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ import { XMLParser } from 'fast-xml-parser';
|
|||||||
import { uniqBy } from 'lodash';
|
import { uniqBy } from 'lodash';
|
||||||
|
|
||||||
import { Tables } from '@kit/supabase/database';
|
import { Tables } from '@kit/supabase/database';
|
||||||
|
import { createAnalysisGroup } from './analysis-group.service';
|
||||||
|
|
||||||
const BASE_URL = process.env.MEDIPOST_URL!;
|
const BASE_URL = process.env.MEDIPOST_URL!;
|
||||||
const USER = process.env.MEDIPOST_USER!;
|
const USER = process.env.MEDIPOST_USER!;
|
||||||
@@ -196,25 +197,11 @@ async function saveAnalysisGroup(
|
|||||||
analysisGroup: UuringuGrupp,
|
analysisGroup: UuringuGrupp,
|
||||||
supabase: SupabaseClient,
|
supabase: SupabaseClient,
|
||||||
) {
|
) {
|
||||||
const { data: insertedAnalysisGroup, error } = await supabase
|
const analysisGroupId = await createAnalysisGroup({
|
||||||
.schema('medreport')
|
id: analysisGroup.UuringuGruppId,
|
||||||
.from('analysis_groups')
|
name: analysisGroup.UuringuGruppNimi,
|
||||||
.upsert(
|
order: analysisGroup.UuringuGruppJarjekord,
|
||||||
{
|
});
|
||||||
original_id: analysisGroup.UuringuGruppId,
|
|
||||||
name: analysisGroup.UuringuGruppNimi,
|
|
||||||
order: analysisGroup.UuringuGruppJarjekord,
|
|
||||||
},
|
|
||||||
{ onConflict: 'original_id', ignoreDuplicates: false },
|
|
||||||
)
|
|
||||||
.select('id');
|
|
||||||
|
|
||||||
if (error || !insertedAnalysisGroup[0]?.id) {
|
|
||||||
throw new Error(
|
|
||||||
`Failed to insert analysis group (id: ${analysisGroup.UuringuGruppId}), error: ${error?.message}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const analysisGroupId = insertedAnalysisGroup[0].id;
|
|
||||||
|
|
||||||
const analysisGroupCodes = toArray(analysisGroup.Kood);
|
const analysisGroupCodes = toArray(analysisGroup.Kood);
|
||||||
const codes: Partial<Tables<{ schema: 'medreport' }, 'codes'>>[] =
|
const codes: Partial<Tables<{ schema: 'medreport' }, 'codes'>>[] =
|
||||||
|
|||||||
18
lib/services/sync-entries.service.ts
Normal file
18
lib/services/sync-entries.service.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
|
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
|
||||||
|
|
||||||
|
export const getLastCheckedDate = async () => {
|
||||||
|
const { data: lastChecked } = await getSupabaseServerAdminClient()
|
||||||
|
.schema('audit')
|
||||||
|
.from('sync_entries')
|
||||||
|
.select('created_at')
|
||||||
|
.eq('status', 'SUCCESS')
|
||||||
|
.order('created_at')
|
||||||
|
.limit(1);
|
||||||
|
const lastEntry = lastChecked?.[0];
|
||||||
|
const lastCheckedDate = lastEntry
|
||||||
|
? format(lastEntry.created_at, 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return lastCheckedDate;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user