B2B-85: add order xml generation (#8)
* B2B-85: add order xml generation * some fixes after merge --------- Co-authored-by: Helena <helena@Helenas-MacBook-Pro.local>
This commit is contained in:
@@ -4,6 +4,7 @@ NEXT_PUBLIC_SUPABASE_URL=your-project-url
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
|
||||
|
||||
MEDIPOST_URL=your-medpost-url
|
||||
MEDIPOST_USER=your-medpost-user
|
||||
MEDIPOST_PASSWORD=your-medpost-password
|
||||
MEDIPOST_URL=your-medipost-url
|
||||
MEDIPOST_USER=your-medipost-user
|
||||
MEDIPOST_PASSWORD=your-medipost-password
|
||||
MEDIPOST_RECIPIENT=your-medipost-recipient
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { MedReportTitle } from "@/components/MedReportTitle";
|
||||
import { MedReportTitle } from "@/components/med-report-title";
|
||||
import React from "react";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { MedReportTitle } from "@/components/MedReportTitle";
|
||||
import { Button } from "@/packages/ui/src/shadcn/button";
|
||||
import { MedReportTitle } from "@/components/med-report-title";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { signOutAction } from "@/lib/actions/sign-out";
|
||||
import { hasEnvVars } from "@/utils/supabase/check-env-vars";
|
||||
import Link from "next/link";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { Button } from "./ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
|
||||
export default async function AuthButton() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MedReportSmallLogo } from "@/public/assets/MedReportSmallLogo";
|
||||
import { MedReportSmallLogo } from "@/public/assets/med-report-small-logo";
|
||||
|
||||
export const MedReportTitle = () => (
|
||||
<div className="flex gap-2 justify-center">
|
||||
2
lib/constants.ts
Normal file
2
lib/constants.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
export const DATE_FORMAT = "yyyy-mm-dd";
|
||||
@@ -1,16 +1,34 @@
|
||||
import {
|
||||
SupabaseClient,
|
||||
createClient as createCustomClient,
|
||||
} from '@supabase/supabase-js';
|
||||
|
||||
import {
|
||||
getAnalysisGroup,
|
||||
getClientInstitution,
|
||||
getClientPerson,
|
||||
getConfidentiality,
|
||||
getOrderEnteredByPerson,
|
||||
getPais,
|
||||
getPatient,
|
||||
getProviderInstitution,
|
||||
getSpecimen,
|
||||
} from '@/lib/templates/medipost-order';
|
||||
import { SyncStatus } from '@/lib/types/audit';
|
||||
import {
|
||||
AnalysisOrderStatus,
|
||||
GetMessageListResponse,
|
||||
MaterjalideGrupp,
|
||||
MedipostAction,
|
||||
MedipostPublicMessageResponse,
|
||||
Message,
|
||||
UuringuGrupp,
|
||||
} from "@/lib/types/medipost";
|
||||
import { Tables } from "@/supabase/database.types";
|
||||
import { createClient, SupabaseClient } from "@supabase/supabase-js";
|
||||
import axios from "axios";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import { SyncStatus } from "@/lib/types/audit";
|
||||
import { toArray } from "@/lib/utils";
|
||||
} from '@/lib/types/medipost';
|
||||
import { toArray } from '@/lib/utils';
|
||||
import { Tables } from '@/supabase/database.types';
|
||||
import axios from 'axios';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import { uniqBy } from 'lodash';
|
||||
|
||||
const BASE_URL = process.env.MEDIPOST_URL!;
|
||||
const USER = process.env.MEDIPOST_USER!;
|
||||
@@ -37,14 +55,14 @@ export async function getLatestPublicMessageListItem() {
|
||||
Action: MedipostAction.GetPublicMessageList,
|
||||
User: USER,
|
||||
Password: PASSWORD,
|
||||
Sender: "syndev",
|
||||
Sender: 'syndev',
|
||||
// LastChecked (date+time) can be used here to get only messages since the last check - add when cron is created
|
||||
// MessageType check only for messages of certain type
|
||||
},
|
||||
});
|
||||
|
||||
if (data.code && data.code !== 0) {
|
||||
throw new Error("Failed to get public message list");
|
||||
throw new Error('Failed to get public message list');
|
||||
}
|
||||
|
||||
return getLatestMessage(data?.messages);
|
||||
@@ -59,7 +77,7 @@ export async function getPublicMessage(messageId: string) {
|
||||
MessageId: messageId,
|
||||
},
|
||||
headers: {
|
||||
Accept: "application/xml",
|
||||
Accept: 'application/xml',
|
||||
},
|
||||
});
|
||||
const parser = new XMLParser({ ignoreAttributes: false });
|
||||
@@ -74,16 +92,16 @@ export async function getPublicMessage(messageId: string) {
|
||||
|
||||
export async function sendPrivateMessage(messageXml: string, receiver: string) {
|
||||
const body = new FormData();
|
||||
body.append("Action", MedipostAction.SendPrivateMessage);
|
||||
body.append("User", USER);
|
||||
body.append("Password", PASSWORD);
|
||||
body.append("Receiver", receiver);
|
||||
body.append("MessageType", "Tellimus");
|
||||
body.append('Action', MedipostAction.SendPrivateMessage);
|
||||
body.append('User', USER);
|
||||
body.append('Password', PASSWORD);
|
||||
body.append('Receiver', receiver);
|
||||
body.append('MessageType', 'Tellimus');
|
||||
body.append(
|
||||
"Message",
|
||||
'Message',
|
||||
new Blob([messageXml], {
|
||||
type: "text/xml; charset=UTF-8",
|
||||
})
|
||||
type: 'text/xml; charset=UTF-8',
|
||||
}),
|
||||
);
|
||||
|
||||
const { data } = await axios.post(BASE_URL, body);
|
||||
@@ -103,7 +121,7 @@ export async function getLatestPrivateMessageListItem() {
|
||||
});
|
||||
|
||||
if (data.code && data.code !== 0) {
|
||||
throw new Error("Failed to get private message list");
|
||||
throw new Error('Failed to get private message list');
|
||||
}
|
||||
|
||||
return getLatestMessage(data?.messages);
|
||||
@@ -118,7 +136,7 @@ export async function getPrivateMessage(messageId: string) {
|
||||
MessageId: messageId,
|
||||
},
|
||||
headers: {
|
||||
Accept: "application/xml",
|
||||
Accept: 'application/xml',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -154,7 +172,7 @@ export async function readPrivateMessageResponse() {
|
||||
}
|
||||
|
||||
const privateMessageContent = await getPrivateMessage(
|
||||
privateMessage.messageId
|
||||
privateMessage.messageId,
|
||||
);
|
||||
|
||||
if (privateMessageContent) {
|
||||
@@ -168,29 +186,29 @@ export async function readPrivateMessageResponse() {
|
||||
|
||||
async function saveAnalysisGroup(
|
||||
analysisGroup: UuringuGrupp,
|
||||
supabase: SupabaseClient
|
||||
supabase: SupabaseClient,
|
||||
) {
|
||||
const { data: insertedAnalysisGroup, error } = await supabase
|
||||
.from("analysis_groups")
|
||||
.from('analysis_groups')
|
||||
.upsert(
|
||||
{
|
||||
original_id: analysisGroup.UuringuGruppId,
|
||||
name: analysisGroup.UuringuGruppNimi,
|
||||
order: analysisGroup.UuringuGruppJarjekord,
|
||||
},
|
||||
{ onConflict: "original_id", ignoreDuplicates: false }
|
||||
{ onConflict: 'original_id', ignoreDuplicates: false },
|
||||
)
|
||||
.select("id");
|
||||
.select('id');
|
||||
|
||||
if (error || !insertedAnalysisGroup[0]?.id) {
|
||||
throw new Error(
|
||||
`Failed to insert analysis group (id: ${analysisGroup.UuringuGruppId}), error: ${error?.message}`
|
||||
`Failed to insert analysis group (id: ${analysisGroup.UuringuGruppId}), error: ${error?.message}`,
|
||||
);
|
||||
}
|
||||
const analysisGroupId = insertedAnalysisGroup[0].id;
|
||||
|
||||
const analysisGroupCodes = toArray(analysisGroup.Kood);
|
||||
const codes: Partial<Tables<"codes">>[] = analysisGroupCodes.map((kood) => ({
|
||||
const codes: Partial<Tables<'codes'>>[] = analysisGroupCodes.map((kood) => ({
|
||||
hk_code: kood.HkKood,
|
||||
hk_code_multiplier: kood.HkKoodiKordaja,
|
||||
coefficient: kood.Koefitsient,
|
||||
@@ -204,7 +222,7 @@ async function saveAnalysisGroup(
|
||||
const analysisElement = item.UuringuElement;
|
||||
|
||||
const { data: insertedAnalysisElement, error } = await supabase
|
||||
.from("analysis_elements")
|
||||
.from('analysis_elements')
|
||||
.upsert(
|
||||
{
|
||||
analysis_id_oid: analysisElement.UuringIdOID,
|
||||
@@ -216,13 +234,13 @@ async function saveAnalysisGroup(
|
||||
parent_analysis_group_id: analysisGroupId,
|
||||
material_groups: toArray(item.MaterjalideGrupp),
|
||||
},
|
||||
{ onConflict: "analysis_id_original", ignoreDuplicates: false }
|
||||
{ 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}`
|
||||
`Failed to insert analysis element (id: ${analysisElement.UuringId}), error: ${error?.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -237,7 +255,7 @@ async function saveAnalysisGroup(
|
||||
coefficient: kood.Koefitsient,
|
||||
price: kood.Hind,
|
||||
analysis_element_id: insertedAnalysisElementId,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,7 +263,7 @@ async function saveAnalysisGroup(
|
||||
if (analyses?.length) {
|
||||
for (const analysis of analyses) {
|
||||
const { data: insertedAnalysis, error } = await supabase
|
||||
.from("analyses")
|
||||
.from('analyses')
|
||||
.upsert(
|
||||
{
|
||||
analysis_id_oid: analysis.UuringIdOID,
|
||||
@@ -256,13 +274,13 @@ async function saveAnalysisGroup(
|
||||
order: analysis.Jarjekord,
|
||||
parent_analysis_element_id: insertedAnalysisElementId,
|
||||
},
|
||||
{ onConflict: "analysis_id_original", ignoreDuplicates: false }
|
||||
{ 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}`
|
||||
`Failed to insert analysis (id: ${analysis.UuringId}) error: ${error?.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,7 +295,7 @@ async function saveAnalysisGroup(
|
||||
coefficient: kood.Koefitsient,
|
||||
price: kood.Hind,
|
||||
analysis_id: insertedAnalysisId,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -285,20 +303,21 @@ async function saveAnalysisGroup(
|
||||
}
|
||||
|
||||
const { error: codesError } = await supabase
|
||||
.from("codes")
|
||||
.from('codes')
|
||||
.upsert(codes, { ignoreDuplicates: false });
|
||||
|
||||
if (codesError?.code) {
|
||||
console.error(codesError); // TODO remove this
|
||||
throw new Error(
|
||||
`Failed to insert codes (analysis group id: ${analysisGroup.UuringuGruppId})`
|
||||
`Failed to insert codes (analysis group id: ${analysisGroup.UuringuGruppId})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function syncPublicMessage(
|
||||
message?: MedipostPublicMessageResponse | null
|
||||
message?: MedipostPublicMessageResponse | null,
|
||||
) {
|
||||
const supabase = createClient(
|
||||
const supabase = createCustomClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!,
|
||||
{
|
||||
@@ -307,20 +326,20 @@ export async function syncPublicMessage(
|
||||
autoRefreshToken: false,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const providers = toArray(message?.Saadetis?.Teenused.Teostaja);
|
||||
const analysisGroups = providers.flatMap((provider) =>
|
||||
toArray(provider.UuringuGrupp)
|
||||
toArray(provider.UuringuGrupp),
|
||||
);
|
||||
if (!message || !analysisGroups.length) {
|
||||
return supabase.schema("audit").from("sync_entries").insert({
|
||||
operation: "ANALYSES_SYNC",
|
||||
comment: "No data received",
|
||||
return supabase.schema('audit').from('sync_entries').insert({
|
||||
operation: 'ANALYSES_SYNC',
|
||||
comment: 'No data received',
|
||||
status: SyncStatus.Fail,
|
||||
changed_by_role: "service_role",
|
||||
changed_by_role: 'service_role',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -328,31 +347,170 @@ export async function syncPublicMessage(
|
||||
await saveAnalysisGroup(analysisGroup, supabase);
|
||||
}
|
||||
|
||||
await supabase.schema("audit").from("sync_entries").insert({
|
||||
operation: "ANALYSES_SYNC",
|
||||
await supabase.schema('audit').from('sync_entries').insert({
|
||||
operation: 'ANALYSES_SYNC',
|
||||
status: SyncStatus.Success,
|
||||
changed_by_role: "service_role",
|
||||
changed_by_role: 'service_role',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
await supabase
|
||||
.schema("audit")
|
||||
.from("sync_entries")
|
||||
.schema('audit')
|
||||
.from('sync_entries')
|
||||
.insert({
|
||||
operation: "ANALYSES_SYNC",
|
||||
operation: 'ANALYSES_SYNC',
|
||||
status: SyncStatus.Fail,
|
||||
comment: JSON.stringify(e),
|
||||
changed_by_role: "service_role",
|
||||
changed_by_role: 'service_role',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO use actual parameters
|
||||
export async function composeOrderXML(
|
||||
/* chosenAnalysisElements?: number[],
|
||||
chosenAnalyses?: number[], */
|
||||
comment?: string,
|
||||
) {
|
||||
const supabase = createCustomClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!,
|
||||
{
|
||||
auth: {
|
||||
persistSession: false,
|
||||
autoRefreshToken: false,
|
||||
detectSessionInUrl: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// TODO remove dummy when actual implemetation is present
|
||||
const orderedElements = [1, 75];
|
||||
const orderedAnalyses = [10, 11, 100];
|
||||
|
||||
const createdAnalysisOrder = {
|
||||
id: 4,
|
||||
user_id: 'currentUser.user?.id',
|
||||
analysis_element_ids: orderedElements,
|
||||
analysis_ids: orderedAnalyses,
|
||||
status: AnalysisOrderStatus[1],
|
||||
created_at: new Date(),
|
||||
};
|
||||
|
||||
const { data: analysisElements } = (await supabase
|
||||
.from('analysis_elements')
|
||||
.select(`*, analysis_groups(*)`)
|
||||
.in('id', orderedElements)) as {
|
||||
data: ({
|
||||
analysis_groups: Tables<'analysis_groups'>;
|
||||
} & Tables<'analysis_elements'>)[];
|
||||
};
|
||||
const { data: analyses } = (await supabase
|
||||
.from('analyses')
|
||||
.select(`*, analysis_elements(*, analysis_groups(*))`)
|
||||
.in('id', orderedAnalyses)) as {
|
||||
data: ({
|
||||
analysis_elements: Tables<'analysis_elements'> & {
|
||||
analysis_groups: Tables<'analysis_groups'>;
|
||||
};
|
||||
} & Tables<'analyses'>)[];
|
||||
};
|
||||
|
||||
const analysisGroups: Tables<'analysis_groups'>[] = uniqBy(
|
||||
(
|
||||
analysisElements?.flatMap(({ analysis_groups }) => analysis_groups) ?? []
|
||||
).concat(
|
||||
analyses?.flatMap(
|
||||
({ analysis_elements }) => analysis_elements.analysis_groups,
|
||||
) ?? [],
|
||||
),
|
||||
'id',
|
||||
);
|
||||
|
||||
const specimenSection = [];
|
||||
const analysisSection = [];
|
||||
let order = 1;
|
||||
for (const currentGroup of analysisGroups) {
|
||||
let relatedAnalysisElement = analysisElements?.find(
|
||||
(element) => element.analysis_groups.id === currentGroup.id,
|
||||
);
|
||||
const relatedAnalyses = analyses?.filter((analysis) => {
|
||||
return analysis.analysis_elements.analysis_groups.id === currentGroup.id;
|
||||
});
|
||||
|
||||
if (!relatedAnalysisElement) {
|
||||
relatedAnalysisElement = relatedAnalyses?.find(
|
||||
(relatedAnalysis) =>
|
||||
relatedAnalysis.analysis_elements.analysis_groups.id ===
|
||||
currentGroup.id,
|
||||
)?.analysis_elements;
|
||||
}
|
||||
|
||||
if (!relatedAnalysisElement || !relatedAnalysisElement.material_groups) {
|
||||
throw new Error(
|
||||
`Failed to find related analysis element for group ${currentGroup.name} (id: ${currentGroup.id})`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const group of relatedAnalysisElement?.material_groups as MaterjalideGrupp[]) {
|
||||
const materials = toArray(group.Materjal);
|
||||
const specimenXml = materials.flatMap(
|
||||
({ MaterjaliNimi, MaterjaliTyyp, MaterjaliTyypOID, Konteiner }) => {
|
||||
return toArray(Konteiner).map((container) =>
|
||||
getSpecimen(
|
||||
MaterjaliTyypOID,
|
||||
MaterjaliTyyp,
|
||||
MaterjaliNimi,
|
||||
order,
|
||||
container.ProovinouKoodOID,
|
||||
container.ProovinouKood,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
specimenSection.push(...specimenXml);
|
||||
}
|
||||
|
||||
const groupXml = getAnalysisGroup(
|
||||
currentGroup.original_id,
|
||||
currentGroup.name,
|
||||
order,
|
||||
relatedAnalysisElement,
|
||||
);
|
||||
order++;
|
||||
analysisSection.push(groupXml);
|
||||
}
|
||||
|
||||
// TODO get actual data when order creation is implemented
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Saadetis xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="TellimusLOINC.xsd">
|
||||
${getPais(process.env.MEDIPOST_USER!, process.env.MEDIPOST_RECIPIENT!, createdAnalysisOrder.created_at, createdAnalysisOrder.id)}
|
||||
<Tellimus cito="EI">
|
||||
<ValisTellimuseId>${createdAnalysisOrder.id}</ValisTellimuseId>
|
||||
<!--<TellijaAsutus>-->
|
||||
${getClientInstitution()}
|
||||
<!--<TeostajaAsutus>-->
|
||||
${getProviderInstitution()}
|
||||
<!--<TellijaIsik>-->
|
||||
${getClientPerson()}
|
||||
<!--<SisestajaIsik>-->
|
||||
${getOrderEnteredByPerson()}
|
||||
<TellijaMarkused>${comment ?? ''}</TellijaMarkused>
|
||||
${getPatient(49610230861, 'Surname', 'First Name', '1996-10-23', 'N')}
|
||||
${getConfidentiality()}
|
||||
${specimenSection.join('')}
|
||||
${analysisSection?.join('')}
|
||||
</Tellimus>
|
||||
</Saadetis>`;
|
||||
}
|
||||
|
||||
function getLatestMessage(messages?: Message[]) {
|
||||
if (!messages?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return messages.reduce((prev, current) =>
|
||||
Number(prev.messageId) > Number(current.messageId) ? prev : current
|
||||
Number(prev.messageId) > Number(current.messageId) ? prev : current,
|
||||
);
|
||||
}
|
||||
|
||||
172
lib/templates/medipost-order.ts
Normal file
172
lib/templates/medipost-order.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { DATE_TIME_FORMAT } from '@/lib/constants';
|
||||
import { Tables } from '@/supabase/database.types';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
export const getPais = (
|
||||
sender: string,
|
||||
recipient: string,
|
||||
createdAt: Date,
|
||||
messageId: number,
|
||||
) => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `<Pais>
|
||||
<Pakett versioon="20">OL</Pakett>
|
||||
<Saatja>${sender}</Saatja>
|
||||
<Saaja>${recipient}</Saaja>
|
||||
<Aeg>${format(createdAt, DATE_TIME_FORMAT)}</Aeg>
|
||||
<SaadetisId>${messageId}</SaadetisId>
|
||||
<Email>argo@medreport.ee</Email>
|
||||
</Pais>`;
|
||||
};
|
||||
|
||||
export const getClientInstitution = () => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `<Asutus tyyp="TELLIJA">
|
||||
<AsutuseId>16381793</AsutuseId>
|
||||
<AsutuseNimi>MedReport OÜ</AsutuseNimi>
|
||||
<AsutuseKood>TSU</AsutuseKood>
|
||||
<Telefon>+37258871517</Telefon>
|
||||
</Asutus>`;
|
||||
};
|
||||
|
||||
export const getProviderInstitution = () => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `<Asutus tyyp="TEOSTAJA">
|
||||
<AsutuseId>11107913</AsutuseId>
|
||||
<AsutuseNimi>Synlab HTI Tallinn</AsutuseNimi>
|
||||
<AsutuseKood>SLA</AsutuseKood>
|
||||
<AllyksuseNimi>Synlab HTI Tallinn</AllyksuseNimi>
|
||||
<Telefon>+3723417123</Telefon>
|
||||
</Asutus>`;
|
||||
};
|
||||
|
||||
export const getClientPerson = () => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `<Personal tyyp="TELLIJA" jarjenumber="1">
|
||||
<PersonalOID>1.3.6.1.4.1.28284.6.2.4.9</PersonalOID>
|
||||
<PersonalKood>D07907</PersonalKood>
|
||||
<PersonalPerekonnaNimi>Eduard</PersonalPerekonnaNimi>
|
||||
<PersonalEesNimi>Tsvetkov</PersonalEesNimi>
|
||||
<Telefon>+37258131202</Telefon>
|
||||
</Personal>`;
|
||||
};
|
||||
|
||||
export const getOrderEnteredPerson = () => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `<Personal tyyp="SISESTAJA" jarjenumber="1">
|
||||
<PersonalOID>1.3.6.1.4.1.28284.6.2.4.9</PersonalOID>
|
||||
<PersonalKood>D07907</PersonalKood>
|
||||
<PersonalPerekonnaNimi>Eduard</PersonalPerekonnaNimi>
|
||||
<PersonalEesNimi>Tsvetkov</PersonalEesNimi>
|
||||
<Telefon>+37258131202</Telefon>
|
||||
</Personal>`;
|
||||
};
|
||||
|
||||
export const getPatient = (
|
||||
idCode: number,
|
||||
surname: string,
|
||||
firstName: string,
|
||||
birthDate: string,
|
||||
genderLetter: string,
|
||||
) => {
|
||||
return `<Patsient>
|
||||
<IsikukoodiOID>1.3.6.1.4.1.28284.6.2.2.1</IsikukoodiOID>
|
||||
<Isikukood>${idCode}</Isikukood>
|
||||
<PerekonnaNimi>${surname}</PerekonnaNimi>
|
||||
<EesNimi>${firstName}</EesNimi>
|
||||
<SynniAeg>${birthDate}</SynniAeg>
|
||||
<SuguOID>1.3.6.1.4.1.28284.6.2.3.16.2</SuguOID>
|
||||
<Sugu>${genderLetter}</Sugu>
|
||||
</Patsient>`;
|
||||
};
|
||||
|
||||
export const getConfidentiality = () => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `<Konfidentsiaalsus>
|
||||
<PatsiendileOID>2.16.840.1.113883.5.25</PatsiendileOID>
|
||||
<Patsiendile>N</Patsiendile>
|
||||
<ArstileOID>1.3.6.1.4.1.28284.6.2.2.39.1</ArstileOID>
|
||||
<Arstile>N</Arstile>
|
||||
<EsindajaleOID>1.3.6.1.4.1.28284.6.2.2.37.1</EsindajaleOID>
|
||||
<Esindajale>N</Esindajale>
|
||||
</Konfidentsiaalsus>`;
|
||||
};
|
||||
|
||||
export const getOrderEnteredByPerson = () => {
|
||||
if (isProd) {
|
||||
// return correct data
|
||||
}
|
||||
return `
|
||||
<Personal tyyp="SISESTAJA" jarjenumber="1">
|
||||
<PersonalOID>1.3.6.1.4.1.28284.6.2.4.9</PersonalOID>
|
||||
<PersonalKood>D07907</PersonalKood>
|
||||
<PersonalPerekonnaNimi>Eduard</PersonalPerekonnaNimi>
|
||||
<PersonalEesNimi>Tsvetkov</PersonalEesNimi>
|
||||
<Telefon>+37258131202</Telefon>
|
||||
</Personal>`;
|
||||
};
|
||||
|
||||
export const getSpecimen = (
|
||||
materialTypeOid: string,
|
||||
materialGroupId: string,
|
||||
materialName: string,
|
||||
order: number,
|
||||
sampleContainerOid?: string,
|
||||
sampleContainerId?: string,
|
||||
) =>
|
||||
`
|
||||
<Proov>
|
||||
${sampleContainerOid ? `<ProovinouIdOID>${sampleContainerOid}</ProovinouIdOID>` : null}
|
||||
${sampleContainerId ? `<ProovinouId>${sampleContainerId}</ProovinouId>` : null}
|
||||
<MaterjaliTyypOID>${materialTypeOid}</MaterjaliTyypOID>
|
||||
<MaterjaliTyyp>${materialGroupId}</MaterjaliTyyp>
|
||||
<MaterjaliNimi>${materialName}</MaterjaliNimi>
|
||||
<Jarjenumber>${order}</Jarjenumber>
|
||||
</Proov>`;
|
||||
|
||||
export const getAnalysisElement = (
|
||||
analysisIdOid: string,
|
||||
analysisIdOriginal: string,
|
||||
tehikShortLoinc: string,
|
||||
tehikLoincName: string,
|
||||
analysisElementId: number,
|
||||
analysisNameLab?: string | null,
|
||||
) => {
|
||||
return `<UuringuElement>
|
||||
<UuringIdOID>${analysisIdOid}</UuringIdOID>
|
||||
<UuringId>${analysisIdOriginal}</UuringId>
|
||||
<TLyhend>${tehikShortLoinc}</TLyhend>
|
||||
<KNimetus>${tehikLoincName}</KNimetus>
|
||||
<UuringNimi>${analysisNameLab ?? tehikLoincName}</UuringNimi>
|
||||
<TellijaUuringId>${analysisElementId}</TellijaUuringId>
|
||||
</UuringuElement>`;
|
||||
};
|
||||
|
||||
export const getAnalysisGroup = (
|
||||
analysisGroupOriginalId: string,
|
||||
analysisGroupName: string,
|
||||
specimenOrderNr: number,
|
||||
analysisElement: Tables<'analysis_elements'>,
|
||||
) =>
|
||||
`<UuringuGrupp>
|
||||
<UuringuGruppId>${analysisGroupOriginalId}</UuringuGruppId>
|
||||
<UuringuGruppNimi>${analysisGroupName}</UuringuGruppNimi>
|
||||
<Uuring>
|
||||
${getAnalysisElement(analysisElement.analysis_id_oid, analysisElement.analysis_id_original, analysisElement.tehik_short_loinc, analysisElement.tehik_loinc_name, analysisElement.id, analysisElement.analysis_name_lab)}
|
||||
<ProoviJarjenumber>${specimenOrderNr}</ProoviJarjenumber>
|
||||
</Uuring>
|
||||
</UuringuGrupp>`;
|
||||
@@ -74,7 +74,7 @@ export type UuringuElement = {
|
||||
export type Uuring = {
|
||||
tellitav: "JAH" | "EI";
|
||||
UuringuElement: UuringuElement; //1..1
|
||||
MaterjalideGrupp?: MaterjalideGrupp[]; //0..n
|
||||
MaterjalideGrupp?: MaterjalideGrupp | MaterjalideGrupp[]; //0..n If this is not present, then the analysis can't be ordered.
|
||||
};
|
||||
|
||||
export type UuringuGrupp = {
|
||||
@@ -86,10 +86,10 @@ export type UuringuGrupp = {
|
||||
};
|
||||
|
||||
export type Konteiner = {
|
||||
ProovinouKoodOID: string;
|
||||
ProovinouKood: string;
|
||||
KonteineriNimi: string;
|
||||
KonteineriKirjeldus: string;
|
||||
ProovinouKoodOID?: string; //0..1
|
||||
ProovinouKood?: string; //0..1
|
||||
KonteineriNimi?: string; //0..1
|
||||
KonteineriKirjeldus: string; //1..1
|
||||
};
|
||||
|
||||
export type Materjal = {
|
||||
@@ -98,12 +98,13 @@ export type Materjal = {
|
||||
MaterjaliNimi: string;
|
||||
KonteineriOmadus: string;
|
||||
MaterjaliPaige: { Kohustuslik: "JAH" | "EI" }; //0..1
|
||||
Konteiner?: Konteiner[]; //0..n
|
||||
MaterjalJarjekord?: number; //0..1
|
||||
Konteiner?: Konteiner | Konteiner[]; //0..n
|
||||
};
|
||||
|
||||
export type MaterjalideGrupp = {
|
||||
vaikimisi: "JAH" | "EI";
|
||||
Materjal: Materjal; //1..n
|
||||
Materjal: Materjal | Materjal[]; //1..n
|
||||
};
|
||||
|
||||
export type Teostaja = {
|
||||
@@ -139,3 +140,12 @@ export type MedipostPublicMessageResponse = {
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export const AnalysisOrderStatus = {
|
||||
1: "QUEUED",
|
||||
2: "ON_HOLD",
|
||||
3: "PROCESSING",
|
||||
4: "COMPLETED",
|
||||
5: "REJECTED",
|
||||
6: "CANCELLED",
|
||||
} as const;
|
||||
|
||||
@@ -61,8 +61,9 @@
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"axios": "^1.9.0",
|
||||
"clsx": "^2.1.1",
|
||||
"fast-xml-parser": "^5.2.3",
|
||||
"date-fns": "^4.1.0",
|
||||
"fast-xml-parser": "^5.2.5",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.510.0",
|
||||
"next": "15.3.2",
|
||||
"next-sitemap": "^4.2.3",
|
||||
@@ -86,11 +87,12 @@
|
||||
"@types/node": "^22.15.18",
|
||||
"@types/react": "19.1.4",
|
||||
"@types/react-dom": "19.1.5",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"@types/lodash": "^4.17.17",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"cssnano": "^7.0.7",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"prettier": "^3.5.3",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"supabase": "^2.22.12",
|
||||
"tailwindcss": "4.1.7",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -102,8 +102,11 @@ importers:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
fast-xml-parser:
|
||||
specifier: ^5.2.3
|
||||
specifier: ^5.2.5
|
||||
version: 5.2.5
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
lucide-react:
|
||||
specifier: ^0.510.0
|
||||
version: 0.510.0(react@19.1.0)
|
||||
@@ -156,6 +159,9 @@ importers:
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4.1.7
|
||||
version: 4.1.8
|
||||
'@types/lodash':
|
||||
specifier: ^4.17.17
|
||||
version: 4.17.17
|
||||
'@types/node':
|
||||
specifier: ^22.15.18
|
||||
version: 22.15.30
|
||||
|
||||
@@ -50,24 +50,27 @@ export type Database = {
|
||||
}
|
||||
sync_entries: {
|
||||
Row: {
|
||||
changed_by_role: string | null
|
||||
changed_by_role: string
|
||||
comment: string | null
|
||||
created_at: string
|
||||
id: number
|
||||
operation: string | null
|
||||
operation: string
|
||||
status: string
|
||||
}
|
||||
Insert: {
|
||||
changed_by_role?: string | null
|
||||
changed_by_role: string
|
||||
comment?: string | null
|
||||
created_at?: string
|
||||
id?: number
|
||||
operation?: string | null
|
||||
operation: string
|
||||
status: string
|
||||
}
|
||||
Update: {
|
||||
changed_by_role?: string | null
|
||||
changed_by_role?: string
|
||||
comment?: string | null
|
||||
created_at?: string
|
||||
id?: number
|
||||
operation?: string | null
|
||||
operation?: string
|
||||
status?: string
|
||||
}
|
||||
Relationships: []
|
||||
@@ -224,7 +227,7 @@ export type Database = {
|
||||
analysis_name_lab: string | null
|
||||
created_at: string
|
||||
id: number
|
||||
order: number | null
|
||||
order: number
|
||||
parent_analysis_element_id: number
|
||||
tehik_loinc_name: string | null
|
||||
tehik_short_loinc: string | null
|
||||
@@ -236,7 +239,7 @@ export type Database = {
|
||||
analysis_name_lab?: string | null
|
||||
created_at?: string
|
||||
id?: number
|
||||
order?: number | null
|
||||
order: number
|
||||
parent_analysis_element_id: number
|
||||
tehik_loinc_name?: string | null
|
||||
tehik_short_loinc?: string | null
|
||||
@@ -248,7 +251,7 @@ export type Database = {
|
||||
analysis_name_lab?: string | null
|
||||
created_at?: string
|
||||
id?: number
|
||||
order?: number | null
|
||||
order?: number
|
||||
parent_analysis_element_id?: number
|
||||
tehik_loinc_name?: string | null
|
||||
tehik_short_loinc?: string | null
|
||||
@@ -266,42 +269,42 @@ export type Database = {
|
||||
}
|
||||
analysis_elements: {
|
||||
Row: {
|
||||
analysis_id_oid: string | null
|
||||
analysis_id_oid: string
|
||||
analysis_id_original: string
|
||||
analysis_name_lab: string | null
|
||||
created_at: string
|
||||
id: number
|
||||
material_groups: Json[] | null
|
||||
order: number | null
|
||||
order: number
|
||||
parent_analysis_group_id: number
|
||||
tehik_loinc_name: string | null
|
||||
tehik_short_loinc: string | null
|
||||
tehik_loinc_name: string
|
||||
tehik_short_loinc: string
|
||||
updated_at: string | null
|
||||
}
|
||||
Insert: {
|
||||
analysis_id_oid?: string | null
|
||||
analysis_id_oid: string
|
||||
analysis_id_original: string
|
||||
analysis_name_lab?: string | null
|
||||
created_at?: string
|
||||
id?: number
|
||||
material_groups?: Json[] | null
|
||||
order?: number | null
|
||||
order: number
|
||||
parent_analysis_group_id: number
|
||||
tehik_loinc_name?: string | null
|
||||
tehik_short_loinc?: string | null
|
||||
tehik_loinc_name: string
|
||||
tehik_short_loinc: string
|
||||
updated_at?: string | null
|
||||
}
|
||||
Update: {
|
||||
analysis_id_oid?: string | null
|
||||
analysis_id_oid?: string
|
||||
analysis_id_original?: string
|
||||
analysis_name_lab?: string | null
|
||||
created_at?: string
|
||||
id?: number
|
||||
material_groups?: Json[] | null
|
||||
order?: number | null
|
||||
order?: number
|
||||
parent_analysis_group_id?: number
|
||||
tehik_loinc_name?: string | null
|
||||
tehik_short_loinc?: string | null
|
||||
tehik_loinc_name?: string
|
||||
tehik_short_loinc?: string
|
||||
updated_at?: string | null
|
||||
}
|
||||
Relationships: [
|
||||
@@ -318,29 +321,53 @@ export type Database = {
|
||||
Row: {
|
||||
created_at: string
|
||||
id: number
|
||||
name: string | null
|
||||
order: number | null
|
||||
name: string
|
||||
order: number
|
||||
original_id: string
|
||||
updated_at: string | null
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string
|
||||
id?: number
|
||||
name?: string | null
|
||||
order?: number | null
|
||||
name: string
|
||||
order: number
|
||||
original_id: string
|
||||
updated_at?: string | null
|
||||
}
|
||||
Update: {
|
||||
created_at?: string
|
||||
id?: number
|
||||
name?: string | null
|
||||
order?: number | null
|
||||
name?: string
|
||||
order?: number
|
||||
original_id?: string
|
||||
updated_at?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
analysis_orders: {
|
||||
Row: {
|
||||
analysis_group_ids: number[] | null
|
||||
created_at: string
|
||||
id: string
|
||||
status: Database["public"]["Enums"]["analysis_order_status"]
|
||||
user_id: string
|
||||
}
|
||||
Insert: {
|
||||
analysis_group_ids?: number[] | null
|
||||
created_at?: string
|
||||
id: string
|
||||
status: Database["public"]["Enums"]["analysis_order_status"]
|
||||
user_id: string
|
||||
}
|
||||
Update: {
|
||||
analysis_group_ids?: number[] | null
|
||||
created_at?: string
|
||||
id?: string
|
||||
status?: Database["public"]["Enums"]["analysis_order_status"]
|
||||
user_id?: string
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
billing_customers: {
|
||||
Row: {
|
||||
account_id: string
|
||||
@@ -1111,6 +1138,13 @@ export type Database = {
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
analysis_order_status:
|
||||
| "QUEUED"
|
||||
| "ON_HOLD"
|
||||
| "PROCESSING"
|
||||
| "COMPLETED"
|
||||
| "REJECTED"
|
||||
| "CANCELLED"
|
||||
app_permissions:
|
||||
| "roles.manage"
|
||||
| "billing.manage"
|
||||
@@ -1257,6 +1291,14 @@ export const Constants = {
|
||||
},
|
||||
public: {
|
||||
Enums: {
|
||||
analysis_order_status: [
|
||||
"QUEUED",
|
||||
"ON_HOLD",
|
||||
"PROCESSING",
|
||||
"COMPLETED",
|
||||
"REJECTED",
|
||||
"CANCELLED",
|
||||
],
|
||||
app_permissions: [
|
||||
"roles.manage",
|
||||
"billing.manage",
|
||||
|
||||
91
supabase/migrations/20250605150146_add_analysis_orders.sql
Normal file
91
supabase/migrations/20250605150146_add_analysis_orders.sql
Normal file
@@ -0,0 +1,91 @@
|
||||
create type "public"."analysis_order_status" as enum ('QUEUED', 'ON_HOLD', 'PROCESSING', 'COMPLETED', 'REJECTED', 'CANCELLED');
|
||||
|
||||
create table "public"."analysis_orders" (
|
||||
"id" uuid not null,
|
||||
"analysis_element_ids" bigint[],
|
||||
"analysis_ids" bigint[],
|
||||
"user_id" uuid not null,
|
||||
"status" public.analysis_order_status not null,
|
||||
"created_at" timestamp with time zone not null default now()
|
||||
);
|
||||
|
||||
alter table "public"."analysis_orders" enable row level security;
|
||||
|
||||
CREATE UNIQUE INDEX analysis_orders_pkey ON public.analysis_orders USING btree (id);
|
||||
|
||||
alter table "public"."analysis_orders" add constraint "analysis_orders_pkey" PRIMARY KEY using index "analysis_orders_pkey";
|
||||
|
||||
alter table "public"."analysis_orders" add constraint "analysis_orders_id_fkey" FOREIGN KEY (id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
|
||||
|
||||
alter table "public"."analysis_orders" validate constraint "analysis_orders_id_fkey";
|
||||
|
||||
grant delete on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant insert on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant references on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant select on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant trigger on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant truncate on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant update on table "public"."analysis_orders" to "authenticated";
|
||||
|
||||
grant delete on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
grant insert on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
grant references on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
grant select on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
grant trigger on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
grant truncate on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
grant update on table "public"."analysis_orders" to "service_role";
|
||||
|
||||
create policy "analysis_all"
|
||||
on "public"."analysis_orders"
|
||||
as permissive
|
||||
for all
|
||||
to authenticated, service_role
|
||||
using (true);
|
||||
|
||||
create policy "analysis_select"
|
||||
on "public"."analyses"
|
||||
as permissive
|
||||
for select
|
||||
to public
|
||||
using (true);
|
||||
|
||||
create policy "analysis_elements_select"
|
||||
on "public"."analysis_elements"
|
||||
as permissive
|
||||
for select
|
||||
to public
|
||||
using (true);
|
||||
|
||||
create policy "analysis_groups_select"
|
||||
on "public"."analysis_groups"
|
||||
as permissive
|
||||
for select
|
||||
to public
|
||||
using (true);
|
||||
|
||||
-- Drop previously created unnecessary indices
|
||||
drop index if exists "public"."analysis_elements_original_id_key";
|
||||
drop index if exists "public"."analysis_original_id_key";
|
||||
|
||||
-- Remove nullable from previously added fields
|
||||
alter table "public"."analyses" alter column "order" set not null;
|
||||
alter table "public"."analysis_elements" alter column "analysis_id_oid" set not null;
|
||||
alter table "public"."analysis_elements" alter column "order" set not null;
|
||||
alter table "public"."analysis_groups" alter column "name" set not null;
|
||||
alter table "public"."analysis_groups" alter column "order" set not null;
|
||||
alter table "public"."analysis_elements" alter column "tehik_loinc_name" set not null;
|
||||
alter table "public"."analysis_elements" alter column "tehik_short_loinc" set not null;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user