feat(MED-161): move medipost services to medipost package

This commit is contained in:
2025-09-17 11:15:26 +03:00
parent 0d08592a9a
commit 000cad7f05
12 changed files with 15 additions and 15 deletions

View File

@@ -0,0 +1,85 @@
export interface IUuringElement {
UuringIdOID: string;
UuringId: string;
TLyhend: string;
KNimetus: string;
UuringNimi: string;
Jarjekord: number;
Kood: {
HkKood: string;
HkKoodiKordaja: number;
Koefitsient: number;
Hind: number;
}[];
UuringuElement: {
UuringIdOID: string;
UuringId: string;
TLyhend: string;
KNimetus: string;
UuringNimi: string;
Jarjekord: number;
Kood: {
HkKood: string;
HkKoodiKordaja: number;
Koefitsient: number;
Hind: number;
}[];
}[];
}
export interface IMaterialGroup {
id: string;
name: string;
order: number;
}
export interface IMedipostPublicMessageDataParsed {
ANSWER: {
CODE: number;
MESSAGE: string;
};
Saadetis: {
Teenused: {
Teostaja: {
UuringuGrupp: {
UuringuGruppId: string;
UuringuGruppNimi: string;
UuringuGruppJarjekord: number;
Kood: {
HkKood: string;
HkKoodiKordaja: number;
Koefitsient: number;
Hind: number;
}[];
Uuring: {
UuringId: string;
UuringNimi: string;
UuringJarjekord: number;
UuringuElement: {
UuringIdOID: string;
UuringId: string;
TLyhend: string;
KNimetus: string;
UuringNimi: string;
Jarjekord: number;
Kood: {
HkKood: string;
HkKoodiKordaja: number;
Koefitsient: number;
Hind: number;
}[];
UuringuElement: IUuringElement;
}[];
MaterjalideGrupp: IMaterialGroup[];
Kood: {
HkKood: string;
HkKoodiKordaja: number;
Koefitsient: number;
Hind: number;
}[];
}[];
}[];
}[];
};
};
}

View File

@@ -18,7 +18,7 @@ import { createMedipostActionLog, getLatestMessage } from './medipostMessageBase
import { validateMedipostResponse } from './medipostValidate.service';
import { getAnalysisOrder, updateAnalysisOrderStatus } from '../order.service';
import { parseXML } from '../util/xml.service';
import { composeOrderXML, OrderedAnalysisElement } from '../medipostXML.service';
import { composeOrderXML, OrderedAnalysisElement } from './medipostXML.service';
import { getAccountAdmin } from '../account.service';
import { logMedipostDispatch } from '../audit.service';
import { MedipostValidationError } from './MedipostValidationError';

View File

