import { listProductTypes } from '@lib/data/products'; import Medusa from '@medusajs/js-sdk'; import type { AdminProductCategory } from '@medusajs/types'; import { createMedusaSyncFailEntry, createMedusaSyncSuccessEntry, } from '~/lib/services/analyses.service'; import { getAnalysisElements } from '~/lib/services/analysis-element.service'; import { getAnalysisGroups } from '~/lib/services/analysis-group.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 { product_categories: existingProductCategories } = await medusa.admin.productCategory.list(); const parentCategory = existingProductCategories.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.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); } } async function getChildProductCategories({ medusa }: { medusa: Medusa }) { const { product_categories: allCategories } = await medusa.admin.productCategory.list(); const childCategories = allCategories.filter( ({ parent_category_id }) => parent_category_id !== null, ); return childCategories; } async function deleteProductCategories({ medusa, categories, }: { medusa: Medusa; categories: AdminProductCategory[]; }) { for (const category of categories) { await medusa.admin.productCategory.delete(category.id); } } /** * In case a reset is needed */ async function deleteProducts({ medusa }: { medusa: Medusa }) { const { products: existingProducts } = await medusa.admin.product.list({ fields: 'id,collection_id', limit: 1000, }); await Promise.all( existingProducts .filter((a) => !a.collection_id) .map(({ id }) => medusa.admin.product.delete(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({ getAll: true }), 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; } 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, }); } } export default async function syncAnalysisGroupsStore() { const medusa = getAdminSdk(); try { await createProductCategories({ medusa }); // const categories = await getChildProductCategories({ medusa }); // await deleteProductCategories({ medusa, categories }); // 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)}`, ); } }