diff --git a/app/auth/update-account/page.tsx b/app/auth/update-account/page.tsx index fc94740..a40b13e 100644 --- a/app/auth/update-account/page.tsx +++ b/app/auth/update-account/page.tsx @@ -37,7 +37,7 @@ async function UpdateAccount() { } return account?.email ?? user?.email ?? ''; })(), - phone: account?.phone ?? '', + phone: account?.phone ?? '+372', city: account?.city ?? '', weight: account?.accountParams?.weight ?? 0, height: account?.accountParams?.height ?? 0, diff --git a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts index 9ef4799..11eca6f 100644 --- a/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts +++ b/app/home/(user)/(dashboard)/cart/montonio-callback/actions.ts @@ -112,7 +112,6 @@ export async function processMontonioCallback(orderToken: string) { throw new Error("Cart not found"); } - const medusaOrder = await placeOrder(cartId, { revalidateCacheTags: false }); const orderedAnalysisElements = await getOrderedAnalysisIds({ medusaOrder }); const orderId = await createOrder({ medusaOrder, orderedAnalysisElements }); diff --git a/app/home/(user)/_components/compare-packages-modal.tsx b/app/home/(user)/_components/compare-packages-modal.tsx index 7c163bd..90ed81c 100644 --- a/app/home/(user)/_components/compare-packages-modal.tsx +++ b/app/home/(user)/_components/compare-packages-modal.tsx @@ -128,7 +128,7 @@ const ComparePackagesModal = async ({ return ( - + {title}{' '} {description && (} />)} diff --git a/app/home/(user)/_components/dashboard.tsx b/app/home/(user)/_components/dashboard.tsx index 53722d9..d2f8007 100644 --- a/app/home/(user)/_components/dashboard.tsx +++ b/app/home/(user)/_components/dashboard.tsx @@ -16,7 +16,6 @@ import { } from 'lucide-react'; import { pathsConfig } from '@kit/shared/config'; -import { getPersonParameters } from '@kit/shared/utils'; import { Button } from '@kit/ui/button'; import { Card, @@ -30,7 +29,7 @@ import { cn } from '@kit/ui/utils'; import { isNil } from 'lodash'; import { BmiCategory } from '~/lib/types/bmi'; -import { +import PersonalCode, { bmiFromMetric, getBmiBackgroundColor, getBmiStatus, @@ -60,7 +59,7 @@ const cards = ({ }) => [ { title: 'dashboard:gender', - description: gender ?? 'dashboard:male', + description: gender ?? '-', icon: , iconBg: 'bg-success', }, @@ -145,21 +144,19 @@ export default function Dashboard({ 'id' >[]; }) { - const params = getPersonParameters(account.personal_code!); - const bmiStatus = getBmiStatus(bmiThresholds, { - age: params?.age || 0, - height: account.accountParams?.height || 0, - weight: account.accountParams?.weight || 0, - }); + const height = account.accountParams?.height || 0; + const weight = account.accountParams?.weight || 0; + const { age = 0, gender } = PersonalCode.parsePersonalCode(account.personal_code!); + const bmiStatus = getBmiStatus(bmiThresholds, { age, height, weight }); return ( <>
{cards({ - gender: params?.gender, - age: params?.age, - height: account.accountParams?.height, - weight: account.accountParams?.weight, + gender: gender.label, + age, + height, + weight, bmiStatus, smoking: account.accountParams?.isSmoker, }).map( diff --git a/app/home/(user)/_lib/server/load-analysis-packages.ts b/app/home/(user)/_lib/server/load-analysis-packages.ts index ca3fd5b..597b95f 100644 --- a/app/home/(user)/_lib/server/load-analysis-packages.ts +++ b/app/home/(user)/_lib/server/load-analysis-packages.ts @@ -1,5 +1,4 @@ import { cache } from 'react'; -import Isikukood, { Gender } from 'isikukood'; import { listProductTypes, listProducts } from "@lib/data/products"; import { listRegions } from '@lib/data/regions'; @@ -8,6 +7,7 @@ import type { StoreProduct } from '@medusajs/types'; import { loadCurrentUserAccount } from './load-user-account'; import { AccountWithParams } from '@/packages/features/accounts/src/server/api'; import { AnalysisPackageWithVariant } from '@kit/shared/components/select-analysis-package'; +import PersonalCode from '~/lib/utils'; async function countryCodesLoader() { const countryCodes = await listRegions().then((regions) => @@ -32,27 +32,8 @@ function userSpecificVariantLoader({ if (!personalCode) { throw new Error('Personal code not found'); } - const parsed = new Isikukood(personalCode); - const ageRange = (() => { - const age = parsed.getAge(); - if (age >= 18 && age <= 29) { - return '18-29'; - } - if (age >= 30 && age <= 39) { - return '30-39'; - } - if (age >= 40 && age <= 49) { - return '40-49'; - } - if (age >= 50 && age <= 59) { - return '50-59'; - } - if (age >= 60) { - return '60'; - } - throw new Error('Age range not supported'); - })(); - const gender = parsed.getGender() === Gender.MALE ? 'M' : 'F'; + + const { ageRange, gender: { value: gender } } = PersonalCode.parsePersonalCode(personalCode); return ({ product, @@ -89,6 +70,7 @@ async function analysisPackageElementsLoader({ queryParams: { id: analysisElementMedusaProductIds, limit: 100, + order: "title", }, }); diff --git a/app/home/[account]/_lib/server/load-team-account-health-details.ts b/app/home/[account]/_lib/server/load-team-account-health-details.ts index 0c4ff72..1705770 100644 --- a/app/home/[account]/_lib/server/load-team-account-health-details.ts +++ b/app/home/[account]/_lib/server/load-team-account-health-details.ts @@ -31,11 +31,11 @@ export const getAccountHealthDetailsFields = ( >[], members: Database['medreport']['Functions']['get_account_members']['Returns'], ): AccountHealthDetailsField[] => { - const avarageWeight = + const averageWeight = memberParams.reduce((sum, r) => sum + r.weight!, 0) / memberParams.length; - const avarageHeight = + const averageHeight = memberParams.reduce((sum, r) => sum + r.height!, 0) / memberParams.length; - const avarageAge = + const averageAge = members.reduce((sum, r) => { const person = new Isikukood(r.personal_code); return sum + person.getAge(); @@ -48,11 +48,11 @@ export const getAccountHealthDetailsFields = ( const person = new Isikukood(r.personal_code); return person.getGender() === 'female'; }).length; - const averageBMI = bmiFromMetric(avarageWeight, avarageHeight); + const averageBMI = bmiFromMetric(averageWeight, averageHeight); const bmiStatus = getBmiStatus(bmiThresholds, { - age: avarageAge, - height: avarageHeight, - weight: avarageWeight, + age: averageAge, + height: averageHeight, + weight: averageWeight, }); const malePercentage = members.length ? (numberOfMaleMembers / members.length) * 100 @@ -76,7 +76,7 @@ export const getAccountHealthDetailsFields = ( }, { title: 'teams:healthDetails.avgAge', - value: avarageAge.toFixed(0), + value: averageAge.toFixed(0), Icon: Clock, iconBg: 'bg-success', }, diff --git a/lib/constants.ts b/lib/constants.ts index 7092724..93f3bca 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,2 +1,2 @@ export const DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; -export const DATE_FORMAT = "yyyy-mm-dd"; +export const DATE_FORMAT = "yyyy-MM-dd"; diff --git a/lib/services/analyses.service.ts b/lib/services/analyses.service.ts index 0127e09..790b201 100644 --- a/lib/services/analyses.service.ts +++ b/lib/services/analyses.service.ts @@ -2,7 +2,7 @@ import type { Tables } from '@/packages/supabase/src/database.types'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; import type { IUuringElement } from "./medipost.types"; -type AnalysesWithGroupsAndElements = ({ +export type AnalysesWithGroupsAndElements = ({ analysis_elements: Tables<{ schema: 'medreport' }, 'analysis_elements'> & { analysis_groups: Tables<{ schema: 'medreport' }, 'analysis_groups'>; }; @@ -105,7 +105,13 @@ export const createMedusaSyncSuccessEntry = async () => { }); } -export async function getAnalyses({ ids, originalIds }: { ids?: number[], originalIds?: string[] }): Promise { +export async function getAnalyses({ + ids, + originalIds, +}: { + ids?: number[]; + originalIds?: string[]; +}): Promise { const query = getSupabaseServerAdminClient() .schema('medreport') .from('analyses') diff --git a/lib/services/medipost.service.ts b/lib/services/medipost.service.ts index b6aec5d..82db51d 100644 --- a/lib/services/medipost.service.ts +++ b/lib/services/medipost.service.ts @@ -5,23 +5,11 @@ import { createClient as createCustomClient, } from '@supabase/supabase-js'; -import { - getAnalysisGroup, - getClientInstitution, - getClientPerson, - getConfidentiality, - getOrderEnteredPerson, - getPais, - getPatient, - getProviderInstitution, - getSpecimen, -} from '@/lib/templates/medipost-order'; import { SyncStatus } from '@/lib/types/audit'; import { AnalysisOrderStatus, GetMessageListResponse, IMedipostResponseXMLBase, - MaterjalideGrupp, MedipostAction, MedipostOrderResponse, MedipostPublicMessageResponse, @@ -32,7 +20,6 @@ import { import { toArray } from '@/lib/utils'; import axios from 'axios'; import { XMLParser } from 'fast-xml-parser'; -import { uniqBy } from 'lodash'; import { Tables } from '@kit/supabase/database'; import { createAnalysisGroup } from './analysis-group.service'; @@ -47,6 +34,7 @@ import { listRegions } from '@lib/data/regions'; import { getAnalysisElementMedusaProductIds } from '@/utils/medusa-product'; import { MedipostValidationError } from './medipost/MedipostValidationError'; import { logMedipostDispatch } from './audit.service'; +import { composeOrderXML, OrderedAnalysisElement } from './medipostXML.service'; const BASE_URL = process.env.MEDIPOST_URL!; const USER = process.env.MEDIPOST_USER!; @@ -451,122 +439,6 @@ export async function syncPublicMessage( } } -export async function composeOrderXML({ - person, - orderedAnalysisElementsIds, - orderedAnalysesIds, - orderId, - orderCreatedAt, - comment, -}: { - person: { - idCode: string; - firstName: string; - lastName: string; - phone: string; - }; - orderedAnalysisElementsIds: number[]; - orderedAnalysesIds: number[]; - orderId: string; - orderCreatedAt: Date; - comment?: string; -}) { - const analysisElements = await getAnalysisElementsAdmin({ ids: orderedAnalysisElementsIds }); - if (analysisElements.length !== orderedAnalysisElementsIds.length) { - throw new Error(`Got ${analysisElements.length} analysis elements, expected ${orderedAnalysisElementsIds.length}`); - } - - const analyses = await getAnalyses({ ids: orderedAnalysesIds }); - if (analyses.length !== orderedAnalysesIds.length) { - throw new Error(`Got ${analyses.length} analyses, expected ${orderedAnalysesIds.length}`); - } - - const analysisGroups: Tables<{ schema: 'medreport' }, '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); - } - - return ` - - ${getPais(USER, RECIPIENT, orderCreatedAt, orderId)} - - ${orderId} - ${getClientInstitution()} - ${getProviderInstitution()} - ${getClientPerson()} - ${getOrderEnteredPerson()} - ${comment ?? ''} - ${getPatient(person)} - ${getConfidentiality()} - ${specimenSection.join('')} - ${analysisSection?.join('')} - -`; -} - function getLatestMessage({ messages, excludedMessageIds, @@ -694,7 +566,7 @@ async function syncPrivateMessage({ ); } - const { data: allOrderResponseElements} = await supabase + const { data: allOrderResponseElements } = await supabase .schema('medreport') .from('analysis_response_elements') .select('*') @@ -714,20 +586,36 @@ export async function sendOrderToMedipost({ orderedAnalysisElements, }: { medusaOrderId: string; - orderedAnalysisElements: { analysisElementId?: number; analysisId?: number }[]; + orderedAnalysisElements: OrderedAnalysisElement[]; }) { const medreportOrder = await getOrder({ medusaOrderId }); const account = await getAccountAdmin({ primaryOwnerUserId: medreportOrder.user_id }); + const orderedAnalysesIds = orderedAnalysisElements + .map(({ analysisId }) => analysisId) + .filter(Boolean) as number[]; + const orderedAnalysisElementsIds = orderedAnalysisElements + .map(({ analysisElementId }) => analysisElementId) + .filter(Boolean) as number[]; + + const analyses = await getAnalyses({ ids: orderedAnalysesIds }); + if (analyses.length !== orderedAnalysesIds.length) { + throw new Error(`Got ${analyses.length} analyses, expected ${orderedAnalysesIds.length}`); + } + const analysisElements = await getAnalysisElementsAdmin({ ids: orderedAnalysisElementsIds }); + if (analysisElements.length !== orderedAnalysisElementsIds.length) { + throw new Error(`Got ${analysisElements.length} analysis elements, expected ${orderedAnalysisElementsIds.length}`); + } + const orderXml = await composeOrderXML({ + analyses, + analysisElements, person: { idCode: account.personal_code!, firstName: account.name ?? '', lastName: account.last_name ?? '', phone: account.phone ?? '', }, - orderedAnalysisElementsIds: orderedAnalysisElements.map(({ analysisElementId }) => analysisElementId).filter(Boolean) as number[], - orderedAnalysesIds: orderedAnalysisElements.map(({ analysisId }) => analysisId).filter(Boolean) as number[], orderId: medusaOrderId, orderCreatedAt: new Date(medreportOrder.created_at), comment: '', @@ -826,7 +714,12 @@ export async function getOrderedAnalysisIds({ throw new Error(`Got ${orderedPackagesProducts.length} ordered packages products, expected ${orderedPackageIds.length}`); } - const ids = getAnalysisElementMedusaProductIds(orderedPackagesProducts); + const ids = getAnalysisElementMedusaProductIds( + orderedPackagesProducts.map(({ id, metadata }) => ({ + metadata, + variant: orderedPackages.find(({ product }) => product?.id === id)?.variant, + })), + ); if (ids.length === 0) { return []; } @@ -867,10 +760,10 @@ export async function createMedipostActionLog({ hasError = false, }: { action: - | 'send_order_to_medipost' - | 'sync_analysis_results_from_medipost' - | 'send_fake_analysis_results_to_medipost' - | 'send_analysis_results_to_medipost'; + | 'send_order_to_medipost' + | 'sync_analysis_results_from_medipost' + | 'send_fake_analysis_results_to_medipost' + | 'send_analysis_results_to_medipost'; xml: string; hasAnalysisResults?: boolean; medusaOrderId?: string | null; diff --git a/lib/services/medipostXML.service.ts b/lib/services/medipostXML.service.ts new file mode 100644 index 0000000..3b55506 --- /dev/null +++ b/lib/services/medipostXML.service.ts @@ -0,0 +1,201 @@ +'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: string; + 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(); + + let specimenOrder = 1; + + // Collect all materials from all analysis groups + 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); + for (const material of materials) { + const { MaterjaliNimi, MaterjaliTyyp, MaterjaliTyypOID, Konteiner } = material; + const containers = toArray(Konteiner); + + for (const container of containers) { + // Use MaterialTyyp as the key for deduplication + const materialKey = MaterjaliTyyp; + + if (!uniqueMaterials.has(materialKey)) { + uniqueMaterials.set(materialKey, { + MaterjaliTyypOID, + MaterjaliTyyp, + 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 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})`, + ); + } + + // Find the specimen order number for this analysis group + let specimenOrderNumber = 1; + for (const group of relatedAnalysisElement?.material_groups as MaterjalideGrupp[]) { + const materials = toArray(group.Materjal); + for (const material of materials) { + const materialKey = material.MaterjaliTyyp; + const uniqueMaterial = uniqueMaterials.get(materialKey); + if (uniqueMaterial) { + specimenOrderNumber = uniqueMaterial.order; + break; // Use the first material's order number + } + } + if (specimenOrderNumber > 1) break; // Found a specimen, use it + } + + const groupXml = getAnalysisGroup( + currentGroup.original_id, + currentGroup.name, + specimenOrderNumber, + relatedAnalysisElement, + ); + analysisSection.push(groupXml); + } + + return ` + + ${getPais(USER, RECIPIENT, orderCreatedAt, orderId)} + + ${orderId} + ${getClientInstitution()} + ${getProviderInstitution()} + ${getClientPerson()} + ${getOrderEnteredPerson()} + ${comment ?? ''} + ${getPatient(person)} + ${getConfidentiality()} + ${specimenSection.join('')} + ${analysisSection?.join('')} + +`; +} diff --git a/lib/templates/medipost-order.ts b/lib/templates/medipost-order.ts index 10ce573..19e5b79 100644 --- a/lib/templates/medipost-order.ts +++ b/lib/templates/medipost-order.ts @@ -1,7 +1,7 @@ import { format } from 'date-fns'; -import Isikukood, { Gender } from 'isikukood'; import { Tables } from '@/packages/supabase/src/database.types'; import { DATE_FORMAT, DATE_TIME_FORMAT } from '@/lib/constants'; +import PersonalCode from '../utils'; const isProd = process.env.NODE_ENV === 'production'; @@ -73,15 +73,15 @@ export const getPatient = ({ lastName: string, firstName: string, }) => { - const isikukood = new Isikukood(idCode); + const { dob, gender } = PersonalCode.parsePersonalCode(idCode); return ` 1.3.6.1.4.1.28284.6.2.2.1 ${idCode} ${lastName} ${firstName} - ${format(isikukood.getBirthday(), DATE_FORMAT)} + ${format(dob, DATE_FORMAT)} 1.3.6.1.4.1.28284.6.2.3.16.2 - ${isikukood.getGender() === Gender.MALE ? 'M' : 'N'} + ${gender.value === 'M' ? 'M' : 'N'} `; }; diff --git a/lib/utils.ts b/lib/utils.ts index 36307b7..d8fa393 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -90,9 +90,61 @@ export function getBmiBackgroundColor(bmiStatus: BmiCategory | null): string { } } -export function getGenderStringFromPersonalCode(personalCode: string) { - const person = new Isikukood(personalCode); - if (person.getGender() === Gender.FEMALE) return 'common:female'; - if (person.getGender() === Gender.MALE) return 'common:male'; - return 'common:unknown'; +type AgeRange = '18-29' | '30-39' | '40-49' | '50-59' | '60'; +export default class PersonalCode { + static getPersonalCode(personalCode: string | null) { + if (!personalCode) { + return null; + } + if (personalCode.toLowerCase().startsWith('ee')) { + return personalCode.substring(2); + } + return personalCode; + } + + static parsePersonalCode(personalCode: string): { + ageRange: AgeRange; + gender: { label: string; value: string }; + dob: Date; + age: number; + } { + const parsed = new Isikukood(personalCode); + const ageRange = (() => { + const age = parsed.getAge(); + if (age >= 18 && age <= 29) { + return '18-29'; + } + if (age >= 30 && age <= 39) { + return '30-39'; + } + if (age >= 40 && age <= 49) { + return '40-49'; + } + if (age >= 50 && age <= 59) { + return '50-59'; + } + if (age >= 60) { + return '60'; + } + throw new Error('Age range not supported'); + })(); + const gender = (() => { + const gender = parsed.getGender(); + switch (gender) { + case Gender.FEMALE: + return { label: 'common:female', value: 'F' }; + case Gender.MALE: + return { label: 'common:male', value: 'M' }; + default: + throw new Error('Gender not supported'); + } + })(); + + return { + ageRange, + gender, + dob: parsed.getBirthday(), + age: parsed.getAge(), + } + } } diff --git a/packages/features/accounts/src/server/api.ts b/packages/features/accounts/src/server/api.ts index f28a490..d1faaef 100644 --- a/packages/features/accounts/src/server/api.ts +++ b/packages/features/accounts/src/server/api.ts @@ -3,6 +3,7 @@ import { SupabaseClient } from '@supabase/supabase-js'; import { Database } from '@kit/supabase/database'; import { AnalysisResultDetails, UserAnalysis } from '../types/accounts'; +import PersonalCode from '~/lib/utils'; export type AccountWithParams = Database['medreport']['Tables']['accounts']['Row'] & { @@ -71,15 +72,7 @@ class AccountsApi { const { personal_code, ...rest } = data; return { ...rest, - personal_code: (() => { - if (!personal_code) { - return null; - } - if (personal_code.toLowerCase().startsWith('ee')) { - return personal_code.substring(2); - } - return personal_code; - })(), + personal_code: PersonalCode.getPersonalCode(personal_code), }; } diff --git a/packages/features/auth/src/server/api.ts b/packages/features/auth/src/server/api.ts index 6f8ead2..223e82c 100644 --- a/packages/features/auth/src/server/api.ts +++ b/packages/features/auth/src/server/api.ts @@ -68,6 +68,7 @@ class AuthApi { p_name: data.firstName, p_last_name: data.lastName, p_personal_code: data.personalCode, + p_email: data.email || '', p_phone: data.phone || '', p_city: data.city || '', p_has_consent_personal_data: data.userConsent, diff --git a/packages/features/medusa-storefront/src/lib/data/products.ts b/packages/features/medusa-storefront/src/lib/data/products.ts index a8ea25d..4b1e250 100644 --- a/packages/features/medusa-storefront/src/lib/data/products.ts +++ b/packages/features/medusa-storefront/src/lib/data/products.ts @@ -14,7 +14,12 @@ export const listProducts = async ({ regionId, }: { pageParam?: number - queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams & { "type_id[0]"?: string; id?: string[], category_id?: string } + queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams & { + "type_id[0]"?: string; + id?: string[], + category_id?: string; + order?: 'title'; + } countryCode?: string regionId?: string }): Promise<{ diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index a4f8cc1..a09d6b8 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -1257,6 +1257,26 @@ export type Database = { }, ] } + medipost_actions: { + Row: { + created_at: string + id: number + action: string + xml: string + has_analysis_results: boolean + medusa_order_id: string + response_xml: string + has_error: boolean + } + Insert: { + action: string + xml: string + has_analysis_results: boolean + medusa_order_id: string + response_xml: string + has_error: boolean + } + } medreport_product_groups: { Row: { created_at: string @@ -2053,6 +2073,7 @@ export type Database = { p_personal_code: string p_phone: string p_uid: string + p_email: string } Returns: undefined } diff --git a/supabase/migrations/20250908145900_update_account_email_keycloak.sql b/supabase/migrations/20250908145900_update_account_email_keycloak.sql new file mode 100644 index 0000000..9e44e06 --- /dev/null +++ b/supabase/migrations/20250908145900_update_account_email_keycloak.sql @@ -0,0 +1,28 @@ +CREATE OR REPLACE FUNCTION medreport.update_account(p_name character varying, p_last_name text, p_personal_code text, p_phone text, p_city text, p_has_consent_personal_data boolean, p_uid uuid, p_email character varying) + RETURNS void + LANGUAGE plpgsql +AS $function$begin + update medreport.accounts + set name = coalesce(p_name, name), + last_name = coalesce(p_last_name, last_name), + personal_code = coalesce(p_personal_code, personal_code), + phone = coalesce(p_phone, phone), + city = coalesce(p_city, city), + has_consent_personal_data = coalesce(p_has_consent_personal_data, + has_consent_personal_data), + email = coalesce(p_email, email) + where id = p_uid; +end;$function$ +; + +grant +execute on function medreport.update_account( + p_name character varying, + p_last_name text, + p_personal_code text, + p_phone text, + p_city text, + p_has_consent_personal_data boolean, + p_uid uuid, + p_email character varying) to authenticated, +service_role; diff --git a/utils/medusa-product.ts b/utils/medusa-product.ts index 6cbbb3b..c505608 100644 --- a/utils/medusa-product.ts +++ b/utils/medusa-product.ts @@ -1,17 +1,27 @@ -export const getAnalysisElementMedusaProductIds = (products: ({ +import { StoreProduct } from "@medusajs/types"; + +type Product = { metadata?: { analysisElementMedusaProductIds?: string; } | null; -} | null)[]) => { + variant?: { + metadata?: { + analysisElementMedusaProductIds?: string; + } | null; + } | null; +} | null; + +export const getAnalysisElementMedusaProductIds = (products: Pick[]) => { if (!products) { return []; } const mapped = products .flatMap((product) => { - const value = product?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"'); + const value = (product as Product)?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"'); + const value_variant = (product as Product)?.variant?.metadata?.analysisElementMedusaProductIds?.replaceAll("'", '"'); try { - return JSON.parse(value as string); + return [...JSON.parse(value as string), ...JSON.parse(value_variant as string)]; } catch (e) { console.error("Failed to parse analysisElementMedusaProductIds from analysis package, possibly invalid format", e); return [];