@@ -0,0 +1,191 @@
'use server';
import {
getClientInstitution,
getClientPerson,
getOrderEnteredPerson,
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 { getAnalysisElementsAdmin } from '../analysis-element.service';
import { validateMedipostResponse } from './medipostValidate.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: number;
orderCreatedAt: Date;
}) {
const analysisElements = await getAnalysisElementsAdmin({ 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 = orderId;
const allAnalysisElementsForGroups = analysisElements?.filter((element) => {
return analysisGroups.some((group) => group.id === element.analysis_groups.id);
});
const addedIds = new Set<number>();
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, orderId, "AL")}
<Vastus>
<ValisTellimuseId>${orderId}</ValisTellimuseId>
${getClientInstitution({ index: 1 })}
${getProviderInstitution({ index: 1 })}
${getClientPerson()}
${getOrderEnteredPerson()}
<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>
${allAnalysisElementsForGroups.map((analysisElement) => {
const group = analysisGroups.find((group) => group.id === analysisElement.analysis_groups.id);
if (!group) {
throw new Error(`Failed to find group for analysis element ${analysisElement.id}`);
}
let relatedAnalysisElement = analysisElements?.find(
(element) => element.analysis_groups.id === group.id && !addedIds.has(element.id),
);
const relatedAnalyses = analyses?.filter((analysis) => {
return analysis.analysis_elements.analysis_groups.id === group.id && !addedIds.has(analysis.analysis_elements.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 - Math.floor(lower * 0.1), upper + Math.floor(upper * 0.1));
addedIds.add(relatedAnalysisElement.id);
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>${result < lower ? 1 : (result > upper ? 1 : 0)}</NormiStaatus>
<ProoviJarjenumber>1</ProoviJarjenumber>
</UuringuVastus>
</UuringuElement>
<UuringuTaitjaAsutuseJnr>2</UuringuTaitjaAsutuseJnr>
</Uuring>
</UuringuGrupp>
`);
}).join('')}
</Vastus>
</Saadetis>`;
}

View File

@@ -0,0 +1,194 @@
'use server';
import {
getAnalysisGroup,
getClientInstitution,
getClientPerson,
getConfidentiality,
getOrderEnteredPerson,
getPais,
getPatient,
getProviderInstitution,
getSpecimen,
} from '@/lib/templates/medipost-order';
import {
MaterjalideGrupp,
} from '@/lib/types/medipost';
import { toArray } from '@/lib/utils';
import { uniqBy } from 'lodash';
import { Tables } from '@kit/supabase/database';
import { AnalysisElement } from '../analysis-element.service';
import { AnalysesWithGroupsAndElements } from '../analyses.service';
const USER = process.env.MEDIPOST_USER!;
const RECIPIENT = process.env.MEDIPOST_RECIPIENT!;
export type OrderedAnalysisElement = {
analysisElementId?: number;
analysisId?: number;
}
export async function composeOrderXML({
analyses,
analysisElements,
person,
orderId,
orderCreatedAt,
comment,
}: {
analyses: AnalysesWithGroupsAndElements;
analysisElements: AnalysisElement[];
person: {
idCode: string;
firstName: string;
lastName: string;
phone: string;
};
orderId: number;
orderCreatedAt: Date;
comment?: string;
}) {
const analysisGroups: Tables<{ schema: 'medreport' }, 'analysis_groups'>[] =
uniqBy(
(
analysisElements?.flatMap(({ analysis_groups }) => analysis_groups) ??
[]
).concat(
analyses?.flatMap(
({ analysis_elements }) => analysis_elements.analysis_groups,
) ?? [],
),
'id',
);
// First, collect all unique materials across all analysis groups
const uniqueMaterials = new Map<string, {
MaterjaliTyypOID: string;
MaterjaliTyyp: string;
MaterjaliNimi: string;
ProovinouKoodOID?: string;
ProovinouKood?: string;
order: number;
}>();
let specimenOrder = 1;
// Collect all materials from all analysis groups
for (const currentGroup of analysisGroups) {
let relatedAnalysisElements = analysisElements?.filter(({ analysis_groups }) => analysis_groups.id === currentGroup.id);
if (!relatedAnalysisElements || relatedAnalysisElements.length === 0) {
relatedAnalysisElements = analyses
.filter(({ analysis_elements }) => analysis_elements.analysis_groups.id === currentGroup.id)
.flatMap(({ analysis_elements }) => analysis_elements);
}
if (!relatedAnalysisElements || relatedAnalysisElements.length === 0) {
throw new Error(
`Failed to find related analysis elements for group ${currentGroup.name} (id: ${currentGroup.id})`,
);
}
for (const analysisElement of relatedAnalysisElements) {
for (const { Materjal } of analysisElement.material_groups as MaterjalideGrupp[]) {
for (const material of toArray(Materjal)) {
const { MaterjaliTyyp } = material;
for (const container of toArray(material.Konteiner)) {
if (uniqueMaterials.has(MaterjaliTyyp)) {
continue;
}
uniqueMaterials.set(MaterjaliTyyp, {
MaterjaliTyypOID: material.MaterjaliTyypOID,
MaterjaliTyyp: material.MaterjaliTyyp,
MaterjaliNimi: material.MaterjaliNimi,
ProovinouKoodOID: container.ProovinouKoodOID,
ProovinouKood: container.ProovinouKood,
order: specimenOrder++,
});
}
}
}
}
}
// Generate specimen section from unique materials
const specimenSection = Array.from(uniqueMaterials.values()).map(material =>
getSpecimen(
material.MaterjaliTyypOID,
material.MaterjaliTyyp,
material.MaterjaliNimi,
material.order,
material.ProovinouKoodOID,
material.ProovinouKood,
)
);
// Generate analysis section with correct specimen references
const analysisSection = [];
for (const currentGroup of analysisGroups) {
let relatedAnalysisElements = analysisElements?.filter(
(element) => element.analysis_groups.id === currentGroup.id,
);
if (!relatedAnalysisElements) {
relatedAnalysisElements = analyses
.filter(({ analysis_elements }) => analysis_elements.analysis_groups.id === currentGroup.id)
.flatMap(({ analysis_elements }) => analysis_elements);
}
if (!relatedAnalysisElements || relatedAnalysisElements.length === 0) {
throw new Error(
`Failed to find related analysis element for group ${currentGroup.name} (id: ${currentGroup.id})`,
);
}
// Find the specimen order numbers for analysis elements
const uuringElementInputs: {
analysisElement: Tables<{ schema: 'medreport' }, 'analysis_elements'>,
specimenOrderNr: number,
}[] = [];
for (const analysisElement of relatedAnalysisElements) {
for (const group of analysisElement.material_groups as MaterjalideGrupp[]) {
const materials = toArray(group.Materjal);
for (const material of materials) {
const uniqueMaterial = uniqueMaterials.get(material.MaterjaliTyyp);
if (!uniqueMaterial) {
continue;
}
uuringElementInputs.push({
analysisElement,
specimenOrderNr: uniqueMaterial.order,
});
break;
}
}
}
const groupXml = getAnalysisGroup(
currentGroup.original_id,
currentGroup.name,
uuringElementInputs,
);
analysisSection.push(groupXml);
}
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, orderId)}
<Tellimus cito="EI">
<ValisTellimuseId>${orderId}</ValisTellimuseId>
${getClientInstitution()}
${getProviderInstitution()}
${getClientPerson()}
${getOrderEnteredPerson()}
<TellijaMarkused>${comment ?? ''}</TellijaMarkused>
${getPatient(person)}
${getConfidentiality()}
${specimenSection.join('')}
${analysisSection?.join('')}
</Tellimus>
</Saadetis>`;
}