'use server'; import { revalidatePath } from 'next/cache'; import { listRegions } from '@lib/data/regions'; import { StoreCart, StoreOrder } from '@medusajs/types'; import { getLogger } from '@kit/shared/logger'; import { Tables } from '@kit/supabase/database'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { EnrichedCartItem } from '../../app/home/(user)/_components/cart/types'; import { loadCurrentUserAccount } from '../../app/home/(user)/_lib/server/load-user-account'; import { createCartEntriesLog } from './audit/cartEntries'; import { handleDeleteCartItem } from './medusaCart.service'; type Locations = Tables<{ schema: 'medreport' }, 'connected_online_locations'>; type Services = Tables<{ schema: 'medreport' }, 'connected_online_services'>; type ServiceProviders = Tables< { schema: 'medreport' }, 'connected_online_service_providers' >; export async function getCartReservations( medusaCart: StoreCart, ): Promise { const supabase = getSupabaseServerClient(); const cartLineItemIds = medusaCart.items?.map(({ id }) => id); if (!cartLineItemIds?.length) { return []; } const { data: reservations } = await supabase .schema('medreport') .from('connected_online_reservation') .select( 'id, startTime:start_time, service:service_id, location:location_sync_id, serviceProvider:service_user_id, medusaCartLineItemId:medusa_cart_line_item_id', ) .in('medusa_cart_line_item_id', cartLineItemIds) .throwOnError(); const locationSyncIds: number[] = reservations ?.filter((reservation) => !!reservation.location) .map((reservation) => reservation.location!) ?? []; const serviceIds = reservations?.map((reservation) => reservation.service) ?? []; const serviceProviderIds = reservations.map((reservation) => reservation.serviceProvider) ?? []; let locations: | { syncId: Locations['sync_id']; name: Locations['name']; address: Locations['address']; }[] | null = null; if (locationSyncIds.length) { ({ data: locations } = await supabase .schema('medreport') .from('connected_online_locations') .select('syncId:sync_id, name, address') .in('sync_id', locationSyncIds) .throwOnError()); } let services: | { id: Services['id']; name: Services['name']; }[] | null = null; if (serviceIds.length) { ({ data: services } = await supabase .schema('medreport') .from('connected_online_services') .select('name, id') .in('id', serviceIds) .throwOnError()); } let serviceProviders: | { id: ServiceProviders['id']; name: ServiceProviders['name']; jobTitleEt: ServiceProviders['job_title_et']; jobTitleEn: ServiceProviders['job_title_en']; jobTitleRu: ServiceProviders['job_title_ru']; spokenLanguages: ServiceProviders['spoken_languages']; }[] | null = null; if (serviceProviderIds.length) { ({ data: serviceProviders } = await supabase .schema('medreport') .from('connected_online_service_providers') .select( 'id, name, jobTitleEt:job_title_et, jobTitleEn:job_title_en, jobTitleRu:job_title_ru, spokenLanguages:spoken_languages', ) .in('id', serviceProviderIds) .throwOnError()); } const results = []; for (const reservation of reservations) { if (reservation.medusaCartLineItemId === null) { continue; } const cartLineItem = medusaCart.items?.find( (item) => item.id === reservation.medusaCartLineItemId, ); if (!cartLineItem) { continue; } const location = locations?.find( (location) => location.syncId === reservation.location, ); const service = services?.find( (service) => service.id === reservation.service, ); const serviceProvider = serviceProviders?.find( (serviceProvider) => serviceProvider.id === reservation.serviceProvider, ); const countryCodes = await listRegions().then((regions) => regions?.map((r) => r.countries?.map((c) => c.iso_2)).flat(), ); const enrichedReservation = { ...reservation, location, service, serviceProvider, }; results.push({ ...cartLineItem, reservation: { ...enrichedReservation, medusaCartLineItemId: reservation.medusaCartLineItemId!, countryCode: countryCodes[0], }, }); } return results; } export async function createInitialReservation({ serviceId, clinicId, appointmentUserId, syncUserID, startTime, medusaLineItemId, locationId, comments = '', }: { serviceId: number; clinicId: number; appointmentUserId: number; syncUserID: number; startTime: Date; medusaLineItemId: string; locationId?: number | null; comments?: string; }) { const logger = await getLogger(); const supabase = getSupabaseServerClient(); const { data: { user }, } = await supabase.auth.getUser(); const userId = user?.id; if (!userId) { throw new Error('User not authenticated'); } logger.info( 'Creating reservation' + JSON.stringify({ serviceId, clinicId, startTime, userId }), ); try { const { data: createdReservation } = await supabase .schema('medreport') .from('connected_online_reservation') .insert({ clinic_id: clinicId, comments, lang: 'et', service_id: serviceId, service_user_id: appointmentUserId, start_time: startTime.toString(), sync_user_id: syncUserID, user_id: userId, status: 'PENDING', medusa_cart_line_item_id: medusaLineItemId, location_sync_id: locationId, }) .select('id') .single() .throwOnError(); logger.info( `Created reservation ${JSON.stringify({ createdReservation, userId })}`, ); return createdReservation; } catch (e) { logger.error( `Failed to create initial reservation ${JSON.stringify({ serviceId, clinicId, startTime })} ${e}`, ); await handleDeleteCartItem({ lineId: medusaLineItemId }); throw e; } } export async function cancelReservation(medusaLineItemId: string) { const supabase = getSupabaseServerClient(); return supabase .schema('medreport') .from('connected_online_reservation') .update({ status: 'CANCELLED', }) .eq('medusa_cart_line_item_id', medusaLineItemId) .throwOnError(); } export async function getOrderedTtoServices({ cart, medusaOrder, }: { cart?: StoreCart; medusaOrder?: StoreOrder; }) { const supabase = getSupabaseServerClient(); if (!medusaOrder && !cart) { throw new Error('No cart or medusa order provided'); } const ttoReservationIds: number[] = (medusaOrder?.items ?? cart?.items) ?.filter(({ metadata }) => !!metadata?.connectedOnlineReservationId) .map(({ metadata }) => Number(metadata!.connectedOnlineReservationId)) ?? []; const { data: orderedTtoServices } = await supabase .schema('medreport') .from('connected_online_reservation') .select('*, provider:connected_online_providers(key)') .in('id', ttoReservationIds) .throwOnError(); return orderedTtoServices; } export async function updateReservationTime({ reservationId, newStartTime, newServiceId, newAppointmentUserId, newSyncUserId, newLocationId, // TODO stop allowing null when Connected starts returning the correct ids instead of -1 cartId, }: { reservationId: number; newStartTime: Date; newServiceId: number; newAppointmentUserId: number; newSyncUserId: number; newLocationId: number | null; cartId: string; }) { const logger = await getLogger(); const supabase = getSupabaseServerClient(); const { data: { user }, } = await supabase.auth.getUser(); const userId = user?.id; const { account } = await loadCurrentUserAccount(); if (!userId || !account) { throw new Error('User not authenticated'); } const reservationData = JSON.stringify({ reservationId, newStartTime, newServiceId, newAppointmentUserId, newSyncUserId, newLocationId, userId, cartId, }); logger.info('Updating reservation' + reservationData); try { await supabase .schema('medreport') .from('connected_online_reservation') .update({ service_id: newServiceId, service_user_id: newAppointmentUserId, sync_user_id: newSyncUserId, start_time: newStartTime.toString(), location_sync_id: newLocationId, updated_at: new Date().toISOString(), }) .eq('id', reservationId) .eq('user_id', user.id) .throwOnError(); logger.info(`Successfully updated reservation ${reservationData}`); await createCartEntriesLog({ operation: 'CHANGE_RESERVATION', accountId: account.id, cartId: cartId, comment: `${reservationData}`, }); revalidatePath('/home/cart', 'layout'); } catch (e) { logger.error(`Failed to update reservation ${reservationData}`); await createCartEntriesLog({ operation: 'CHANGE_RESERVATION', accountId: account.id, cartId: cartId, comment: `${e}`, }); throw e; } }