Compare commits
30 Commits
5cf29447b3
...
2df366c14a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2df366c14a | |||
|
|
6ce60eacc7 | ||
|
|
6e76e75e85 | ||
| 71c3e2ef1e | |||
| d83319a094 | |||
|
|
505ef0d91b | ||
|
|
fce4355be8 | ||
|
|
da9658ad7a | ||
|
|
e023d54a2a | ||
|
|
bdaacbe78a | ||
|
|
5479f310d7 | ||
| 815b877b5b | |||
| 0c28f9681b | |||
| 70b85dc967 | |||
| 71f5a25632 | |||
| d072226a5c | |||
| da7f574234 | |||
| b3505c1627 | |||
|
|
ad28352fc8 | ||
| b931035c3b | |||
| f723633646 | |||
| 6e6ad13b52 | |||
|
|
31bc4b6cff | ||
| 3ddc0a2716 | |||
| 49eeaa1876 | |||
| 2ffad84100 | |||
| b4985afdf0 | |||
| a37c4cad9c | |||
| 47ab39172e | |||
| d760f86632 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -43,3 +43,4 @@ yarn-error.log*
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
dump.sql
|
dump.sql
|
||||||
|
data.sql
|
||||||
@@ -71,19 +71,19 @@ export default async function syncConnectedOnline() {
|
|||||||
return {
|
return {
|
||||||
id: service.ID,
|
id: service.ID,
|
||||||
clinic_id: service.ClinicID,
|
clinic_id: service.ClinicID,
|
||||||
code: service.Code,
|
sync_id: service.SyncID,
|
||||||
description: service.Description || null,
|
|
||||||
display: service.Display,
|
|
||||||
duration: service.Duration,
|
|
||||||
has_free_codes: !!service.HasFreeCodes,
|
|
||||||
name: service.Name,
|
name: service.Name,
|
||||||
|
description: service.Description || null,
|
||||||
|
price: service.Price,
|
||||||
|
requires_payment: !!service.RequiresPayment,
|
||||||
|
duration: service.Duration,
|
||||||
neto_duration: service.NetoDuration,
|
neto_duration: service.NetoDuration,
|
||||||
|
display: service.Display,
|
||||||
|
price_periods: service.PricePeriods || null,
|
||||||
online_hide_duration: service.OnlineHideDuration,
|
online_hide_duration: service.OnlineHideDuration,
|
||||||
online_hide_price: service.OnlineHidePrice,
|
online_hide_price: service.OnlineHidePrice,
|
||||||
price: service.Price,
|
code: service.Code,
|
||||||
price_periods: service.PricePeriods || null,
|
has_free_codes: !!service.HasFreeCodes,
|
||||||
requires_payment: !!service.RequiresPayment,
|
|
||||||
sync_id: service.SyncID,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ export async function POST(request: NextRequest) {
|
|||||||
orderCreatedAt: new Date(medreportOrder.created_at),
|
orderCreatedAt: new Date(medreportOrder.created_at),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.info("SEND XML", messageXml);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendPrivateMessageTestResponse({ messageXml });
|
await sendPrivateMessageTestResponse({ messageXml });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getOrder } from "~/lib/services/order.service";
|
|||||||
import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipostTest.service";
|
import { composeOrderTestResponseXML, sendPrivateMessageTestResponse } from "~/lib/services/medipostTest.service";
|
||||||
import { retrieveOrder } from "@lib/data";
|
import { retrieveOrder } from "@lib/data";
|
||||||
import { getAccountAdmin } from "~/lib/services/account.service";
|
import { getAccountAdmin } from "~/lib/services/account.service";
|
||||||
import { getOrderedAnalysisElementsIds } from "~/lib/services/medipost.service";
|
import { createMedipostActionLog, getOrderedAnalysisElementsIds } from "~/lib/services/medipost.service";
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
// const isDev = process.env.NODE_ENV === 'development';
|
// const isDev = process.env.NODE_ENV === 'development';
|
||||||
@@ -35,6 +35,11 @@ export async function POST(request: Request) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
await createMedipostActionLog({
|
||||||
|
action: 'send_fake_analysis_results_to_medipost',
|
||||||
|
xml: messageXml,
|
||||||
|
medusaOrderId,
|
||||||
|
});
|
||||||
await sendPrivateMessageTestResponse({ messageXml });
|
await sendPrivateMessageTestResponse({ messageXml });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending private message test response: ", error);
|
console.error("Error sending private message test response: ", error);
|
||||||
|
|||||||
41
app/home/(user)/(dashboard)/booking/[handle]/page.tsx
Normal file
41
app/home/(user)/(dashboard)/booking/[handle]/page.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { HomeLayoutPageHeader } from '@/app/home/(user)/_components/home-page-header';
|
||||||
|
import { loadCategory } from '@/app/home/(user)/_lib/server/load-category';
|
||||||
|
|
||||||
|
import { AppBreadcrumbs } from '@kit/ui/makerkit/app-breadcrumbs';
|
||||||
|
import { PageBody } from '@kit/ui/page';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
const i18n = await createI18nServerInstance();
|
||||||
|
const title = i18n.t('booking:title');
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
async function BookingHandlePage({ params }: { params: { handle: string } }) {
|
||||||
|
const handle = await params.handle;
|
||||||
|
const { category } = await loadCategory({ handle });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppBreadcrumbs
|
||||||
|
values={{
|
||||||
|
[handle]: category?.name || handle,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<HomeLayoutPageHeader
|
||||||
|
title={<Trans i18nKey={'booking:title'} />}
|
||||||
|
description={<Trans i18nKey={'booking:description'} />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PageBody></PageBody>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n(BookingHandlePage);
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
|
import { use } from 'react';
|
||||||
|
|
||||||
|
import { AppBreadcrumbs } from '@kit/ui/makerkit/app-breadcrumbs';
|
||||||
import { PageBody } from '@kit/ui/page';
|
import { PageBody } from '@kit/ui/page';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
|
||||||
import { withI18n } from '~/lib/i18n/with-i18n';
|
import { withI18n } from '~/lib/i18n/with-i18n';
|
||||||
|
|
||||||
|
|
||||||
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
|
import { HomeLayoutPageHeader } from '../../_components/home-page-header';
|
||||||
import OrderCards from '../../_components/order-cards';
|
import OrderCards from '../../_components/order-cards';
|
||||||
|
import ServiceCategories from '../../_components/service-categories';
|
||||||
|
import { loadTtoServices } from '../../_lib/server/load-tto-services';
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
const i18n = await createI18nServerInstance();
|
const i18n = await createI18nServerInstance();
|
||||||
@@ -18,15 +22,30 @@ export const generateMetadata = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function BookingPage() {
|
function BookingPage() {
|
||||||
|
const { heroCategories, ttoCategories } = use(loadTtoServices());
|
||||||
|
|
||||||
|
if (!heroCategories.length && !ttoCategories.length) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppBreadcrumbs />
|
||||||
|
<h3 className="mt-8">
|
||||||
|
<Trans i18nKey="booking:noCategories" />
|
||||||
|
</h3>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<AppBreadcrumbs />
|
||||||
<HomeLayoutPageHeader
|
<HomeLayoutPageHeader
|
||||||
title={<Trans i18nKey={'booking:title'} />}
|
title={<Trans i18nKey={'booking:title'} />}
|
||||||
description={<Trans i18nKey={'booking:description'} />}
|
description={<Trans i18nKey={'booking:description'} />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageBody>
|
<PageBody className="space-y-2">
|
||||||
<OrderCards />
|
<OrderCards heroCategories={heroCategories} />
|
||||||
|
<ServiceCategories categories={ttoCategories} />
|
||||||
</PageBody>
|
</PageBody>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ export async function HomeMenuNavigation(props: {
|
|||||||
})
|
})
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const cartItemsCount = props.cart?.items?.length ?? 0;
|
const cartQuantityTotal = props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0;
|
||||||
const hasCartItems = cartItemsCount > 0;
|
const hasCartItems = cartQuantityTotal > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex w-full flex-1 items-center justify-between gap-3'}>
|
<div className={'flex w-full flex-1 items-center justify-between gap-3'}>
|
||||||
@@ -64,7 +64,7 @@ export async function HomeMenuNavigation(props: {
|
|||||||
<ShoppingCart className="stroke-[1.5px]" />
|
<ShoppingCart className="stroke-[1.5px]" />
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="common:shoppingCartCount"
|
i18nKey="common:shoppingCartCount"
|
||||||
values={{ count: cartItemsCount }}
|
values={{ count: cartQuantityTotal }}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ export function HomeMobileNavigation(props: {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const cartItemsCount = props.cart?.items?.length ?? 0;
|
const cartQuantityTotal = props.cart?.items?.reduce((acc, item) => acc + item.quantity, 0) ?? 0;
|
||||||
const hasCartItems = cartItemsCount > 0;
|
const hasCartItems = cartQuantityTotal > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -83,7 +83,7 @@ export function HomeMobileNavigation(props: {
|
|||||||
path="/home/cart"
|
path="/home/cart"
|
||||||
label="common:shoppingCartCount"
|
label="common:shoppingCartCount"
|
||||||
Icon={<ShoppingCart className="stroke-[1.5px]" />}
|
Icon={<ShoppingCart className="stroke-[1.5px]" />}
|
||||||
labelOptions={{ count: cartItemsCount }}
|
labelOptions={{ count: cartQuantityTotal }}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import {
|
|||||||
import { StoreProduct } from '@medusajs/types';
|
import { StoreProduct } from '@medusajs/types';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { handleAddToCart } from '~/lib/services/medusaCart.service';
|
import { handleAddToCart } from '~/lib/services/medusaCart.service';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
|
import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
import { toast } from '@kit/ui/sonner';
|
||||||
|
|
||||||
export type OrderAnalysisCard = Pick<
|
export type OrderAnalysisCard = Pick<
|
||||||
StoreProduct, 'title' | 'description' | 'subtitle'
|
StoreProduct, 'title' | 'description' | 'subtitle'
|
||||||
@@ -30,8 +30,6 @@ export default function OrderAnalysesCards({
|
|||||||
analyses: OrderAnalysisCard[];
|
analyses: OrderAnalysisCard[];
|
||||||
countryCode: string;
|
countryCode: string;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const [isAddingToCart, setIsAddingToCart] = useState(false);
|
const [isAddingToCart, setIsAddingToCart] = useState(false);
|
||||||
const handleSelect = async (variantId: string) => {
|
const handleSelect = async (variantId: string) => {
|
||||||
if (isAddingToCart) {
|
if (isAddingToCart) {
|
||||||
@@ -44,9 +42,10 @@ export default function OrderAnalysesCards({
|
|||||||
selectedVariant: { id: variantId },
|
selectedVariant: { id: variantId },
|
||||||
countryCode,
|
countryCode,
|
||||||
});
|
});
|
||||||
|
toast.success(<Trans i18nKey={'order-analysis:analysisAddedToCart'} />);
|
||||||
setIsAddingToCart(false);
|
setIsAddingToCart(false);
|
||||||
router.push('/home/cart');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
toast.error(<Trans i18nKey={'order-analysis:analysisAddToCartError'} />);
|
||||||
setIsAddingToCart(false);
|
setIsAddingToCart(false);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +1,68 @@
|
|||||||
"use client";
|
'use client';
|
||||||
|
|
||||||
import { ChevronRight, HeartPulse } from 'lucide-react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { pathsConfig } from '@/packages/shared/src/config';
|
||||||
|
import { ComponentInstanceIcon } from '@radix-ui/react-icons';
|
||||||
|
import { ChevronRight, HeartPulse } from 'lucide-react';
|
||||||
|
|
||||||
import { Button } from '@kit/ui/button';
|
import { Button } from '@kit/ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
|
||||||
CardDescription,
|
CardDescription,
|
||||||
CardProps,
|
|
||||||
CardFooter,
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardProps,
|
||||||
} from '@kit/ui/card';
|
} from '@kit/ui/card';
|
||||||
import { Trans } from '@kit/ui/trans';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
const dummyCards = [
|
import { ServiceCategory } from './service-categories';
|
||||||
{
|
|
||||||
title: 'booking:analysisPackages.title',
|
|
||||||
description: 'booking:analysisPackages.description',
|
|
||||||
descriptionColor: 'text-primary',
|
|
||||||
icon: (
|
|
||||||
<Link href={'/home/order-analysis-package'}>
|
|
||||||
<Button size="icon" variant="outline" className="px-2 text-black">
|
|
||||||
<ChevronRight className="size-4 stroke-2" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
cardVariant: 'gradient-success' as CardProps['variant'],
|
|
||||||
iconBg: 'bg-warning',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function OrderCards() {
|
export default function OrderCards({
|
||||||
|
heroCategories,
|
||||||
|
}: {
|
||||||
|
heroCategories: ServiceCategory[];
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-3 gap-6 mt-4">
|
<div className="xs:grid-cols-3 mt-4 grid grid-cols-1 gap-2">
|
||||||
{dummyCards.map(({
|
{heroCategories.map(({ name, description, color, handle }) => (
|
||||||
title,
|
|
||||||
description,
|
|
||||||
icon,
|
|
||||||
cardVariant,
|
|
||||||
descriptionColor,
|
|
||||||
iconBg,
|
|
||||||
}) => (
|
|
||||||
<Card
|
<Card
|
||||||
key={title}
|
key={name}
|
||||||
variant={cardVariant}
|
variant={`gradient-${color}` as CardProps['variant']}
|
||||||
className="flex flex-col justify-between"
|
className="flex flex-col justify-between"
|
||||||
>
|
>
|
||||||
<CardHeader className="items-end-safe">
|
<CardHeader className="relative flex flex-row justify-between">
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex size-8 items-center-safe justify-center-safe rounded-full text-white',
|
'flex size-8 items-center-safe justify-center-safe rounded-full',
|
||||||
iconBg,
|
`text-${color}`,
|
||||||
|
`bg-${color}/10`,
|
||||||
|
{
|
||||||
|
'bg-primary/10': color === 'success',
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{icon}
|
<ComponentInstanceIcon
|
||||||
|
className={cn('size-4', `fill-${color}`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-2 right-2 flex size-8 items-center-safe justify-center-safe rounded-xl text-white">
|
||||||
|
<Link
|
||||||
|
href={pathsConfig.app.bookingHandle.replace('[handle]', handle)}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
className="px-2 text-black"
|
||||||
|
>
|
||||||
|
<ChevronRight className="size-4 stroke-2" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="flex flex-col items-start gap-2">
|
<CardFooter className="mt-5 flex flex-col items-start gap-2">
|
||||||
<div
|
<h5>{name}</h5>
|
||||||
className={'flex size-8 items-center-safe justify-center-safe rounded-full text-white bg-primary\/10 mb-6'}
|
<CardDescription>{description}</CardDescription>
|
||||||
>
|
|
||||||
<HeartPulse className="size-4 fill-green-500" />
|
|
||||||
</div>
|
|
||||||
<h5>
|
|
||||||
<Trans i18nKey={title} />
|
|
||||||
</h5>
|
|
||||||
<CardDescription className={descriptionColor}>
|
|
||||||
<Trans i18nKey={description} />
|
|
||||||
</CardDescription>
|
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
18
app/home/(user)/_components/orders/actions.ts
Normal file
18
app/home/(user)/_components/orders/actions.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { createPageViewLog, PageViewAction } from "~/lib/services/audit/pageView.service";
|
||||||
|
import { loadCurrentUserAccount } from "../../_lib/server/load-user-account";
|
||||||
|
|
||||||
|
export async function logAnalysisResultsNavigateAction(analysisOrderId: string) {
|
||||||
|
const account = await loadCurrentUserAccount();
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('Account not found');
|
||||||
|
}
|
||||||
|
await createPageViewLog({
|
||||||
|
accountId: account.id,
|
||||||
|
action: PageViewAction.VIEW_ANALYSIS_RESULTS_FROM_ORDER,
|
||||||
|
extraData: {
|
||||||
|
analysisOrderId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -10,18 +12,26 @@ import {
|
|||||||
import { StoreOrderLineItem } from "@medusajs/types";
|
import { StoreOrderLineItem } from "@medusajs/types";
|
||||||
import { AnalysisOrder } from '~/lib/services/order.service';
|
import { AnalysisOrder } from '~/lib/services/order.service';
|
||||||
import { formatDate } from 'date-fns';
|
import { formatDate } from 'date-fns';
|
||||||
import Link from 'next/link';
|
|
||||||
import { Eye } from 'lucide-react';
|
import { Eye } from 'lucide-react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { logAnalysisResultsNavigateAction } from './actions';
|
||||||
|
|
||||||
export default function OrderItemsTable({ items, title, analysisOrder }: {
|
export default function OrderItemsTable({ items, title, analysisOrder }: {
|
||||||
items: StoreOrderLineItem[];
|
items: StoreOrderLineItem[];
|
||||||
title: string;
|
title: string;
|
||||||
analysisOrder: AnalysisOrder;
|
analysisOrder: AnalysisOrder;
|
||||||
}) {
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
if (!items || items.length === 0) {
|
if (!items || items.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openAnalysisResults = async () => {
|
||||||
|
await logAnalysisResultsNavigateAction(analysisOrder.medusa_order_id);
|
||||||
|
router.push(`/home/analysis-results`);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table className="rounded-lg border border-separate">
|
<Table className="rounded-lg border border-separate">
|
||||||
<TableHeader className="text-ui-fg-subtle txt-medium-plus">
|
<TableHeader className="text-ui-fg-subtle txt-medium-plus">
|
||||||
@@ -60,13 +70,12 @@ export default function OrderItemsTable({ items, title, analysisOrder }: {
|
|||||||
|
|
||||||
<TableCell className="text-right px-6">
|
<TableCell className="text-right px-6">
|
||||||
<span className="flex gap-x-1 justify-end w-[30px]">
|
<span className="flex gap-x-1 justify-end w-[30px]">
|
||||||
<Link href={`/home/analysis-results`} className="flex items-center justify-between text-small-regular">
|
<button
|
||||||
<button
|
className="flex gap-x-1 text-ui-fg-subtle hover:text-ui-fg-base cursor-pointer "
|
||||||
className="flex gap-x-1 text-ui-fg-subtle hover:text-ui-fg-base cursor-pointer"
|
onClick={openAnalysisResults}
|
||||||
>
|
>
|
||||||
<Eye />
|
<Eye />
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
|
||||||
</span>
|
</span>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|||||||
61
app/home/(user)/_components/service-categories.tsx
Normal file
61
app/home/(user)/_components/service-categories.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
import { createPath, pathsConfig } from '@/packages/shared/src/config';
|
||||||
|
import { ComponentInstanceIcon } from '@radix-ui/react-icons';
|
||||||
|
|
||||||
|
import { cn } from '@kit/ui/shadcn';
|
||||||
|
import { Card, CardDescription, CardTitle } from '@kit/ui/shadcn/card';
|
||||||
|
|
||||||
|
export interface ServiceCategory {
|
||||||
|
name: string;
|
||||||
|
handle: string;
|
||||||
|
color: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServiceCategories = ({
|
||||||
|
categories,
|
||||||
|
}: {
|
||||||
|
categories: ServiceCategory[];
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||||
|
{categories.map((category, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
className="flex cursor-pointer gap-2 p-4 shadow hover:shadow-md"
|
||||||
|
onClick={() => {
|
||||||
|
redirect(
|
||||||
|
pathsConfig.app.bookingHandle.replace(
|
||||||
|
'[handle]',
|
||||||
|
category.handle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex size-8 items-center-safe justify-center-safe rounded-full',
|
||||||
|
`bg-${category.color}/10`,
|
||||||
|
`text-${category.color}`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ComponentInstanceIcon />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-2 text-lg font-semibold">{category.name}</h5>
|
||||||
|
<CardDescription className="">
|
||||||
|
{category.description}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServiceCategories;
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { cache } from 'react';
|
import { cache } from 'react';
|
||||||
|
|
||||||
import { listProductTypes } from "@lib/data/products";
|
|
||||||
import { listRegions } from '@lib/data/regions';
|
|
||||||
import { getProductCategories } from '@lib/data/categories';
|
import { getProductCategories } from '@lib/data/categories';
|
||||||
|
import { listProductTypes } from '@lib/data/products';
|
||||||
|
import { listRegions } from '@lib/data/regions';
|
||||||
|
|
||||||
import { OrderAnalysisCard } from '../../_components/order-analyses-cards';
|
import { OrderAnalysisCard } from '../../_components/order-analyses-cards';
|
||||||
|
import { ServiceCategory } from '../../_components/service-categories';
|
||||||
|
|
||||||
async function countryCodesLoader() {
|
async function countryCodesLoader() {
|
||||||
const countryCodes = await listRegions().then((regions) =>
|
const countryCodes = await listRegions().then((regions) =>
|
||||||
@@ -14,7 +16,9 @@ async function countryCodesLoader() {
|
|||||||
export const loadCountryCodes = cache(countryCodesLoader);
|
export const loadCountryCodes = cache(countryCodesLoader);
|
||||||
|
|
||||||
async function productCategoriesLoader() {
|
async function productCategoriesLoader() {
|
||||||
const productCategories = await getProductCategories({ fields: "*products, *products.variants" });
|
const productCategories = await getProductCategories({
|
||||||
|
fields: '*products, *products.variants, is_active',
|
||||||
|
});
|
||||||
return productCategories.product_categories ?? [];
|
return productCategories.product_categories ?? [];
|
||||||
}
|
}
|
||||||
export const loadProductCategories = cache(productCategoriesLoader);
|
export const loadProductCategories = cache(productCategoriesLoader);
|
||||||
@@ -32,22 +36,31 @@ async function analysesLoader() {
|
|||||||
]);
|
]);
|
||||||
const countryCode = countryCodes[0]!;
|
const countryCode = countryCodes[0]!;
|
||||||
|
|
||||||
const category = productCategories.find(({ metadata }) => metadata?.page === 'order-analysis');
|
const category = productCategories.find(
|
||||||
|
({ metadata }) => metadata?.page === 'order-analysis',
|
||||||
|
);
|
||||||
|
const serviceCategories = productCategories.filter(
|
||||||
|
({ parent_category }) => parent_category?.handle === 'tto-categories',
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
analyses: category?.products?.map<OrderAnalysisCard>(({ title, description, subtitle, variants, status, metadata }) => {
|
analyses:
|
||||||
const variant = variants![0]!;
|
category?.products?.map<OrderAnalysisCard>(
|
||||||
return {
|
({ title, description, subtitle, variants, status, metadata }) => {
|
||||||
title,
|
const variant = variants![0]!;
|
||||||
description,
|
return {
|
||||||
subtitle,
|
title,
|
||||||
variant: {
|
description,
|
||||||
id: variant.id,
|
subtitle,
|
||||||
|
variant: {
|
||||||
|
id: variant.id,
|
||||||
|
},
|
||||||
|
isAvailable:
|
||||||
|
status === 'published' && !!metadata?.analysisIdOriginal,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
isAvailable: status === 'published' && !!metadata?.analysisIdOriginal,
|
) ?? [],
|
||||||
};
|
|
||||||
}) ?? [],
|
|
||||||
countryCode,
|
countryCode,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
export const loadAnalyses = cache(analysesLoader);
|
export const loadAnalyses = cache(analysesLoader);
|
||||||
|
|||||||
@@ -38,8 +38,11 @@ function userSpecificVariantLoader({
|
|||||||
if (age >= 18 && age <= 29) {
|
if (age >= 18 && age <= 29) {
|
||||||
return '18-29';
|
return '18-29';
|
||||||
}
|
}
|
||||||
if (age >= 30 && age <= 49) {
|
if (age >= 30 && age <= 39) {
|
||||||
return '30-49';
|
return '30-39';
|
||||||
|
}
|
||||||
|
if (age >= 40 && age <= 49) {
|
||||||
|
return '40-49';
|
||||||
}
|
}
|
||||||
if (age >= 50 && age <= 59) {
|
if (age >= 50 && age <= 59) {
|
||||||
return '50-59';
|
return '50-59';
|
||||||
|
|||||||
31
app/home/(user)/_lib/server/load-category.ts
Normal file
31
app/home/(user)/_lib/server/load-category.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { cache } from 'react';
|
||||||
|
|
||||||
|
import { getProductCategories } from '@lib/data';
|
||||||
|
|
||||||
|
import { ServiceCategory } from '../../_components/service-categories';
|
||||||
|
|
||||||
|
async function categoryLoader({
|
||||||
|
handle,
|
||||||
|
}: {
|
||||||
|
handle: string;
|
||||||
|
}): Promise<{ category: ServiceCategory | null }> {
|
||||||
|
const response = await getProductCategories({
|
||||||
|
handle,
|
||||||
|
fields: '*products, is_active, metadata',
|
||||||
|
});
|
||||||
|
|
||||||
|
const category = response.product_categories[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
category: {
|
||||||
|
color:
|
||||||
|
typeof category?.metadata?.color === 'string'
|
||||||
|
? category?.metadata?.color
|
||||||
|
: 'primary',
|
||||||
|
description: category?.description || '',
|
||||||
|
handle: category?.handle || '',
|
||||||
|
name: category?.name || '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export const loadCategory = cache(categoryLoader);
|
||||||
49
app/home/(user)/_lib/server/load-tto-services.ts
Normal file
49
app/home/(user)/_lib/server/load-tto-services.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { cache } from 'react';
|
||||||
|
|
||||||
|
import { getProductCategories } from '@lib/data';
|
||||||
|
|
||||||
|
import { ServiceCategory } from '../../_components/service-categories';
|
||||||
|
|
||||||
|
async function ttoServicesLoader() {
|
||||||
|
const response = await getProductCategories({
|
||||||
|
fields: '*products, is_active, metadata',
|
||||||
|
});
|
||||||
|
|
||||||
|
const heroCategories = response.product_categories?.filter(
|
||||||
|
({ parent_category, is_active, metadata }) =>
|
||||||
|
parent_category?.handle === 'tto-categories' &&
|
||||||
|
is_active &&
|
||||||
|
metadata?.isHero,
|
||||||
|
);
|
||||||
|
|
||||||
|
const ttoCategories = response.product_categories?.filter(
|
||||||
|
({ parent_category, is_active, metadata }) =>
|
||||||
|
parent_category?.handle === 'tto-categories' &&
|
||||||
|
is_active &&
|
||||||
|
!metadata?.isHero,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
heroCategories:
|
||||||
|
heroCategories.map<ServiceCategory>(
|
||||||
|
({ name, handle, metadata, description }) => ({
|
||||||
|
name,
|
||||||
|
handle,
|
||||||
|
color:
|
||||||
|
typeof metadata?.color === 'string' ? metadata.color : 'primary',
|
||||||
|
description,
|
||||||
|
}),
|
||||||
|
) ?? [],
|
||||||
|
ttoCategories:
|
||||||
|
ttoCategories.map<ServiceCategory>(
|
||||||
|
({ name, handle, metadata, description }) => ({
|
||||||
|
name,
|
||||||
|
handle,
|
||||||
|
color:
|
||||||
|
typeof metadata?.color === 'string' ? metadata.color : 'primary',
|
||||||
|
description,
|
||||||
|
}),
|
||||||
|
) ?? [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export const loadTtoServices = cache(ttoServicesLoader);
|
||||||
@@ -2,6 +2,7 @@ import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
|||||||
|
|
||||||
export enum PageViewAction {
|
export enum PageViewAction {
|
||||||
VIEW_ANALYSIS_RESULTS = 'VIEW_ANALYSIS_RESULTS',
|
VIEW_ANALYSIS_RESULTS = 'VIEW_ANALYSIS_RESULTS',
|
||||||
|
VIEW_ANALYSIS_RESULTS_FROM_ORDER = 'VIEW_ANALYSIS_RESULTS_FROM_ORDER',
|
||||||
REGISTRATION_SUCCESS = 'REGISTRATION_SUCCESS',
|
REGISTRATION_SUCCESS = 'REGISTRATION_SUCCESS',
|
||||||
VIEW_ORDER_ANALYSIS = 'VIEW_ORDER_ANALYSIS',
|
VIEW_ORDER_ANALYSIS = 'VIEW_ORDER_ANALYSIS',
|
||||||
VIEW_TEAM_ACCOUNT_DASHBOARD = 'VIEW_TEAM_ACCOUNT_DASHBOARD',
|
VIEW_TEAM_ACCOUNT_DASHBOARD = 'VIEW_TEAM_ACCOUNT_DASHBOARD',
|
||||||
@@ -10,9 +11,11 @@ export enum PageViewAction {
|
|||||||
export const createPageViewLog = async ({
|
export const createPageViewLog = async ({
|
||||||
accountId,
|
accountId,
|
||||||
action,
|
action,
|
||||||
|
extraData,
|
||||||
}: {
|
}: {
|
||||||
accountId: string;
|
accountId: string;
|
||||||
action: PageViewAction;
|
action: PageViewAction;
|
||||||
|
extraData?: Record<string, any>;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
const supabase = getSupabaseServerClient();
|
const supabase = getSupabaseServerClient();
|
||||||
@@ -34,6 +37,7 @@ export const createPageViewLog = async ({
|
|||||||
account_id: accountId,
|
account_id: accountId,
|
||||||
action,
|
action,
|
||||||
changed_by: user.id,
|
changed_by: user.id,
|
||||||
|
extra_data: extraData,
|
||||||
})
|
})
|
||||||
.throwOnError();
|
.throwOnError();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -180,7 +180,10 @@ export async function getPrivateMessage(messageId: string) {
|
|||||||
|
|
||||||
await validateMedipostResponse(data, { canHaveEmptyCode: true });
|
await validateMedipostResponse(data, { canHaveEmptyCode: true });
|
||||||
|
|
||||||
return parseXML(data) as MedipostOrderResponse;
|
return {
|
||||||
|
message: parseXML(data) as MedipostOrderResponse,
|
||||||
|
xml: data as string,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePrivateMessage(messageId: string) {
|
export async function deletePrivateMessage(messageId: string) {
|
||||||
@@ -211,7 +214,9 @@ export async function readPrivateMessageResponse({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const privateMessage = await getLatestPrivateMessageListItem({ excludedMessageIds });
|
const privateMessage = await getLatestPrivateMessageListItem({ excludedMessageIds });
|
||||||
if (!privateMessage) {
|
messageId = privateMessage?.messageId ?? null;
|
||||||
|
|
||||||
|
if (!privateMessage || !messageId) {
|
||||||
return {
|
return {
|
||||||
messageId: null,
|
messageId: null,
|
||||||
hasAnalysisResponse: false,
|
hasAnalysisResponse: false,
|
||||||
@@ -221,40 +226,28 @@ export async function readPrivateMessageResponse({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
messageId = privateMessage.messageId;
|
const { message: privateMessageContent, xml: privateMessageXml } = await getPrivateMessage(
|
||||||
if (!messageId) {
|
|
||||||
return {
|
|
||||||
messageId: null,
|
|
||||||
hasAnalysisResponse: false,
|
|
||||||
hasPartialAnalysisResponse: false,
|
|
||||||
hasFullAnalysisResponse: false,
|
|
||||||
medusaOrderId: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const privateMessageContent = await getPrivateMessage(
|
|
||||||
privateMessage.messageId,
|
privateMessage.messageId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageResponse = privateMessageContent?.Saadetis?.Vastus;
|
const messageResponse = privateMessageContent?.Saadetis?.Vastus;
|
||||||
medusaOrderId = privateMessageContent?.Saadetis?.Tellimus?.ValisTellimuseId || messageResponse?.ValisTellimuseId;
|
medusaOrderId = privateMessageContent?.Saadetis?.Tellimus?.ValisTellimuseId || messageResponse?.ValisTellimuseId;
|
||||||
|
|
||||||
if (!medusaOrderId || !medusaOrderId.toString().startsWith('order_')) {
|
const hasInvalidOrderId = !medusaOrderId || !medusaOrderId.toString().startsWith('order_');
|
||||||
return {
|
|
||||||
messageId,
|
|
||||||
hasAnalysisResponse: false,
|
|
||||||
hasPartialAnalysisResponse: false,
|
|
||||||
hasFullAnalysisResponse: false,
|
|
||||||
medusaOrderId: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!messageResponse) {
|
if (hasInvalidOrderId || !messageResponse) {
|
||||||
|
await createMedipostActionLog({
|
||||||
|
action: 'sync_analysis_results_from_medipost',
|
||||||
|
xml: privateMessageXml,
|
||||||
|
hasAnalysisResults: false,
|
||||||
|
medusaOrderId: hasInvalidOrderId ? undefined : medusaOrderId,
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
messageId,
|
messageId,
|
||||||
hasAnalysisResponse: false,
|
hasAnalysisResponse: false,
|
||||||
hasPartialAnalysisResponse: false,
|
hasPartialAnalysisResponse: false,
|
||||||
hasFullAnalysisResponse: false,
|
hasFullAnalysisResponse: false,
|
||||||
medusaOrderId,
|
medusaOrderId: hasInvalidOrderId ? undefined : medusaOrderId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,12 +738,36 @@ export async function sendOrderToMedipost({
|
|||||||
await sendPrivateMessage(orderXml);
|
await sendPrivateMessage(orderXml);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const isMedipostError = e instanceof MedipostValidationError;
|
const isMedipostError = e instanceof MedipostValidationError;
|
||||||
await logMedipostDispatch({
|
if (isMedipostError) {
|
||||||
medusaOrderId,
|
await logMedipostDispatch({
|
||||||
isSuccess: false,
|
medusaOrderId,
|
||||||
isMedipostError,
|
isSuccess: false,
|
||||||
errorMessage: isMedipostError ? e.response : undefined,
|
isMedipostError,
|
||||||
});
|
errorMessage: e.response,
|
||||||
|
});
|
||||||
|
await createMedipostActionLog({
|
||||||
|
action: 'send_order_to_medipost',
|
||||||
|
xml: orderXml,
|
||||||
|
hasAnalysisResults: false,
|
||||||
|
medusaOrderId,
|
||||||
|
responseXml: e.response,
|
||||||
|
hasError: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await logMedipostDispatch({
|
||||||
|
medusaOrderId,
|
||||||
|
isSuccess: false,
|
||||||
|
isMedipostError,
|
||||||
|
});
|
||||||
|
await createMedipostActionLog({
|
||||||
|
action: 'send_order_to_medipost',
|
||||||
|
xml: orderXml,
|
||||||
|
hasAnalysisResults: false,
|
||||||
|
medusaOrderId,
|
||||||
|
hasError: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
await logMedipostDispatch({
|
await logMedipostDispatch({
|
||||||
@@ -758,6 +775,12 @@ export async function sendOrderToMedipost({
|
|||||||
isSuccess: true,
|
isSuccess: true,
|
||||||
isMedipostError: false,
|
isMedipostError: false,
|
||||||
});
|
});
|
||||||
|
await createMedipostActionLog({
|
||||||
|
action: 'send_order_to_medipost',
|
||||||
|
xml: orderXml,
|
||||||
|
hasAnalysisResults: false,
|
||||||
|
medusaOrderId,
|
||||||
|
});
|
||||||
await updateOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING' });
|
await updateOrderStatus({ medusaOrderId, orderStatus: 'PROCESSING' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,3 +848,37 @@ export async function getOrderedAnalysisElementsIds({
|
|||||||
|
|
||||||
return [...analysisPackageElements, ...orderedAnalysisElements];
|
return [...analysisPackageElements, ...orderedAnalysisElements];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createMedipostActionLog({
|
||||||
|
action,
|
||||||
|
xml,
|
||||||
|
hasAnalysisResults = false,
|
||||||
|
medusaOrderId,
|
||||||
|
responseXml,
|
||||||
|
hasError = false,
|
||||||
|
}: {
|
||||||
|
action:
|
||||||
|
| '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;
|
||||||
|
responseXml?: string | null;
|
||||||
|
hasError?: boolean;
|
||||||
|
}) {
|
||||||
|
await getSupabaseServerAdminClient()
|
||||||
|
.schema('medreport')
|
||||||
|
.from('medipost_actions')
|
||||||
|
.insert({
|
||||||
|
action,
|
||||||
|
xml,
|
||||||
|
has_analysis_results: hasAnalysisResults,
|
||||||
|
medusa_order_id: medusaOrderId,
|
||||||
|
response_xml: responseXml,
|
||||||
|
has_error: hasError,
|
||||||
|
})
|
||||||
|
.select('id')
|
||||||
|
.throwOnError();
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,8 +134,10 @@ export async function getAnalysisOrders({
|
|||||||
|
|
||||||
export async function getAnalysisOrdersAdmin({
|
export async function getAnalysisOrdersAdmin({
|
||||||
orderStatus,
|
orderStatus,
|
||||||
|
medusaOrderId,
|
||||||
}: {
|
}: {
|
||||||
orderStatus?: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status'];
|
orderStatus?: Tables<{ schema: 'medreport' }, 'analysis_orders'>['status'];
|
||||||
|
medusaOrderId?: string | null;
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const query = getSupabaseServerAdminClient()
|
const query = getSupabaseServerAdminClient()
|
||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
@@ -144,6 +146,9 @@ export async function getAnalysisOrdersAdmin({
|
|||||||
if (orderStatus) {
|
if (orderStatus) {
|
||||||
query.eq('status', orderStatus);
|
query.eq('status', orderStatus);
|
||||||
}
|
}
|
||||||
|
if (medusaOrderId) {
|
||||||
|
query.eq('medusa_order_id', medusaOrderId);
|
||||||
|
}
|
||||||
const orders = await query.order('created_at', { ascending: false }).throwOnError();
|
const orders = await query.order('created_at', { ascending: false }).throwOnError();
|
||||||
return orders.data;
|
return orders.data;
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/email-templates/src/components/email-button.tsx
Normal file
16
packages/email-templates/src/components/email-button.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Button } from '@react-email/components';
|
||||||
|
|
||||||
|
export function EmailButton(
|
||||||
|
props: React.PropsWithChildren<{
|
||||||
|
href: string;
|
||||||
|
}>,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className="hover:bg-primary/90 inline-flex w-full items-center justify-center gap-1 rounded bg-[#16a249] py-3 text-center text-[16px] font-semibold whitespace-nowrap text-white no-underline shadow-xs transition-colors focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
||||||
|
href={props.href}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Button,
|
|
||||||
Head,
|
Head,
|
||||||
Html,
|
Html,
|
||||||
Link,
|
|
||||||
Preview,
|
Preview,
|
||||||
Tailwind,
|
Tailwind,
|
||||||
Text,
|
Text,
|
||||||
@@ -13,6 +11,7 @@ import {
|
|||||||
import { BodyStyle } from '../components/body-style';
|
import { BodyStyle } from '../components/body-style';
|
||||||
import CommonFooter from '../components/common-footer';
|
import CommonFooter from '../components/common-footer';
|
||||||
import { EmailContent } from '../components/content';
|
import { EmailContent } from '../components/content';
|
||||||
|
import { EmailButton } from '../components/email-button';
|
||||||
import { EmailHeader } from '../components/header';
|
import { EmailHeader } from '../components/header';
|
||||||
import { EmailHeading } from '../components/heading';
|
import { EmailHeading } from '../components/heading';
|
||||||
import { EmailWrapper } from '../components/wrapper';
|
import { EmailWrapper } from '../components/wrapper';
|
||||||
@@ -72,11 +71,12 @@ export async function renderDoctorSummaryReceivedEmail({
|
|||||||
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
<Text className="text-[16px] leading-[24px] text-[#242424]">
|
||||||
{t(`${namespace}:summaryReceivedForOrder`, { orderNr })}
|
{t(`${namespace}:summaryReceivedForOrder`, { orderNr })}
|
||||||
</Text>
|
</Text>
|
||||||
<Link
|
|
||||||
|
<EmailButton
|
||||||
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/order/${orderId}`}
|
href={`${process.env.NEXT_PUBLIC_SITE_URL}/home/order/${orderId}`}
|
||||||
>
|
>
|
||||||
<Button> {t(`${namespace}:linkText`, { orderNr })}</Button>
|
{t(`${namespace}:linkText`, { orderNr })}
|
||||||
</Link>
|
</EmailButton>
|
||||||
<Text>
|
<Text>
|
||||||
{t(`${namespace}:ifButtonDisabled`)}{' '}
|
{t(`${namespace}:ifButtonDisabled`)}{' '}
|
||||||
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/order/${orderId}`}
|
{`${process.env.NEXT_PUBLIC_SITE_URL}/home/order/${orderId}`}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { sdk } from "@lib/config"
|
import { sdk } from "@lib/config";
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types";
|
||||||
import { getCacheOptions } from "./cookies"
|
import { getCacheOptions } from "./cookies";
|
||||||
|
|
||||||
export const listCategories = async (query?: Record<string, any>) => {
|
export const listCategories = async (query?: Record<string, any>) => {
|
||||||
const next = {
|
const next = {
|
||||||
...(await getCacheOptions("categories")),
|
...(await getCacheOptions("categories")),
|
||||||
}
|
};
|
||||||
|
|
||||||
const limit = query?.limit || 100
|
const limit = query?.limit || 100;
|
||||||
|
|
||||||
return sdk.client
|
return sdk.client
|
||||||
.fetch<{ product_categories: HttpTypes.StoreProductCategory[] }>(
|
.fetch<{ product_categories: HttpTypes.StoreProductCategory[] }>(
|
||||||
@@ -23,8 +23,8 @@ export const listCategories = async (query?: Record<string, any>) => {
|
|||||||
cache: "force-cache",
|
cache: "force-cache",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(({ product_categories }) => product_categories)
|
.then(({ product_categories }) => product_categories);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getCategoryByHandle = async (categoryHandle: string[]) => {
|
export const getCategoryByHandle = async (categoryHandle: string[]) => {
|
||||||
const { product_categories } = await getProductCategories({
|
const { product_categories } = await getProductCategories({
|
||||||
@@ -32,7 +32,7 @@ export const getCategoryByHandle = async (categoryHandle: string[]) => {
|
|||||||
limit: 1,
|
limit: 1,
|
||||||
});
|
});
|
||||||
return product_categories[0];
|
return product_categories[0];
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getProductCategories = async ({
|
export const getProductCategories = async ({
|
||||||
handle,
|
handle,
|
||||||
@@ -45,19 +45,18 @@ export const getProductCategories = async ({
|
|||||||
} = {}) => {
|
} = {}) => {
|
||||||
const next = {
|
const next = {
|
||||||
...(await getCacheOptions("categories")),
|
...(await getCacheOptions("categories")),
|
||||||
}
|
};
|
||||||
|
|
||||||
return sdk.client
|
return sdk.client.fetch<HttpTypes.StoreProductCategoryListResponse>(
|
||||||
.fetch<HttpTypes.StoreProductCategoryListResponse>(
|
`/store/product-categories`,
|
||||||
`/store/product-categories`,
|
{
|
||||||
{
|
query: {
|
||||||
query: {
|
fields,
|
||||||
fields,
|
handle,
|
||||||
handle,
|
limit,
|
||||||
limit,
|
},
|
||||||
},
|
next,
|
||||||
next,
|
//cache: "force-cache",
|
||||||
//cache: "force-cache",
|
}
|
||||||
}
|
);
|
||||||
);
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { StoreProduct } from '@medusajs/types';
|
|||||||
import { Button } from '@medusajs/ui';
|
import { Button } from '@medusajs/ui';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { handleAddToCart } from '../../../../lib/services/medusaCart.service';
|
import { handleAddToCart } from '../../../../lib/services/medusaCart.service';
|
||||||
|
import { toast } from '@kit/ui/sonner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -49,12 +50,19 @@ export default function SelectAnalysisPackage({
|
|||||||
|
|
||||||
const handleSelect = async () => {
|
const handleSelect = async () => {
|
||||||
setIsAddingToCart(true);
|
setIsAddingToCart(true);
|
||||||
await handleAddToCart({
|
try {
|
||||||
selectedVariant: { id: variantId },
|
await handleAddToCart({
|
||||||
countryCode,
|
selectedVariant: { id: variantId },
|
||||||
});
|
countryCode,
|
||||||
setIsAddingToCart(false);
|
});
|
||||||
router.push('/home/cart');
|
setIsAddingToCart(false);
|
||||||
|
toast.success(<Trans i18nKey={'order-analysis-package:analysisPackageAddedToCart'} />);
|
||||||
|
router.push('/home/cart');
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(<Trans i18nKey={'order-analysis-package:analysisPackageAddToCartError'} />);
|
||||||
|
setIsAddingToCart(false);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const PathsSchema = z.object({
|
|||||||
home: z.string().min(1),
|
home: z.string().min(1),
|
||||||
selectPackage: z.string().min(1),
|
selectPackage: z.string().min(1),
|
||||||
booking: z.string().min(1),
|
booking: z.string().min(1),
|
||||||
|
bookingHandle: z.string().min(1),
|
||||||
myOrders: z.string().min(1),
|
myOrders: z.string().min(1),
|
||||||
analysisResults: z.string().min(1),
|
analysisResults: z.string().min(1),
|
||||||
orderAnalysisPackage: z.string().min(1),
|
orderAnalysisPackage: z.string().min(1),
|
||||||
@@ -64,6 +65,7 @@ const pathsConfig = PathsSchema.parse({
|
|||||||
joinTeam: '/join',
|
joinTeam: '/join',
|
||||||
selectPackage: '/select-package',
|
selectPackage: '/select-package',
|
||||||
booking: '/home/booking',
|
booking: '/home/booking',
|
||||||
|
bookingHandle: '/home/booking/[handle]',
|
||||||
orderAnalysisPackage: '/home/order-analysis-package',
|
orderAnalysisPackage: '/home/order-analysis-package',
|
||||||
myOrders: '/home/order',
|
myOrders: '/home/order',
|
||||||
analysisResults: '/home/analysis-results',
|
analysisResults: '/home/analysis-results',
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ export type Database = {
|
|||||||
changed_by: string
|
changed_by: string
|
||||||
created_at: string
|
created_at: string
|
||||||
id: number
|
id: number
|
||||||
|
extra_data?: Json | null
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
account_id: string
|
account_id: string
|
||||||
@@ -206,6 +207,7 @@ export type Database = {
|
|||||||
changed_by: string
|
changed_by: string
|
||||||
created_at?: string
|
created_at?: string
|
||||||
id?: number
|
id?: number
|
||||||
|
extra_data?: Json | null
|
||||||
}
|
}
|
||||||
Update: {
|
Update: {
|
||||||
account_id?: string
|
account_id?: string
|
||||||
@@ -213,6 +215,7 @@ export type Database = {
|
|||||||
changed_by?: string
|
changed_by?: string
|
||||||
created_at?: string
|
created_at?: string
|
||||||
id?: number
|
id?: number
|
||||||
|
extra_data?: Json | null
|
||||||
}
|
}
|
||||||
Relationships: []
|
Relationships: []
|
||||||
}
|
}
|
||||||
@@ -1088,7 +1091,7 @@ export type Database = {
|
|||||||
price: number
|
price: number
|
||||||
price_periods: string | null
|
price_periods: string | null
|
||||||
requires_payment: boolean
|
requires_payment: boolean
|
||||||
sync_id: number
|
sync_id: string | null
|
||||||
updated_at: string | null
|
updated_at: string | null
|
||||||
}
|
}
|
||||||
Insert: {
|
Insert: {
|
||||||
@@ -1107,7 +1110,7 @@ export type Database = {
|
|||||||
price: number
|
price: number
|
||||||
price_periods?: string | null
|
price_periods?: string | null
|
||||||
requires_payment: boolean
|
requires_payment: boolean
|
||||||
sync_id: number
|
sync_id?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
}
|
}
|
||||||
Update: {
|
Update: {
|
||||||
@@ -1126,7 +1129,7 @@ export type Database = {
|
|||||||
price?: number
|
price?: number
|
||||||
price_periods?: string | null
|
price_periods?: string | null
|
||||||
requires_payment?: boolean
|
requires_payment?: boolean
|
||||||
sync_id?: number
|
sync_id?: string | null
|
||||||
updated_at?: string | null
|
updated_at?: string | null
|
||||||
}
|
}
|
||||||
Relationships: [
|
Relationships: [
|
||||||
@@ -1147,7 +1150,7 @@ export type Database = {
|
|||||||
doctor_user_id: string | null
|
doctor_user_id: string | null
|
||||||
id: number
|
id: number
|
||||||
status: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
status: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
||||||
updated_at: string
|
updated_at: string | null
|
||||||
updated_by: string | null
|
updated_by: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
value: string | null
|
value: string | null
|
||||||
@@ -1159,7 +1162,7 @@ export type Database = {
|
|||||||
doctor_user_id?: string | null
|
doctor_user_id?: string | null
|
||||||
id?: number
|
id?: number
|
||||||
status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
||||||
updated_at?: string
|
updated_at?: string | null
|
||||||
updated_by?: string | null
|
updated_by?: string | null
|
||||||
user_id: string
|
user_id: string
|
||||||
value?: string | null
|
value?: string | null
|
||||||
@@ -1171,7 +1174,7 @@ export type Database = {
|
|||||||
doctor_user_id?: string | null
|
doctor_user_id?: string | null
|
||||||
id?: number
|
id?: number
|
||||||
status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
status?: Database["medreport"]["Enums"]["analysis_feedback_status"]
|
||||||
updated_at?: string
|
updated_at?: string | null
|
||||||
updated_by?: string | null
|
updated_by?: string | null
|
||||||
user_id?: string
|
user_id?: string
|
||||||
value?: string | null
|
value?: string | null
|
||||||
@@ -1254,6 +1257,34 @@ export type Database = {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
medipost_actions: {
|
||||||
|
Row: {
|
||||||
|
id: string
|
||||||
|
action: string
|
||||||
|
xml: string
|
||||||
|
has_analysis_results: boolean
|
||||||
|
created_at: string
|
||||||
|
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
|
||||||
|
}
|
||||||
|
Update: {
|
||||||
|
action?: string
|
||||||
|
xml?: string
|
||||||
|
has_analysis_results?: boolean
|
||||||
|
medusa_order_id?: string
|
||||||
|
response_xml?: string
|
||||||
|
has_error?: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
medreport_product_groups: {
|
medreport_product_groups: {
|
||||||
Row: {
|
Row: {
|
||||||
created_at: string
|
created_at: string
|
||||||
@@ -1850,9 +1881,7 @@ export type Database = {
|
|||||||
Returns: Json
|
Returns: Json
|
||||||
}
|
}
|
||||||
create_team_account: {
|
create_team_account: {
|
||||||
Args:
|
Args: { account_name: string; new_personal_code: string }
|
||||||
| { account_name: string }
|
|
||||||
| { account_name: string; new_personal_code: string }
|
|
||||||
Returns: {
|
Returns: {
|
||||||
application_role: Database["medreport"]["Enums"]["application_role"]
|
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||||
city: string | null
|
city: string | null
|
||||||
@@ -1921,6 +1950,15 @@ export type Database = {
|
|||||||
account_id: string
|
account_id: string
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
|
get_latest_medipost_dispatch_state_for_order: {
|
||||||
|
Args: {
|
||||||
|
medusa_order_id: string
|
||||||
|
}
|
||||||
|
Returns: {
|
||||||
|
has_success: boolean
|
||||||
|
action_date: string
|
||||||
|
}
|
||||||
|
}
|
||||||
get_medipost_dispatch_tries: {
|
get_medipost_dispatch_tries: {
|
||||||
Args: { p_medusa_order_id: string }
|
Args: { p_medusa_order_id: string }
|
||||||
Returns: number
|
Returns: number
|
||||||
@@ -2137,6 +2175,21 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Returns: Json
|
Returns: Json
|
||||||
}
|
}
|
||||||
|
sync_analysis_results: {
|
||||||
|
}
|
||||||
|
send_medipost_test_response_for_order: {
|
||||||
|
Args: {
|
||||||
|
medusa_order_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
order_has_medipost_dispatch_error: {
|
||||||
|
Args: {
|
||||||
|
medusa_order_id: string
|
||||||
|
}
|
||||||
|
Returns: {
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Enums: {
|
Enums: {
|
||||||
analysis_feedback_status: "STARTED" | "DRAFT" | "COMPLETED"
|
analysis_feedback_status: "STARTED" | "DRAFT" | "COMPLETED"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"title": "Select analysis package",
|
"title": "Select analysis package",
|
||||||
"noPackagesAvailable": "No packages available",
|
"noPackagesAvailable": "No packages available",
|
||||||
"selectThisPackage": "Select this package",
|
"selectThisPackage": "Select this package",
|
||||||
"selectPackage": "Select package",
|
"selectPackage": "Select package",
|
||||||
"comparePackages": "Compare packages"
|
"comparePackages": "Compare packages",
|
||||||
|
"analysisPackageAddedToCart": "Analysis package added to cart",
|
||||||
|
"analysisPackageAddToCartError": "Adding analysis package to cart failed"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "Select analysis",
|
"title": "Select analysis",
|
||||||
"description": "Select the analysis that suits your needs",
|
"description": "All analysis results will appear within 1-3 days after the blood test.",
|
||||||
"analysisNotAvailable": "Analysis is not available currently"
|
"analysisNotAvailable": "Analysis is not available currently",
|
||||||
|
"analysisAddedToCart": "Analysis added to cart",
|
||||||
|
"analysisAddToCartError": "Adding analysis to cart failed"
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"title": "Vali teenus",
|
"title": "Vali teenus",
|
||||||
"description": "Vali sobiv teenus või pakett vastavalt oma tervisemurele või -eesmärgile.",
|
"description": "Vali sobiv teenus või pakett vastavalt oma tervisemurele või -eesmärgile.",
|
||||||
"analysisPackages": {
|
"analysisPackages": {
|
||||||
"title": "Analüüside paketid",
|
"title": "Analüüside paketid",
|
||||||
"description": "Tutvu personaalsete analüüsi pakettidega ja telli"
|
"description": "Tutvu personaalsete analüüsi pakettidega ja telli"
|
||||||
}
|
},
|
||||||
|
"noCategories": "Teenuste loetelu ei leitud, proovi hiljem uuesti"
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,8 @@
|
|||||||
"dashboard": "Ülevaade",
|
"dashboard": "Ülevaade",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
"application": "Application"
|
"application": "Application",
|
||||||
|
"pickTime": "Vali aeg"
|
||||||
},
|
},
|
||||||
"roles": {
|
"roles": {
|
||||||
"owner": {
|
"owner": {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"title": "Vali analüüsi pakett",
|
"title": "Vali analüüsi pakett",
|
||||||
"noPackagesAvailable": "Teenuste loetelu ei leitud, proovi hiljem uuesti",
|
"noPackagesAvailable": "Teenuste loetelu ei leitud, proovi hiljem uuesti",
|
||||||
"selectThisPackage": "Vali see pakett",
|
"selectThisPackage": "Vali see pakett",
|
||||||
"selectPackage": "Vali pakett",
|
"selectPackage": "Vali pakett",
|
||||||
"comparePackages": "Võrdle pakette"
|
"comparePackages": "Võrdle pakette",
|
||||||
|
"analysisPackageAddedToCart": "Analüüsi pakett lisatud ostukorvi",
|
||||||
|
"analysisPackageAddToCartError": "Analüüsi paketi lisamine ostukorvi ebaõnnestus"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"title": "Vali analüüs",
|
"title": "Vali analüüs",
|
||||||
"description": "Vali enda vajadustele sobiv analüüs",
|
"description": "Kõikide analüüside tulemused ilmuvad 1–3 tööpäeva jooksul peale vere andmist.",
|
||||||
"analysisNotAvailable": "Analüüsi tellimine ei ole hetkel saadaval"
|
"analysisNotAvailable": "Analüüsi tellimine ei ole hetkel saadaval",
|
||||||
|
"analysisAddedToCart": "Analüüs lisatud ostukorvi",
|
||||||
|
"analysisAddToCartError": "Analüüsi lisamine ostukorvi ebaõnnestus"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- Enable required extensions for cron jobs and HTTP requests
|
||||||
|
create extension if not exists pg_cron;
|
||||||
|
create extension if not exists pg_net;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
create extension if not exists pg_net;
|
|
||||||
|
|
||||||
create or replace function medreport.medipost_retry_dispatch(
|
create or replace function medreport.medipost_retry_dispatch(
|
||||||
order_id text
|
order_id text
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION medreport.sync_analysis_results()
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
select net.http_post(
|
||||||
|
url := 'https://test.medreport.ee/api/job/sync-analysis-results',
|
||||||
|
headers := jsonb_build_object(
|
||||||
|
'Content-Type', 'application/json',
|
||||||
|
'x-jobs-api-key', 'fd26ec26-70ed-11f0-9e95-431ac3b15a84'
|
||||||
|
)
|
||||||
|
) as request_id;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
grant execute on function medreport.sync_analysis_results() to service_role;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION medreport.send_medipost_test_response_for_order(medusa_order_id text)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
select net.http_post(
|
||||||
|
url := 'https://test.medreport.ee/api/order/medipost-test-response',
|
||||||
|
headers := jsonb_build_object(
|
||||||
|
'Content-Type', 'application/json',
|
||||||
|
'x-jobs-api-key', 'fd26ec26-70ed-11f0-9e95-431ac3b15a84'
|
||||||
|
),
|
||||||
|
body := jsonb_build_object(
|
||||||
|
'medusaOrderId', medusa_order_id
|
||||||
|
)
|
||||||
|
) as request_id;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
grant execute on function medreport.send_medipost_test_response_for_order(text) to service_role;
|
||||||
@@ -1,12 +1,8 @@
|
|||||||
-- Enable required extensions for cron jobs and HTTP requests
|
|
||||||
create extension if not exists pg_cron;
|
|
||||||
create extension if not exists pg_net;
|
|
||||||
|
|
||||||
-- Schedule the test-medipost-responses job to run every 15 minutes
|
-- Schedule the test-medipost-responses job to run every 15 minutes
|
||||||
select
|
select
|
||||||
cron.schedule(
|
cron.schedule(
|
||||||
'send-test-medipost-responses-every-15-minutes', -- Unique job name
|
'send-test-medipost-responses-every-15-minutes',
|
||||||
'*/15 * * * *', -- Cron schedule: every 15 minutes
|
'*/15 * * * *',
|
||||||
$$
|
$$
|
||||||
select
|
select
|
||||||
net.http_post(
|
net.http_post(
|
||||||
@@ -1,7 +1,3 @@
|
|||||||
-- Enable required extensions for cron jobs and HTTP requests
|
|
||||||
create extension if not exists pg_cron;
|
|
||||||
create extension if not exists pg_net;
|
|
||||||
|
|
||||||
-- Schedule the sync-analysis-results job to run every 15 minutes
|
-- Schedule the sync-analysis-results job to run every 15 minutes
|
||||||
select
|
select
|
||||||
cron.schedule(
|
cron.schedule(
|
||||||
4
supabase/migrations-env-specific/README.md
Normal file
4
supabase/migrations-env-specific/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Migrations that require env specific parameters.
|
||||||
|
|
||||||
|
- JOBS_API_TOKEN
|
||||||
|
- app deploy public or internal URL
|
||||||
2
supabase/migrations/20250827134000_bookings.sql
Normal file
2
supabase/migrations/20250827134000_bookings.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE medreport.connected_online_services
|
||||||
|
ALTER COLUMN sync_id TYPE text USING sync_id::text;
|
||||||
16
supabase/migrations/20250828105844_medipost_actions.sql
Normal file
16
supabase/migrations/20250828105844_medipost_actions.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
CREATE TABLE medreport.medipost_actions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
action VARCHAR(255) NOT NULL,
|
||||||
|
xml VARCHAR(131072),
|
||||||
|
has_analysis_results BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
medusa_order_id VARCHAR(255),
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE medreport.medipost_actions ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
CREATE POLICY "service_role_select" ON medreport.medipost_actions FOR SELECT TO service_role USING (true);
|
||||||
|
CREATE POLICY "service_role_insert" ON medreport.medipost_actions FOR INSERT TO service_role WITH CHECK (true);
|
||||||
|
CREATE POLICY "service_role_update" ON medreport.medipost_actions FOR UPDATE TO service_role USING (true);
|
||||||
|
CREATE POLICY "service_role_delete" ON medreport.medipost_actions FOR DELETE TO service_role USING (true);
|
||||||
|
grant select, insert, update, delete on table medreport.medipost_actions to service_role;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE medreport.medipost_actions ADD COLUMN response_xml VARCHAR(131072);
|
||||||
|
ALTER TABLE medreport.medipost_actions ADD COLUMN has_error BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE audit.page_views ADD COLUMN extra_data JSONB;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION medreport.order_has_medipost_dispatch_error(medusa_order_id text)
|
||||||
|
RETURNS boolean AS $$
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM medreport.medipost_actions
|
||||||
|
WHERE medusa_order_id = $1
|
||||||
|
AND action = 'send_order_to_medipost'
|
||||||
|
AND has_error = true
|
||||||
|
);
|
||||||
|
$$ LANGUAGE sql STABLE;
|
||||||
|
|
||||||
|
grant execute on function medreport.order_has_medipost_dispatch_error(text) to service_role;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION medreport.get_latest_medipost_dispatch_state_for_order(medusa_order_id text)
|
||||||
|
RETURNS TABLE(has_success boolean, action_date timestamp with time zone) AS $$
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
WHEN ma.has_error = false THEN true
|
||||||
|
ELSE false
|
||||||
|
END as has_success,
|
||||||
|
ma.created_at as action_date
|
||||||
|
FROM medreport.medipost_actions ma
|
||||||
|
WHERE ma.medusa_order_id = $1
|
||||||
|
AND ma.action = 'send_order_to_medipost'
|
||||||
|
ORDER BY ma.created_at DESC
|
||||||
|
LIMIT 1;
|
||||||
|
$$ LANGUAGE sql STABLE;
|
||||||
|
|
||||||
|
grant execute on function medreport.get_latest_medipost_dispatch_state_for_order(text) to service_role;
|
||||||
Reference in New Issue
Block a user