feat(MED-131): send fake test response for an order
This commit is contained in:
@@ -30,8 +30,8 @@ async function createProductCategories({
|
|||||||
}: {
|
}: {
|
||||||
medusa: Medusa;
|
medusa: Medusa;
|
||||||
}) {
|
}) {
|
||||||
const existingProductCategories = await medusa.admin.productCategory.list();
|
const { product_categories: existingProductCategories } = await medusa.admin.productCategory.list();
|
||||||
const parentCategory = existingProductCategories.product_categories.find(({ handle }) => handle === SYNLAB_SERVICES_CATEGORY_HANDLE);
|
const parentCategory = existingProductCategories.find(({ handle }) => handle === SYNLAB_SERVICES_CATEGORY_HANDLE);
|
||||||
|
|
||||||
if (!parentCategory) {
|
if (!parentCategory) {
|
||||||
throw new Error('Parent category not found');
|
throw new Error('Parent category not found');
|
||||||
@@ -46,7 +46,7 @@ async function createProductCategories({
|
|||||||
for (const analysisGroup of analysisGroups) {
|
for (const analysisGroup of analysisGroups) {
|
||||||
console.info(`Processing analysis group '${analysisGroup.name}'`);
|
console.info(`Processing analysis group '${analysisGroup.name}'`);
|
||||||
|
|
||||||
const isExisting = existingProductCategories.product_categories.find(({ name }) => name === analysisGroup.name);
|
const isExisting = existingProductCategories.find(({ name }) => name === analysisGroup.name);
|
||||||
const isNewlyCreated = createdCategories.find(({ name }) => name === analysisGroup.name);
|
const isNewlyCreated = createdCategories.find(({ name }) => name === analysisGroup.name);
|
||||||
if (isExisting || isNewlyCreated) {
|
if (isExisting || isNewlyCreated) {
|
||||||
console.info(`Analysis group '${analysisGroup.name}' already exists`);
|
console.info(`Analysis group '${analysisGroup.name}' already exists`);
|
||||||
@@ -68,6 +68,28 @@ async function createProductCategories({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* In case a reset is needed
|
||||||
*/
|
*/
|
||||||
@@ -76,14 +98,12 @@ async function deleteProducts({
|
|||||||
}: {
|
}: {
|
||||||
medusa: Medusa;
|
medusa: Medusa;
|
||||||
}) {
|
}) {
|
||||||
const { product_categories: allCategories } = await medusa.admin.productCategory.list();
|
|
||||||
const { products: existingProducts } = await medusa.admin.product.list({
|
const { products: existingProducts } = await medusa.admin.product.list({
|
||||||
category_id: allCategories.map(({ id }) => id),
|
fields: 'id,collection_id',
|
||||||
|
limit: 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const product of existingProducts) {
|
await Promise.all(existingProducts.filter((a) => !a.collection_id).map(({ id }) => medusa.admin.product.delete(id)));
|
||||||
await medusa.admin.product.delete(product.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAnalysisPackagesType() {
|
async function getAnalysisPackagesType() {
|
||||||
@@ -145,7 +165,7 @@ async function createProducts({
|
|||||||
medusa.admin.product.list({
|
medusa.admin.product.list({
|
||||||
category_id: allCategories.map(({ id }) => id),
|
category_id: allCategories.map(({ id }) => id),
|
||||||
}),
|
}),
|
||||||
getAnalysisElements(),
|
getAnalysisElements({}),
|
||||||
getAnalysisPackagesType(),
|
getAnalysisPackagesType(),
|
||||||
getProductDefaultFields({ medusa }),
|
getProductDefaultFields({ medusa }),
|
||||||
])
|
])
|
||||||
@@ -169,7 +189,7 @@ async function createProducts({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createResponse = await medusa.admin.product.create({
|
await medusa.admin.product.create({
|
||||||
title: name,
|
title: name,
|
||||||
handle: `analysis-element-${analysisElement.id}`,
|
handle: `analysis-element-${analysisElement.id}`,
|
||||||
categories: [{ id: category.id }],
|
categories: [{ id: category.id }],
|
||||||
@@ -194,7 +214,6 @@ async function createProducts({
|
|||||||
],
|
],
|
||||||
type_id: analysisPackagesType.id,
|
type_id: analysisPackagesType.id,
|
||||||
});
|
});
|
||||||
console.info(`Successfully created product, id=${createResponse.product.id}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +223,8 @@ export default async function syncAnalysisGroupsStore() {
|
|||||||
try {
|
try {
|
||||||
await createProductCategories({ medusa });
|
await createProductCategories({ medusa });
|
||||||
|
|
||||||
|
// const categories = await getChildProductCategories({ medusa });
|
||||||
|
// await deleteProductCategories({ medusa, categories });
|
||||||
// await deleteProducts({ medusa });
|
// await deleteProducts({ medusa });
|
||||||
// return;
|
// return;
|
||||||
|
|
||||||
|
|||||||
52
app/api/order/medipost-test-response/route.ts
Normal file
52
app/api/order/medipost-test-response/route.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { getOrder } from "~/lib/services/order.service";
|
||||||
|
import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipostTest.service";
|
||||||
|
import { retrieveOrder } from "@lib/data";
|
||||||
|
import { getAccountAdmin } from "~/lib/services/account.service";
|
||||||
|
|
||||||
|
export async function POST(request: Request) {
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
if (!isDev) {
|
||||||
|
return NextResponse.json({ error: 'This endpoint is only available in development mode' }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { medusaOrderId } = await request.json();
|
||||||
|
|
||||||
|
const medusaOrder = await retrieveOrder(medusaOrderId)
|
||||||
|
const medreportOrder = await getOrder({ medusaOrderId });
|
||||||
|
|
||||||
|
const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id });
|
||||||
|
|
||||||
|
const ANALYSIS_ELEMENT_HANDLE_PREFIX = 'analysis-element-';
|
||||||
|
const orderedAnalysisElementsIds = (medusaOrder?.items ?? [])
|
||||||
|
.filter((item) => item.product?.handle?.startsWith(ANALYSIS_ELEMENT_HANDLE_PREFIX))
|
||||||
|
.map((item) => {
|
||||||
|
const id = Number(item.product?.handle?.replace(ANALYSIS_ELEMENT_HANDLE_PREFIX, ''));
|
||||||
|
if (Number.isNaN(id)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as number[];
|
||||||
|
|
||||||
|
const messageXml = await composeOrderTestResponseXML({
|
||||||
|
person: {
|
||||||
|
idCode: account.personal_code!,
|
||||||
|
firstName: account.name ?? '',
|
||||||
|
lastName: account.last_name ?? '',
|
||||||
|
phone: account.phone ?? '',
|
||||||
|
},
|
||||||
|
orderedAnalysisElementsIds,
|
||||||
|
orderedAnalysesIds: [],
|
||||||
|
orderId: medusaOrderId,
|
||||||
|
orderCreatedAt: new Date(medreportOrder.created_at),
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendPrivateMessageTestResponse({ messageXml });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending private message test response: ", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
}
|
||||||
177
lib/services/medipostTest.service.ts
Normal file
177
lib/services/medipostTest.service.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getClientInstitution,
|
||||||
|
getClientPerson,
|
||||||
|
getPais,
|
||||||
|
getPatient,
|
||||||
|
getProviderInstitution,
|
||||||
|
} from '@/lib/templates/medipost-order';
|
||||||
|
import {
|
||||||
|
MedipostAction,
|
||||||
|
} from '@/lib/types/medipost';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { uniqBy } from 'lodash';
|
||||||
|
|
||||||
|
import { Tables } from '@kit/supabase/database';
|
||||||
|
import { formatDate } from 'date-fns';
|
||||||
|
import { getAnalyses } from './analyses.service';
|
||||||
|
import { getAnalysisElements } from './analysis-element.service';
|
||||||
|
import { validateMedipostResponse } from './medipost.service';
|
||||||
|
|
||||||
|
const BASE_URL = process.env.MEDIPOST_URL!;
|
||||||
|
const USER = process.env.MEDIPOST_USER!;
|
||||||
|
const PASSWORD = process.env.MEDIPOST_PASSWORD!;
|
||||||
|
const RECIPIENT = process.env.MEDIPOST_RECIPIENT!;
|
||||||
|
|
||||||
|
export async function sendPrivateMessageTestResponse({
|
||||||
|
messageXml,
|
||||||
|
}: {
|
||||||
|
messageXml: string;
|
||||||
|
}) {
|
||||||
|
const body = new FormData();
|
||||||
|
body.append('Action', MedipostAction.SendPrivateMessage);
|
||||||
|
body.append('User', USER);
|
||||||
|
body.append('Password', PASSWORD);
|
||||||
|
body.append('Receiver', RECIPIENT);
|
||||||
|
body.append('MessageType', 'Vastus');
|
||||||
|
body.append(
|
||||||
|
'Message',
|
||||||
|
new Blob([messageXml], {
|
||||||
|
type: 'text/xml; charset=UTF-8',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data } = await axios.post(BASE_URL, body);
|
||||||
|
await validateMedipostResponse(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomInt(min: number, max: number) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function composeOrderTestResponseXML({
|
||||||
|
person,
|
||||||
|
orderedAnalysisElementsIds,
|
||||||
|
orderedAnalysesIds,
|
||||||
|
orderId,
|
||||||
|
orderCreatedAt,
|
||||||
|
}: {
|
||||||
|
person: {
|
||||||
|
idCode: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
phone: string;
|
||||||
|
};
|
||||||
|
orderedAnalysisElementsIds: number[];
|
||||||
|
orderedAnalysesIds: number[];
|
||||||
|
orderId: string;
|
||||||
|
orderCreatedAt: Date;
|
||||||
|
}) {
|
||||||
|
const analysisElements = await getAnalysisElements({ ids: orderedAnalysisElementsIds });
|
||||||
|
const analyses = await getAnalyses({ ids: orderedAnalysesIds });
|
||||||
|
|
||||||
|
const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] =
|
||||||
|
uniqBy(
|
||||||
|
(
|
||||||
|
analysisElements?.flatMap(({ analysis_groups }) => analysis_groups) ??
|
||||||
|
[]
|
||||||
|
).concat(
|
||||||
|
analyses?.flatMap(
|
||||||
|
({ analysis_elements }) => analysis_elements.analysis_groups,
|
||||||
|
) ?? [],
|
||||||
|
),
|
||||||
|
'id',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tellimuse olek:
|
||||||
|
// 1 – Järjekorras, 2 – Ootel, 3 - Töös, 4 – Lõpetatud,
|
||||||
|
// 5 – Tagasi lükatud, 6 – Tühistatud.
|
||||||
|
const orderStatus = 4;
|
||||||
|
const orderNumber = 'TSU000001200';
|
||||||
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Saadetis xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TellimusLOINC.xsd">
|
||||||
|
${getPais(USER, RECIPIENT, orderCreatedAt, orderId, "AL")}
|
||||||
|
<Vastus>
|
||||||
|
<ValisTellimuseId>${orderId}</ValisTellimuseId>
|
||||||
|
${getClientInstitution({ index: 1 })}
|
||||||
|
${getProviderInstitution({ index: 1 })}
|
||||||
|
${getClientPerson(person)}
|
||||||
|
<TellijaMarkused>Siia tuleb tellija poolne märkus</TellijaMarkused>
|
||||||
|
|
||||||
|
${getPatient(person)}
|
||||||
|
|
||||||
|
<Proov>
|
||||||
|
<ProovinouIdOID>1.3.6.1.4.1.28284.1.625.2.17</ProovinouIdOID>
|
||||||
|
<ProovinouId>16522314</ProovinouId>
|
||||||
|
<MaterjaliTyypOID>1.3.6.1.4.1.28284.6.2.1.244.8</MaterjaliTyypOID>
|
||||||
|
<MaterjaliTyyp>119297000</MaterjaliTyyp>
|
||||||
|
<MaterjaliNimi>Veri</MaterjaliNimi>
|
||||||
|
<Ribakood>16522314</Ribakood>
|
||||||
|
<Jarjenumber>1</Jarjenumber>
|
||||||
|
<VotmisAeg>2022-08-19 08:53:00</VotmisAeg>
|
||||||
|
<SaabumisAeg>2022-08-23 15:10:00</SaabumisAeg>
|
||||||
|
</Proov>
|
||||||
|
|
||||||
|
<TellimuseNumber>${orderNumber}</TellimuseNumber>
|
||||||
|
|
||||||
|
<TellimuseOlek>${orderStatus}</TellimuseOlek>
|
||||||
|
${analysisGroups.map((group) => {
|
||||||
|
let relatedAnalysisElement = analysisElements?.find(
|
||||||
|
(element) => element.analysis_groups.id === group.id,
|
||||||
|
);
|
||||||
|
const relatedAnalyses = analyses?.filter((analysis) => {
|
||||||
|
return analysis.analysis_elements.analysis_groups.id === group.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!relatedAnalysisElement) {
|
||||||
|
relatedAnalysisElement = relatedAnalyses?.find(
|
||||||
|
(relatedAnalysis) =>
|
||||||
|
relatedAnalysis.analysis_elements.analysis_groups.id ===
|
||||||
|
group.id,
|
||||||
|
)?.analysis_elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relatedAnalysisElement || !relatedAnalysisElement.material_groups) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to find related analysis element for group ${group.name} (id: ${group.id})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = getRandomInt(0, 100);
|
||||||
|
const upper = getRandomInt(lower + 1, 500);
|
||||||
|
const result = getRandomInt(lower, upper);
|
||||||
|
return (`
|
||||||
|
<UuringuGrupp>
|
||||||
|
<UuringuGruppId>${group.original_id}</UuringuGruppId>
|
||||||
|
<UuringuGruppNimi>${group.name}</UuringuGruppNimi>
|
||||||
|
<Uuring>
|
||||||
|
<UuringuElement>
|
||||||
|
<UuringIdOID>${relatedAnalysisElement.analysis_id_oid}</UuringIdOID>
|
||||||
|
<UuringId>${relatedAnalysisElement.analysis_id_original}</UuringId>
|
||||||
|
<TLyhend>${relatedAnalysisElement.tehik_short_loinc}</TLyhend>
|
||||||
|
<KNimetus>${relatedAnalysisElement.tehik_loinc_name}</KNimetus>
|
||||||
|
<UuringNimi>${relatedAnalysisElement.analysis_name_lab ?? relatedAnalysisElement.tehik_loinc_name}</UuringNimi>
|
||||||
|
<TellijaUuringId>${relatedAnalysisElement.id}</TellijaUuringId>
|
||||||
|
<TeostajaUuringId>${relatedAnalysisElement.id}</TeostajaUuringId>
|
||||||
|
<UuringOlek>4</UuringOlek>
|
||||||
|
<Mootyhik>%</Mootyhik>
|
||||||
|
<UuringuVastus>
|
||||||
|
<VastuseVaartus>${result}</VastuseVaartus>
|
||||||
|
<VastuseAeg>${formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')}</VastuseAeg>
|
||||||
|
<NormYlem kaasaarvatud=\"EI\">${upper}</NormYlem>
|
||||||
|
<NormAlum kaasaarvatud=\"EI\">${lower}</NormAlum>
|
||||||
|
<NormiStaatus>0</NormiStaatus>
|
||||||
|
<ProoviJarjenumber>1</ProoviJarjenumber>
|
||||||
|
</UuringuVastus>
|
||||||
|
</UuringuElement>
|
||||||
|
<UuringuTaitjaAsutuseJnr>2</UuringuTaitjaAsutuseJnr>
|
||||||
|
</Uuring>
|
||||||
|
</UuringuGrupp>
|
||||||
|
`);
|
||||||
|
}).join('')}
|
||||||
|
</Vastus>
|
||||||
|
</Saadetis>`;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import Isikukood from 'isikukood';
|
import Isikukood, { Gender } from 'isikukood';
|
||||||
import { Tables } from '@/packages/supabase/src/database.types';
|
import { Tables } from '@/packages/supabase/src/database.types';
|
||||||
import { DATE_FORMAT, DATE_TIME_FORMAT } from '@/lib/constants';
|
import { DATE_FORMAT, DATE_TIME_FORMAT } from '@/lib/constants';
|
||||||
|
|
||||||
@@ -9,26 +9,27 @@ export const getPais = (
|
|||||||
sender: string,
|
sender: string,
|
||||||
recipient: string,
|
recipient: string,
|
||||||
createdAt: Date,
|
createdAt: Date,
|
||||||
messageId: number,
|
orderId: string,
|
||||||
|
packageName = "OL",
|
||||||
) => {
|
) => {
|
||||||
if (isProd) {
|
if (isProd) {
|
||||||
// return correct data
|
// return correct data
|
||||||
}
|
}
|
||||||
return `<Pais>
|
return `<Pais>
|
||||||
<Pakett versioon="20">OL</Pakett>
|
<Pakett versioon="20">${packageName}</Pakett>
|
||||||
<Saatja>${sender}</Saatja>
|
<Saatja>${sender}</Saatja>
|
||||||
<Saaja>${recipient}</Saaja>
|
<Saaja>${recipient}</Saaja>
|
||||||
<Aeg>${format(createdAt, DATE_TIME_FORMAT)}</Aeg>
|
<Aeg>${format(createdAt, DATE_TIME_FORMAT)}</Aeg>
|
||||||
<SaadetisId>${messageId}</SaadetisId>
|
<SaadetisId>${orderId}</SaadetisId>
|
||||||
<Email>argo@medreport.ee</Email>
|
<Email>argo@medreport.ee</Email>
|
||||||
</Pais>`;
|
</Pais>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getClientInstitution = () => {
|
export const getClientInstitution = ({ index }: { index?: number } = {}) => {
|
||||||
if (isProd) {
|
if (isProd) {
|
||||||
// return correct data
|
// return correct data
|
||||||
}
|
}
|
||||||
return `<Asutus tyyp="TELLIJA">
|
return `<Asutus tyyp="TELLIJA" ${index ? ` jarjenumber="${index}"` : ''}>
|
||||||
<AsutuseId>16381793</AsutuseId>
|
<AsutuseId>16381793</AsutuseId>
|
||||||
<AsutuseNimi>MedReport OÜ</AsutuseNimi>
|
<AsutuseNimi>MedReport OÜ</AsutuseNimi>
|
||||||
<AsutuseKood>TSU</AsutuseKood>
|
<AsutuseKood>TSU</AsutuseKood>
|
||||||
@@ -36,11 +37,11 @@ export const getClientInstitution = () => {
|
|||||||
</Asutus>`;
|
</Asutus>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProviderInstitution = () => {
|
export const getProviderInstitution = ({ index }: { index?: number } = {}) => {
|
||||||
if (isProd) {
|
if (isProd) {
|
||||||
// return correct data
|
// return correct data
|
||||||
}
|
}
|
||||||
return `<Asutus tyyp="TEOSTAJA">
|
return `<Asutus tyyp="TEOSTAJA" ${index ? ` jarjenumber="${index}"` : ''}>
|
||||||
<AsutuseId>11107913</AsutuseId>
|
<AsutuseId>11107913</AsutuseId>
|
||||||
<AsutuseNimi>Synlab HTI Tallinn</AsutuseNimi>
|
<AsutuseNimi>Synlab HTI Tallinn</AsutuseNimi>
|
||||||
<AsutuseKood>SLA</AsutuseKood>
|
<AsutuseKood>SLA</AsutuseKood>
|
||||||
@@ -68,7 +69,7 @@ export const getClientPerson = ({
|
|||||||
<PersonalKood>${idCode}</PersonalKood>
|
<PersonalKood>${idCode}</PersonalKood>
|
||||||
<PersonalPerekonnaNimi>${lastName}</PersonalPerekonnaNimi>
|
<PersonalPerekonnaNimi>${lastName}</PersonalPerekonnaNimi>
|
||||||
<PersonalEesNimi>${firstName}</PersonalEesNimi>
|
<PersonalEesNimi>${firstName}</PersonalEesNimi>
|
||||||
<Telefon>${phone}</Telefon>
|
${phone ? `<Telefon>${phone.startsWith('+372') ? phone : `+372${phone}`}</Telefon>` : ''}
|
||||||
</Personal>`;
|
</Personal>`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user