add health benefit form

fix super admin
This commit is contained in:
Danel Kungla
2025-07-23 16:33:24 +03:00
parent 2db67b7f20
commit 86b86c6752
43 changed files with 1329 additions and 561 deletions

2
.env
View File

@@ -35,7 +35,7 @@ NEXT_PUBLIC_ENABLE_THEME_TOGGLE=true
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION=true NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION=true
NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=false NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=false
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION=false NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_DELETION=false
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=false NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_BILLING=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=true NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS=true
NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION=true NEXT_PUBLIC_ENABLE_TEAM_ACCOUNTS_CREATION=true
NEXT_PUBLIC_LANGUAGE_PRIORITY=application NEXT_PUBLIC_LANGUAGE_PRIORITY=application

View File

@@ -85,5 +85,11 @@ To access admin pages follow these steps:
- Register new user - Register new user
- Go to Profile and add Multi-Factor Authentication - Go to Profile and add Multi-Factor Authentication
- Sign out and Sign in
- Authenticate with mfa (at current time profile page prompts it again) - Authenticate with mfa (at current time profile page prompts it again)
- update your role. look at `supabase/sql/super-admin.sql`
- Sign out and Sign in
## Company User
- With admin account go to `http://localhost:3000/admin/accounts`
- For Create Company Account to work you need to have rows in `medreport.roles` table. For that you can sql in `supabase/sql/super-admin.sql`

View File

@@ -3,6 +3,7 @@
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import Link from 'next/link'; import Link from 'next/link';
import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown'; import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown';
@@ -29,7 +30,11 @@ const features = {
enableThemeToggle: featuresFlagConfig.enableThemeToggle, enableThemeToggle: featuresFlagConfig.enableThemeToggle,
}; };
export function SiteHeaderAccountSection() { export function SiteHeaderAccountSection({
accounts,
}: {
accounts: UserWorkspace['accounts'];
}) {
const session = useSession(); const session = useSession();
const signOut = useSignOut(); const signOut = useSignOut();
@@ -41,6 +46,7 @@ export function SiteHeaderAccountSection() {
features={features} features={features}
user={session.data.user} user={session.data.user}
signOutRequested={() => signOut.mutateAsync()} signOutRequested={() => signOut.mutateAsync()}
accounts={accounts}
/> />
); );
} }

View File

@@ -1,3 +1,5 @@
import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace';
import { Header } from '@kit/ui/marketing'; import { Header } from '@kit/ui/marketing';
import { AppLogo } from '~/components/app-logo'; import { AppLogo } from '~/components/app-logo';
@@ -5,12 +7,16 @@ import { AppLogo } from '~/components/app-logo';
import { SiteHeaderAccountSection } from './site-header-account-section'; import { SiteHeaderAccountSection } from './site-header-account-section';
import { SiteNavigation } from './site-navigation'; import { SiteNavigation } from './site-navigation';
export function SiteHeader() { export function SiteHeader({
accounts,
}: {
accounts: UserWorkspace['accounts'];
}) {
return ( return (
<Header <Header
logo={<AppLogo />} logo={<AppLogo />}
navigation={<SiteNavigation />} navigation={<SiteNavigation />}
actions={<SiteHeaderAccountSection />} actions={<SiteHeaderAccountSection accounts={accounts} />}
/> />
); );
} }

View File

@@ -1,11 +1,17 @@
import { use } from 'react';
import { SiteFooter } from '~/(marketing)/_components/site-footer'; import { SiteFooter } from '~/(marketing)/_components/site-footer';
import { SiteHeader } from '~/(marketing)/_components/site-header'; import { SiteHeader } from '~/(marketing)/_components/site-header';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
import { loadUserWorkspace } from '../home/(user)/_lib/server/load-user-workspace';
function SiteLayout(props: React.PropsWithChildren) { function SiteLayout(props: React.PropsWithChildren) {
const workspace = use(loadUserWorkspace());
return ( return (
<div className={'flex min-h-[100vh] flex-col'}> <div className={'flex min-h-[100vh] flex-col'}>
<SiteHeader /> <SiteHeader accounts={workspace.accounts} />
{props.children} {props.children}

View File

@@ -3,6 +3,7 @@
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace';
import { LayoutDashboard, Users } from 'lucide-react'; import { LayoutDashboard, Users } from 'lucide-react';
import { import {
@@ -21,10 +22,13 @@ import {
import { AppLogo } from '~/components/app-logo'; import { AppLogo } from '~/components/app-logo';
import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container'; import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container';
export function AdminSidebar() { export function AdminSidebar({
accounts,
}: {
accounts: UserWorkspace['accounts'];
}) {
const path = usePathname(); const path = usePathname();
const { open } = useSidebar(); const { open } = useSidebar();
return ( return (
<Sidebar collapsible="icon"> <Sidebar collapsible="icon">
<SidebarHeader className={'m-2'}> <SidebarHeader className={'m-2'}>
@@ -62,8 +66,8 @@ export function AdminSidebar() {
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>
<ProfileAccountDropdownContainer /> <ProfileAccountDropdownContainer accounts={accounts} />
</SidebarFooter> </SidebarFooter>
</Sidebar> </Sidebar>
); );
} }

View File

@@ -1,8 +1,8 @@
import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs'; import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs';
import { AdminAccountsTable } from '@kit/admin/components/admin-accounts-table'; import { AdminAccountsTable } from '@kit/admin/components/admin-accounts-table';
import { AdminCreateUserDialog } from '@kit/admin/components/admin-create-user-dialog';
import { AdminCreateCompanyDialog } from '@kit/admin/components/admin-create-company-dialog'; import { AdminCreateCompanyDialog } from '@kit/admin/components/admin-create-company-dialog';
import { AdminCreateUserDialog } from '@kit/admin/components/admin-create-user-dialog';
import { AdminGuard } from '@kit/admin/components/admin-guard'; import { AdminGuard } from '@kit/admin/components/admin-guard';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
@@ -24,7 +24,7 @@ export const metadata = {
}; };
async function AccountsPage(props: AdminAccountsPageProps) { async function AccountsPage(props: AdminAccountsPageProps) {
const client = getSupabaseServerClient(); const client = getSupabaseServerClient().schema('medreport');
const searchParams = await props.searchParams; const searchParams = await props.searchParams;
const page = searchParams.page ? parseInt(searchParams.page) : 1; const page = searchParams.page ? parseInt(searchParams.page) : 1;
@@ -33,18 +33,19 @@ async function AccountsPage(props: AdminAccountsPageProps) {
<PageHeader description={<AppBreadcrumbs />}> <PageHeader description={<AppBreadcrumbs />}>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<AdminCreateUserDialog> <AdminCreateUserDialog>
<Button data-test="admin-create-user-button">Create Personal Account</Button> <Button data-test="admin-create-user-button">
Create Personal Account
</Button>
</AdminCreateUserDialog> </AdminCreateUserDialog>
<AdminCreateCompanyDialog> <AdminCreateCompanyDialog>
<Button>Create Company Account</Button> <Button>Create Company Account</Button>
</AdminCreateCompanyDialog> </AdminCreateCompanyDialog>
</div> </div>
</PageHeader> </PageHeader>
<PageBody> <PageBody>
<ServerDataLoader <ServerDataLoader
table={'accounts'} table="accounts"
client={client} client={client}
page={page} page={page}
where={(queryBuilder) => { where={(queryBuilder) => {
@@ -55,7 +56,9 @@ async function AccountsPage(props: AdminAccountsPageProps) {
} }
if (query) { if (query) {
queryBuilder.or(`name.ilike.%${query}%,email.ilike.%${query}%,personal_code.ilike.%${query}%`); queryBuilder.or(
`name.ilike.%${query}%,email.ilike.%${query}%,personal_code.ilike.%${query}%`,
);
} }
return queryBuilder; return queryBuilder;

View File

@@ -8,6 +8,8 @@ import { SidebarProvider } from '@kit/ui/shadcn-sidebar';
import { AdminSidebar } from '~/admin/_components/admin-sidebar'; import { AdminSidebar } from '~/admin/_components/admin-sidebar';
import { AdminMobileNavigation } from '~/admin/_components/mobile-navigation'; import { AdminMobileNavigation } from '~/admin/_components/mobile-navigation';
import { loadUserWorkspace } from '../home/(user)/_lib/server/load-user-workspace';
export const metadata = { export const metadata = {
title: `Super Admin`, title: `Super Admin`,
}; };
@@ -16,12 +18,13 @@ export const dynamic = 'force-dynamic';
export default function AdminLayout(props: React.PropsWithChildren) { export default function AdminLayout(props: React.PropsWithChildren) {
const state = use(getLayoutState()); const state = use(getLayoutState());
const workspace = use(loadUserWorkspace());
return ( return (
<SidebarProvider defaultOpen={state.open}> <SidebarProvider defaultOpen={state.open}>
<Page style={'sidebar'}> <Page style={'sidebar'}>
<PageNavigation> <PageNavigation>
<AdminSidebar /> <AdminSidebar accounts={workspace.accounts} />
</PageNavigation> </PageNavigation>
<PageMobileNavigation> <PageMobileNavigation>

View File

@@ -8,6 +8,8 @@ import { ExternalLink } from '@/public/assets/external-link';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { onUpdateAccount } from '@kit/auth/actions/update-account-actions';
import { UpdateAccountSchema } from '@kit/auth/schemas/update-account.schema';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { Checkbox } from '@kit/ui/checkbox'; import { Checkbox } from '@kit/ui/checkbox';
import { import {
@@ -21,9 +23,6 @@ import {
import { Input } from '@kit/ui/input'; import { Input } from '@kit/ui/input';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { UpdateAccountSchema } from '../_lib/schemas/update-account.schema';
import { onUpdateAccount } from '../_lib/server/update-account';
export function UpdateAccountForm({ user }: { user: User }) { export function UpdateAccountForm({ user }: { user: User }) {
const form = useForm({ const form = useForm({
resolver: zodResolver(UpdateAccountSchema), resolver: zodResolver(UpdateAccountSchema),

View File

@@ -2,6 +2,7 @@ import { use } from 'react';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { retrieveCart } from '@lib/data';
import { z } from 'zod'; import { z } from 'zod';
import { UserWorkspaceContextProvider } from '@kit/accounts/components'; import { UserWorkspaceContextProvider } from '@kit/accounts/components';
@@ -17,7 +18,6 @@ import { HomeMenuNavigation } from '../_components/home-menu-navigation';
import { HomeMobileNavigation } from '../_components/home-mobile-navigation'; import { HomeMobileNavigation } from '../_components/home-mobile-navigation';
import { HomeSidebar } from '../_components/home-sidebar'; import { HomeSidebar } from '../_components/home-sidebar';
import { loadUserWorkspace } from '../_lib/server/load-user-workspace'; import { loadUserWorkspace } from '../_lib/server/load-user-workspace';
import { retrieveCart } from '@lib/data';
function UserHomeLayout({ children }: React.PropsWithChildren) { function UserHomeLayout({ children }: React.PropsWithChildren) {
const state = use(getLayoutState()); const state = use(getLayoutState());

View File

@@ -0,0 +1,83 @@
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@kit/ui/select';
import { Trans } from '@kit/ui/trans';
const HealthBenefitFields = () => {
return (
<div className="flex flex-col gap-3">
<FormField
name="occurance"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey="billing:healthBenefitForm.title" />
</FormLabel>
<FormControl>
<Select {...field} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue
placeholder={<Trans i18nKey="common:formField:occurance" />}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="yearly">
<Trans i18nKey="billing:occurance.yearly" />
</SelectItem>
<SelectItem value="quarterly">
<Trans i18nKey="billing:occurance.quarterly" />
</SelectItem>
<SelectItem value="monthly">
<Trans i18nKey="billing:occurance.monthly" />
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="amount"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:amount'} />
</FormLabel>
<FormControl>
<Input
{...field}
type="number"
onChange={(e) =>
field.onChange(
e.target.value === '' ? null : Number(e.target.value),
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
);
};
export default HealthBenefitFields;

View File

@@ -0,0 +1,111 @@
'use client';
import { UpdateHealthBenefitSchema } from '@/packages/billing/core/src/schema';
import { Database } from '@/packages/supabase/src/database.types';
import { zodResolver } from '@hookform/resolvers/zod';
import { PiggyBankIcon } from 'lucide-react';
import { useForm } from 'react-hook-form';
import { Button } from '@kit/ui/button';
import { Form } from '@kit/ui/form';
import { Spinner } from '@kit/ui/makerkit/spinner';
import { Separator } from '@kit/ui/shadcn/separator';
import { toast } from '@kit/ui/shadcn/sonner';
import { Trans } from '@kit/ui/trans';
import { cn } from '~/lib/utils';
import { updateHealthBenefit } from '../_lib/server/server-actions';
import HealthBenefitFields from './health-benefit-fields';
import YearlyExpensesOverview from './yearly-expenses-overview';
const HealthBenefitForm = ({
account,
companyParams,
}: {
account: Database['medreport']['Tables']['accounts']['Row'];
companyParams: Database['medreport']['Tables']['company_params']['Row'];
}) => {
const form = useForm({
resolver: zodResolver(UpdateHealthBenefitSchema),
mode: 'onChange',
defaultValues: {
occurance: companyParams.benefit_occurance || 'yearly',
amount: companyParams.benefit_amount || 0,
},
});
return (
<Form {...form}>
<form
className="flex flex-col gap-6 px-6 text-left"
onSubmit={form.handleSubmit((data) => {
toast.promise(
() => updateHealthBenefit({ ...data, accountId: account.id }),
{
success: 'success',
error: 'error',
},
);
})}
>
<div className="mt-8 flex items-center justify-between">
<div>
<h4>
<Trans
i18nKey="billing:pageTitle"
values={{ companyName: account.slug }}
/>
</h4>
<p className="text-muted-foreground text-sm">
<Trans i18nKey="billing:description" />
</p>
</div>
<Button type="submit" className="relative">
{form.formState.isSubmitting && (
<div className="absolute inset-0 flex items-center justify-center">
<Spinner />
</div>
)}
<div className={cn({ invisible: form.formState.isSubmitting })}>
<Trans i18nKey="account:saveChanges" />
</div>
</Button>
</div>
<div className="flex flex-row gap-6">
<div className="border-border w-1/3 rounded-lg border">
<div className="p-6">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-orange-100">
<PiggyBankIcon className="h-[32px] w-[32px] stroke-orange-400 stroke-2" />
</div>
<p className="mt-4 text-sm font-medium">
<Trans i18nKey="billing:healthBenefitForm.description" />
</p>
<p className="pt-2 text-2xl font-semibold">
{companyParams.benefit_amount || 0}
</p>
</div>
<Separator />
<div className="p-6">
<HealthBenefitFields />
</div>
</div>
<div className="flex-1">
<YearlyExpensesOverview
employeeCount={22}
companyParams={companyParams}
/>
<p className="text-muted-foreground mt-2 text-sm">
<Trans i18nKey="billing:healthBenefitForm.info" />
</p>
</div>
</div>
</form>
</Form>
);
};
export default HealthBenefitForm;

View File

@@ -0,0 +1,90 @@
import { useMemo } from 'react';
import { Database } from '@/packages/supabase/src/database.types';
import { Trans } from '@kit/ui/makerkit/trans';
import { Separator } from '@kit/ui/separator';
const YearlyExpensesOverview = ({
employeeCount = 0,
companyParams,
}: {
employeeCount?: number;
companyParams: Database['medreport']['Tables']['company_params']['Row'];
}) => {
const monthlyExpensePerEmployee = useMemo(() => {
if (!companyParams.benefit_amount) {
return '0.00';
}
switch (companyParams.benefit_occurance) {
case 'yearly':
return (companyParams.benefit_amount / 12).toFixed(2);
case 'quarterly':
return (companyParams.benefit_amount / 4).toFixed(2);
case 'monthly':
return companyParams.benefit_amount.toFixed(2);
default:
return '0.00';
}
}, [companyParams]);
const maxYearlyExpensePerEmployee = useMemo(() => {
if (!companyParams.benefit_amount) {
return '0.00';
}
switch (companyParams.benefit_occurance) {
case 'yearly':
return companyParams.benefit_amount.toFixed(2);
case 'quarterly':
return (companyParams.benefit_amount * 4).toFixed(2);
case 'monthly':
return (companyParams.benefit_amount * 12).toFixed(2);
default:
return '0.00';
}
}, [companyParams]);
return (
<div className="border-border rounded-lg border p-6">
<h5>
<Trans i18nKey="billing:expensesOverview.title" />
</h5>
<div className="mt-5 flex justify-between">
<p className="text-sm font-medium">
<Trans i18nKey="billing:expensesOverview.monthly" />
</p>
<span className="text-primary text-sm font-bold">
{monthlyExpensePerEmployee}
</span>
</div>
<div className="mt-3 flex justify-between">
<p className="text-sm font-medium">
<Trans i18nKey="billing:expensesOverview.yearly" />
</p>
<span className="text-sm font-medium">
{maxYearlyExpensePerEmployee}
</span>
</div>
<div className="mt-5 mb-3 flex justify-between">
<p className="text-sm font-medium">
<Trans
i18nKey="billing:expensesOverview.total"
values={{ employeeCount: employeeCount || 0 }}
/>
</p>
<span className="text-sm font-medium">
{(Number(maxYearlyExpensePerEmployee) * employeeCount).toFixed(2)}
</span>
</div>
<Separator />
<div className="mt-4 flex justify-between">
<p className="font-semibold">Kokku</p>
<span className="font-semibold">13 200,00 </span>
</div>
</div>
);
};
export default YearlyExpensesOverview;

View File

@@ -2,6 +2,8 @@
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { UpdateHealthBenefitData } from '@/packages/features/team-accounts/src/server/types';
import { enhanceAction } from '@kit/next/actions'; import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
@@ -63,3 +65,13 @@ export const createBillingPortalSession = enhanceAction(
}, },
{}, {},
); );
export const updateHealthBenefit = enhanceAction(
async (data: UpdateHealthBenefitData) => {
const client = getSupabaseServerClient();
const service = createTeamBillingService(client);
await service.updateHealthBenefit(data);
},
{},
);

View File

@@ -2,6 +2,7 @@ import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js'; import { SupabaseClient } from '@supabase/supabase-js';
import { UpdateHealthBenefitData } from '@/packages/features/team-accounts/src/server/types';
import { Database } from '@/packages/supabase/src/database.types'; import { Database } from '@/packages/supabase/src/database.types';
import { z } from 'zod'; import { z } from 'zod';
@@ -292,6 +293,25 @@ class TeamBillingService {
return Promise.reject(error as Error); return Promise.reject(error as Error);
} }
} }
async updateHealthBenefit(data: UpdateHealthBenefitData): Promise<void> {
const api = createTeamAccountsApi(this.client);
const logger = await getLogger();
try {
await api.updateHealthBenefit(data);
} catch (error) {
logger.error(
{
accountId: data.accountId,
error,
name: `billing.updateHealthBenefit`,
},
`Encountered an error while updating the company health benefit`,
);
return Promise.reject(error as Error);
}
}
} }
function getCheckoutSessionReturnUrl(accountSlug: string) { function getCheckoutSessionReturnUrl(accountSlug: string) {

View File

@@ -1,27 +1,12 @@
import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; import { createTeamAccountsApi } from '@/packages/features/team-accounts/src/server/api';
import { getSupabaseServerClient } from '@/packages/supabase/src/clients/server-client';
import {
BillingPortalCard,
CurrentLifetimeOrderCard,
CurrentSubscriptionCard,
} from '@kit/billing-gateway/components';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { If } from '@kit/ui/if';
import { PageBody } from '@kit/ui/page'; import { PageBody } from '@kit/ui/page';
import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils';
import billingConfig from '~/config/billing.config';
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';
// local imports import HealthBenefitForm from './_components/health-benefit-form';
import { TeamAccountLayoutPageHeader } from '../_components/team-account-layout-page-header';
import { loadTeamAccountBillingPage } from '../_lib/server/team-account-billing-page.loader';
import { loadTeamWorkspace } from '../_lib/server/team-account-workspace.loader';
import { TeamAccountCheckoutForm } from './_components/team-account-checkout-form';
import { createBillingPortalSession } from './_lib/server/server-actions';
interface TeamAccountBillingPageProps { interface TeamAccountBillingPageProps {
params: Promise<{ account: string }>; params: Promise<{ account: string }>;
@@ -37,99 +22,16 @@ export const generateMetadata = async () => {
}; };
async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) { async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) {
const account = (await params).account; const accountSlug = (await params).account;
const workspace = await loadTeamWorkspace(account); const api = createTeamAccountsApi(getSupabaseServerClient());
const accountId = workspace.account.id; const account = await api.getTeamAccount(accountSlug);
const companyParams = await api.getTeamAccountParams(account.id);
const [data, customerId] = await loadTeamAccountBillingPage(accountId);
const canManageBilling =
workspace.account.permissions.includes('billing.manage');
const Checkout = () => {
if (!canManageBilling) {
return <CannotManageBillingAlert />;
}
return (
<TeamAccountCheckoutForm customerId={customerId} accountId={accountId} />
);
};
const BillingPortal = () => {
if (!canManageBilling || !customerId) {
return null;
}
return (
<form action={createBillingPortalSession}>
<input type="hidden" name={'accountId'} value={accountId} />
<input type="hidden" name={'slug'} value={account} />
<BillingPortalCard />
</form>
);
};
return ( return (
<> <PageBody>
<TeamAccountLayoutPageHeader <HealthBenefitForm account={account} companyParams={companyParams} />
account={account} </PageBody>
title={<Trans i18nKey={'common:routes.billing'} />}
description={<AppBreadcrumbs />}
/>
<PageBody>
<div
className={cn(`flex w-full flex-col space-y-4`, {
'max-w-2xl': data,
})}
>
<If
condition={data}
fallback={
<div>
<Checkout />
</div>
}
>
{(data) => {
if ('active' in data) {
return (
<CurrentSubscriptionCard
subscription={data}
config={billingConfig}
/>
);
}
return (
<CurrentLifetimeOrderCard order={data} config={billingConfig} />
);
}}
</If>
<BillingPortal />
</div>
</PageBody>
</>
); );
} }
export default withI18n(TeamAccountBillingPage); export default withI18n(TeamAccountBillingPage);
function CannotManageBillingAlert() {
return (
<Alert variant={'warning'}>
<ExclamationTriangleIcon className={'h-4'} />
<AlertTitle>
<Trans i18nKey={'billing:cannotManageBillingAlertTitle'} />
</AlertTitle>
<AlertDescription>
<Trans i18nKey={'billing:cannotManageBillingAlertDescription'} />
</AlertDescription>
</Alert>
);
}

View File

@@ -4,7 +4,10 @@ import { cookies } from 'next/headers';
import { z } from 'zod'; import { z } from 'zod';
import { CompanyGuard, TeamAccountWorkspaceContextProvider } from '@kit/team-accounts/components'; import {
CompanyGuard,
TeamAccountWorkspaceContextProvider,
} from '@kit/team-accounts/components';
import { Page, PageMobileNavigation, PageNavigation } from '@kit/ui/page'; import { Page, PageMobileNavigation, PageNavigation } from '@kit/ui/page';
import { SidebarProvider } from '@kit/ui/shadcn-sidebar'; import { SidebarProvider } from '@kit/ui/shadcn-sidebar';
@@ -124,9 +127,10 @@ function HeaderLayout({
/> />
</PageNavigation> </PageNavigation>
<PageMobileNavigation className={'flex items-center justify-between'}> <PageMobileNavigation
className={'flex items-center justify-between'}
>
<AppLogo /> <AppLogo />
<div className={'flex space-x-4'}> <div className={'flex space-x-4'}>
<TeamAccountLayoutMobileNavigation <TeamAccountLayoutMobileNavigation
userId={data.user.id} userId={data.user.id}

View File

@@ -1,5 +1,7 @@
import { use } from 'react'; import { use } from 'react';
import { CompanyGuard } from '@/packages/features/team-accounts/src/components';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { AppBreadcrumbs } from '@kit/ui/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';
@@ -9,7 +11,6 @@ import { withI18n } from '~/lib/i18n/with-i18n';
import { DashboardDemo } from './_components/dashboard-demo'; import { DashboardDemo } from './_components/dashboard-demo';
import { TeamAccountLayoutPageHeader } from './_components/team-account-layout-page-header'; import { TeamAccountLayoutPageHeader } from './_components/team-account-layout-page-header';
import { CompanyGuard } from '@/packages/features/team-accounts/src/components';
interface TeamAccountHomePageProps { interface TeamAccountHomePageProps {
params: Promise<{ account: string }>; params: Promise<{ account: string }>;
@@ -26,7 +27,7 @@ export const generateMetadata = async () => {
function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { function TeamAccountHomePage({ params }: TeamAccountHomePageProps) {
const account = use(params).account; const account = use(params).account;
console.log('TeamAccountHomePage account', account);
return ( return (
<> <>
<TeamAccountLayoutPageHeader <TeamAccountLayoutPageHeader
@@ -34,7 +35,7 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) {
title={<Trans i18nKey={'common:routes.dashboard'} />} title={<Trans i18nKey={'common:routes.dashboard'} />}
description={<AppBreadcrumbs />} description={<AppBreadcrumbs />}
/> />
<PageBody> <PageBody>
<DashboardDemo /> <DashboardDemo />
</PageBody> </PageBody>
@@ -42,4 +43,4 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) {
); );
} }
export default withI18n(CompanyGuard(TeamAccountHomePage)); export default withI18n(CompanyGuard(TeamAccountHomePage));

View File

@@ -5,3 +5,4 @@ export * from './cancel-subscription-params.schema';
export * from './report-billing-usage.schema'; export * from './report-billing-usage.schema';
export * from './update-subscription-params.schema'; export * from './update-subscription-params.schema';
export * from './query-billing-usage.schema'; export * from './query-billing-usage.schema';
export * from './update-health-benefit.schema';

View File

@@ -0,0 +1,10 @@
import { z } from 'zod';
export const UpdateHealthBenefitSchema = z.object({
occurance: z
.string({
required_error: 'Occurance is required',
})
.nonempty(),
amount: z.number({ required_error: 'Amount is required' }),
});

View File

@@ -6,14 +6,9 @@ import Link from 'next/link';
import type { User } from '@supabase/supabase-js'; import type { User } from '@supabase/supabase-js';
import { import { ChevronsUpDown, Home, LogOut, Shield, UserCircle } from 'lucide-react';
ChevronsUpDown,
Home,
LogOut,
UserCircle,
Shield,
} from 'lucide-react';
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -26,11 +21,11 @@ import { SubMenuModeToggle } from '@kit/ui/mode-toggle';
import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { ProfileAvatar } from '@kit/ui/profile-avatar';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
import { cn } from '@kit/ui/utils'; import { cn } from '@kit/ui/utils';
import { usePersonalAccountData } from '../hooks/use-personal-account-data';
import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar';
import { toTitleCase } from '~/lib/utils'; import { toTitleCase } from '~/lib/utils';
import { usePersonalAccountData } from '../hooks/use-personal-account-data';
const PERSONAL_ACCOUNT_SLUG = 'personal'; const PERSONAL_ACCOUNT_SLUG = 'personal';
export function PersonalAccountDropdown({ export function PersonalAccountDropdown({
@@ -41,7 +36,7 @@ export function PersonalAccountDropdown({
paths, paths,
features, features,
account, account,
accounts = [] accounts = [],
}: { }: {
user: User; user: User;
@@ -104,7 +99,8 @@ export function PersonalAccountDropdown({
className ?? '', className ?? '',
{ {
['active:bg-secondary/50 items-center gap-4 rounded-md' + ['active:bg-secondary/50 items-center gap-4 rounded-md' +
' hover:bg-secondary m-0 transition-colors border-1 rounded-md px-4 py-1 h-10']: showProfileName, ' hover:bg-secondary m-0 h-10 rounded-md border-1 px-4 py-1 transition-colors']:
showProfileName,
}, },
)} )}
> >
@@ -127,7 +123,6 @@ export function PersonalAccountDropdown({
> >
{toTitleCase(displayName)} {toTitleCase(displayName)}
</span> </span>
</div> </div>
<ChevronsUpDown <ChevronsUpDown
@@ -171,7 +166,7 @@ export function PersonalAccountDropdown({
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<If condition={accounts.length > 0}> <If condition={accounts.length > 0}>
<span className='px-2 text-muted-foreground text-xs'> <span className="text-muted-foreground px-2 text-xs">
<Trans <Trans
i18nKey={'teams:yourTeams'} i18nKey={'teams:yourTeams'}
values={{ teamsCount: accounts.length }} values={{ teamsCount: accounts.length }}
@@ -185,12 +180,15 @@ export function PersonalAccountDropdown({
href={`/home/${account.value}`} href={`/home/${account.value}`}
> >
<div className={'flex items-center'}> <div className={'flex items-center'}>
<Avatar className={'rounded-xs h-5 w-5 ' + account.image}> <Avatar className={'h-5 w-5 rounded-xs ' + account.image}>
<AvatarImage {...(account.image && { src: account.image })} /> <AvatarImage
{...(account.image && { src: account.image })}
/>
<AvatarFallback <AvatarFallback
className={cn('rounded-md', { className={cn('rounded-md', {
['bg-background']: PERSONAL_ACCOUNT_SLUG === account.value, ['bg-background']:
PERSONAL_ACCOUNT_SLUG === account.value,
['group-hover:bg-background']: ['group-hover:bg-background']:
PERSONAL_ACCOUNT_SLUG !== account.value, PERSONAL_ACCOUNT_SLUG !== account.value,
})} })}
@@ -199,9 +197,7 @@ export function PersonalAccountDropdown({
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
<span className={'pl-3'}> <span className={'pl-3'}>{account.label}</span>
{account.label}
</span>
</div> </div>
</Link> </Link>
</DropdownMenuItem> </DropdownMenuItem>

View File

@@ -3,11 +3,6 @@ import { BadgeX, Ban, ShieldPlus, VenetianMask } from 'lucide-react';
import { Tables } from '@kit/supabase/database'; import { Tables } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import {
AccountInvitationsTable,
AccountMembersTable,
InviteMembersDialogContainer,
} from '@kit/team-accounts/components';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { Badge } from '@kit/ui/badge'; import { Badge } from '@kit/ui/badge';

View File

@@ -22,9 +22,11 @@ class CreateTeamAccountService {
logger.info(ctx, `Creating new company account...`); logger.info(ctx, `Creating new company account...`);
const { error, data } = await this.client.rpc('create_team_account', { const { error, data } = await this.client
account_name: params.name, .schema('medreport')
}); .rpc('create_team_account', {
account_name: params.name,
});
if (error) { if (error) {
logger.error( logger.error(
@@ -35,7 +37,7 @@ class CreateTeamAccountService {
`Error creating company account`, `Error creating company account`,
); );
throw new Error('Error creating company account'); throw new Error('Error creating company account: ' + error);
} }
logger.info(ctx, `Company account created successfully`); logger.info(ctx, `Company account created successfully`);

View File

@@ -9,7 +9,9 @@ import { Database } from '@kit/supabase/database';
*/ */
export async function isSuperAdmin(client: SupabaseClient<Database>) { export async function isSuperAdmin(client: SupabaseClient<Database>) {
try { try {
const { data, error } = await client.rpc('is_super_admin'); const { data, error } = await client
.schema('medreport')
.rpc('is_super_admin');
if (error) { if (error) {
throw error; throw error;

View File

@@ -18,7 +18,9 @@
"./captcha/server": "./src/captcha/server/index.ts", "./captcha/server": "./src/captcha/server/index.ts",
"./resend-email-link": "./src/components/resend-auth-link-form.tsx", "./resend-email-link": "./src/components/resend-auth-link-form.tsx",
"./lib/utils/*": "./src/lib/utils/*.ts", "./lib/utils/*": "./src/lib/utils/*.ts",
"./api": "./src/server/api.ts" "./api": "./src/server/api.ts",
"./schemas/*": "./src/schemas/*.ts",
"./actions/*": "./src/server/actions/*.ts"
}, },
"devDependencies": { "devDependencies": {
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.0.1",

View File

@@ -0,0 +1,225 @@
'use client';
import Link from 'next/link';
import { User } from '@supabase/supabase-js';
import { ExternalLink } from '@/public/assets/external-link';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { Button } from '@kit/ui/button';
import { Checkbox } from '@kit/ui/checkbox';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { Trans } from '@kit/ui/trans';
import { UpdateAccountSchema } from '../schemas/update-account.schema';
import { onUpdateAccount } from '../server/actions/update-account-actions';
export function UpdateAccountForm({ user }: { user: User }) {
const form = useForm({
resolver: zodResolver(UpdateAccountSchema),
mode: 'onChange',
defaultValues: {
firstName: '',
lastName: '',
personalCode: '',
email: user.email,
phone: '',
city: '',
weight: 0,
height: 0,
userConsent: false,
},
});
return (
<Form {...form}>
<form
className="flex flex-col gap-6 px-6 pt-10 text-left"
onSubmit={form.handleSubmit(onUpdateAccount)}
>
<FormField
name="firstName"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:firstName'} />
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="lastName"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:lastName'} />
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="personalCode"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:personalCode'} />
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:email'} />
</FormLabel>
<FormControl>
<Input {...field} disabled />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:phone'} />
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="city"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:city'} />
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="flex flex-row justify-between gap-4">
<FormField
name="weight"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:weight'} />
</FormLabel>
<FormControl>
<Input
type="number"
placeholder="kg"
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(
e.target.value === '' ? null : Number(e.target.value),
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
name="height"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans i18nKey={'common:formField:height'} />
</FormLabel>
<FormControl>
<Input
placeholder="cm"
type="number"
{...field}
value={field.value ?? ''}
onChange={(e) =>
field.onChange(
e.target.value === '' ? null : Number(e.target.value),
)
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<FormField
name="userConsent"
render={({ field }) => (
<FormItem>
<div className="flex flex-row items-center gap-2 pb-1">
<FormControl>
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormLabel>
<Trans i18nKey={'account:updateAccount:userConsentLabel'} />
</FormLabel>
</div>
<Link
href={''}
className="flex flex-row items-center gap-2 text-sm hover:underline"
target="_blank"
>
<ExternalLink />
<Trans i18nKey={'account:updateAccount:userConsentUrlTitle'} />
</Link>
</FormItem>
)}
/>
<Button disabled={form.formState.isSubmitting} type="submit">
<Trans i18nKey={'account:updateAccount:button'} />
</Button>
</form>
</Form>
);
}

View File

@@ -2,14 +2,15 @@
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { updateCustomer } from '@lib/data/customer';
import { createAuthApi } from '@kit/auth/api'; import { createAuthApi } from '@kit/auth/api';
import { enhanceAction } from '@kit/next/actions'; import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { getSupabaseServerClient } from '@kit/supabase/server-client';
import pathsConfig from '~/config/paths.config'; import pathsConfig from '~/config/paths.config';
import { updateCustomer } from '@lib/data/customer';
import { UpdateAccountSchema } from '../schemas/update-account.schema'; import { UpdateAccountSchema } from '../../schemas/update-account.schema';
export interface AccountSubmitData { export interface AccountSubmitData {
firstName: string; firstName: string;

View File

@@ -2,6 +2,8 @@ import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
import { UpdateHealthBenefitData } from './types';
/** /**
* Class representing an API for interacting with team accounts. * Class representing an API for interacting with team accounts.
* @constructor * @constructor
@@ -254,6 +256,45 @@ export class TeamAccountsApi {
return invitation; return invitation;
} }
/**
* @name updateHealthBenefit
* @description Update health benefits for the team account
*/
async updateHealthBenefit(data: UpdateHealthBenefitData) {
const { error } = await this.client
.schema('medreport')
.from('company_params')
.update({
benefit_occurance: data.occurance,
benefit_amount: data.amount,
updated_at: new Date().toISOString(),
})
.eq('account_id', data.accountId);
if (error) {
throw error;
}
}
/**
* @name getTeamAccountParams
* @description Get health benefits for the team account
*/
async getTeamAccountParams(accountId: string) {
const { data, error } = await this.client
.schema('medreport')
.from('company_params')
.select('*')
.eq('account_id', accountId)
.single();
if (error) {
throw error;
}
return data;
}
} }
export function createTeamAccountsApi(client: SupabaseClient<Database>) { export function createTeamAccountsApi(client: SupabaseClient<Database>) {

View File

@@ -0,0 +1,5 @@
export interface UpdateHealthBenefitData {
accountId: string;
occurance: string;
amount: number;
}

View File

@@ -7,11 +7,16 @@ import { Database } from '@kit/supabase/database';
* @description Check if the current user is a super admin. * @description Check if the current user is a super admin.
* @param client * @param client
*/ */
export async function isCompanyAdmin(client: SupabaseClient<Database>, accountSlug: string) { export async function isCompanyAdmin(
client: SupabaseClient<Database>,
accountSlug: string,
) {
try { try {
const { data, error } = await client.rpc('is_company_admin', { const { data, error } = await client
account_slug: accountSlug, .schema('medreport')
}); .rpc('is_company_admin', {
account_slug: accountSlug,
});
if (error) { if (error) {
throw error; throw error;

View File

@@ -9,6 +9,39 @@ export type Json =
export type Database = { export type Database = {
audit: { audit: {
Tables: { Tables: {
cart_entries: {
Row: {
account_id: string
cart_id: string
changed_by: string
comment: string | null
created_at: string
id: number
operation: string
variant_id: string | null
}
Insert: {
account_id: string
cart_id: string
changed_by: string
comment?: string | null
created_at?: string
id?: number
operation: string
variant_id?: string | null
}
Update: {
account_id?: string
cart_id?: string
changed_by?: string
comment?: string | null
created_at?: string
id?: number
operation?: string
variant_id?: string | null
}
Relationships: []
}
log_entries: { log_entries: {
Row: { Row: {
changed_at: string changed_at: string
@@ -116,29 +149,7 @@ export type Database = {
status?: string status?: string
} }
Relationships: [] Relationships: []
}, }
cart_entries: {
Row: {
id: number
account_id: string
cart_id: string
operation: string
variant_id: string
comment: string | null
created_at: string
changed_by: string | null
}
Insert: {
id: number
account_id: string
cart_id: string
operation: string
variant_id: string
comment: string | null
created_at: string
changed_by: string | null
}
},
} }
Views: { Views: {
[_ in never]: never [_ in never]: never
@@ -685,6 +696,58 @@ export type Database = {
}, },
] ]
} }
company_params: {
Row: {
account_id: string | null
benefit_amount: number | null
benefit_occurance: string | null
created_at: string | null
id: string
slug: string | null
updated_at: string | null
}
Insert: {
account_id?: string | null
benefit_amount?: number | null
benefit_occurance?: string | null
created_at?: string | null
id?: string
slug?: string | null
updated_at?: string | null
}
Update: {
account_id?: string | null
benefit_amount?: number | null
benefit_occurance?: string | null
created_at?: string | null
id?: string
slug?: string | null
updated_at?: string | null
}
Relationships: [
{
foreignKeyName: "company_params_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "accounts"
referencedColumns: ["id"]
},
{
foreignKeyName: "company_params_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "user_account_workspace"
referencedColumns: ["id"]
},
{
foreignKeyName: "company_params_account_id_fkey"
columns: ["account_id"]
isOneToOne: false
referencedRelation: "user_accounts"
referencedColumns: ["id"]
},
]
}
config: { config: {
Row: { Row: {
billing_provider: Database["medreport"]["Enums"]["billing_provider"] billing_provider: Database["medreport"]["Enums"]["billing_provider"]
@@ -1628,6 +1691,10 @@ export type Database = {
Args: { target_account_id: string } Args: { target_account_id: string }
Returns: boolean Returns: boolean
} }
is_company_admin: {
Args: { account_slug: string }
Returns: boolean
}
is_mfa_compliant: { is_mfa_compliant: {
Args: Record<PropertyKey, never> Args: Record<PropertyKey, never>
Returns: boolean Returns: boolean

View File

@@ -119,5 +119,24 @@
}, },
"cart": { "cart": {
"label": "Cart ({{ items }})" "label": "Cart ({{ items }})"
},
"pageTitle": "{{companyName}} eelarve",
"description": "Vali kalendrist sobiv kuupäev ja broneeri endale vastuvõtuaeg.",
"saveChanges": "Salvesta muudatused",
"healthBenefitForm": {
"title": "Tervisetoetuse vorm",
"description": "Ettevõtte Tervisekassa toetus töötajale",
"info": "* Hindadele lisanduvad riigipoolsed maksud"
},
"occurance": {
"yearly": "Kord aastas",
"quarterly": "kord kvartalis",
"monthly": "Kord kuus"
},
"expensesOverview": {
"title": "Kulude ülevaade 2025 aasta raames",
"monthly": "Kulu töötaja kohta kuus *",
"yearly": "Maksimaalne kulu inimese kohta kokku aastas *",
"total": "Maksimaalne kulu {{employeeCount}} töötaja kohta aastas *"
} }
} }

View File

@@ -119,7 +119,9 @@
"personalCode": "Isikukood", "personalCode": "Isikukood",
"city": "Linn", "city": "Linn",
"weight": "Kaal", "weight": "Kaal",
"height": "Pikkus" "height": "Pikkus",
"occurance": "Toetuse sagedus",
"amount": "Summa"
}, },
"wallet": { "wallet": {
"balance": "Sinu MedReporti konto seis", "balance": "Sinu MedReporti konto seis",

View File

@@ -71,11 +71,11 @@
} }
h4 { h4 {
@apply text-2xl; @apply text-xl;
} }
h5 { h5 {
@apply text-xl; @apply text-base;
} }
h6 { h6 {

View File

@@ -1,227 +1,227 @@
-- create table "public"."connected_online_providers" ( create table "public"."connected_online_providers" (
-- "id" bigint not null, "id" bigint not null,
-- "name" text not null, "name" text not null,
-- "email" text, "email" text,
-- "phone_number" text, "phone_number" text,
-- "can_select_worker" boolean not null, "can_select_worker" boolean not null,
-- "personal_code_required" boolean not null, "personal_code_required" boolean not null,
-- "created_at" timestamp with time zone not null default now(), "created_at" timestamp with time zone not null default now(),
-- "updated_at" timestamp without time zone default now() "updated_at" timestamp without time zone default now()
-- ); );
-- alter table "public"."connected_online_providers" enable row level security; alter table "public"."connected_online_providers" enable row level security;
-- create table "public"."connected_online_services" ( create table "public"."connected_online_services" (
-- "id" bigint not null, "id" bigint not null,
-- "clinic_id" bigint not null, "clinic_id" bigint not null,
-- "sync_id" bigint not null, "sync_id" bigint not null,
-- "name" text not null, "name" text not null,
-- "description" text, "description" text,
-- "price" double precision not null, "price" double precision not null,
-- "requires_payment" boolean not null, "requires_payment" boolean not null,
-- "duration" bigint not null, "duration" bigint not null,
-- "neto_duration" bigint, "neto_duration" bigint,
-- "display" text, "display" text,
-- "price_periods" text, "price_periods" text,
-- "online_hide_duration" bigint, "online_hide_duration" bigint,
-- "online_hide_price" bigint, "online_hide_price" bigint,
-- "code" text not null, "code" text not null,
-- "has_free_codes" boolean not null, "has_free_codes" boolean not null,
-- "created_at" timestamp with time zone not null default now(), "created_at" timestamp with time zone not null default now(),
-- "updated_at" timestamp with time zone default now() "updated_at" timestamp with time zone default now()
-- ); );
-- alter table "public"."connected_online_services" enable row level security; alter table "public"."connected_online_services" enable row level security;
-- CREATE UNIQUE INDEX connected_online_providers_id_key ON public.connected_online_providers USING btree (id); CREATE UNIQUE INDEX connected_online_providers_id_key ON public.connected_online_providers USING btree (id);
-- CREATE UNIQUE INDEX connected_online_providers_pkey ON public.connected_online_providers USING btree (id); CREATE UNIQUE INDEX connected_online_providers_pkey ON public.connected_online_providers USING btree (id);
-- CREATE UNIQUE INDEX connected_online_services_id_key ON public.connected_online_services USING btree (id); CREATE UNIQUE INDEX connected_online_services_id_key ON public.connected_online_services USING btree (id);
-- CREATE UNIQUE INDEX connected_online_services_pkey ON public.connected_online_services USING btree (id); CREATE UNIQUE INDEX connected_online_services_pkey ON public.connected_online_services USING btree (id);
-- alter table "public"."connected_online_providers" add constraint "connected_online_providers_pkey" PRIMARY KEY using index "connected_online_providers_pkey"; alter table "public"."connected_online_providers" add constraint "connected_online_providers_pkey" PRIMARY KEY using index "connected_online_providers_pkey";
-- alter table "public"."connected_online_services" add constraint "connected_online_services_pkey" PRIMARY KEY using index "connected_online_services_pkey"; alter table "public"."connected_online_services" add constraint "connected_online_services_pkey" PRIMARY KEY using index "connected_online_services_pkey";
-- alter table "public"."connected_online_providers" add constraint "connected_online_providers_id_key" UNIQUE using index "connected_online_providers_id_key"; alter table "public"."connected_online_providers" add constraint "connected_online_providers_id_key" UNIQUE using index "connected_online_providers_id_key";
-- alter table "public"."connected_online_services" add constraint "connected_online_services_clinic_id_fkey" FOREIGN KEY (clinic_id) REFERENCES connected_online_providers(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."connected_online_services" add constraint "connected_online_services_clinic_id_fkey" FOREIGN KEY (clinic_id) REFERENCES connected_online_providers(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."connected_online_services" validate constraint "connected_online_services_clinic_id_fkey"; alter table "public"."connected_online_services" validate constraint "connected_online_services_clinic_id_fkey";
-- alter table "public"."connected_online_services" add constraint "connected_online_services_id_key" UNIQUE using index "connected_online_services_id_key"; alter table "public"."connected_online_services" add constraint "connected_online_services_id_key" UNIQUE using index "connected_online_services_id_key";
-- grant delete on table "public"."connected_online_providers" to "service_role"; grant delete on table "public"."connected_online_providers" to "service_role";
-- grant insert on table "public"."connected_online_providers" to "service_role"; grant insert on table "public"."connected_online_providers" to "service_role";
-- grant references on table "public"."connected_online_providers" to "service_role"; grant references on table "public"."connected_online_providers" to "service_role";
-- grant select on table "public"."connected_online_providers" to "service_role"; grant select on table "public"."connected_online_providers" to "service_role";
-- grant trigger on table "public"."connected_online_providers" to "service_role"; grant trigger on table "public"."connected_online_providers" to "service_role";
-- grant truncate on table "public"."connected_online_providers" to "service_role"; grant truncate on table "public"."connected_online_providers" to "service_role";
-- grant update on table "public"."connected_online_providers" to "service_role"; grant update on table "public"."connected_online_providers" to "service_role";
-- grant select on table "public"."connected_online_providers" to "authenticated"; grant select on table "public"."connected_online_providers" to "authenticated";
-- grant delete on table "public"."connected_online_services" to "service_role"; grant delete on table "public"."connected_online_services" to "service_role";
-- grant insert on table "public"."connected_online_services" to "service_role"; grant insert on table "public"."connected_online_services" to "service_role";
-- grant references on table "public"."connected_online_services" to "service_role"; grant references on table "public"."connected_online_services" to "service_role";
-- grant select on table "public"."connected_online_services" to "service_role"; grant select on table "public"."connected_online_services" to "service_role";
-- grant trigger on table "public"."connected_online_services" to "service_role"; grant trigger on table "public"."connected_online_services" to "service_role";
-- grant truncate on table "public"."connected_online_services" to "service_role"; grant truncate on table "public"."connected_online_services" to "service_role";
-- grant update on table "public"."connected_online_services" to "service_role"; grant update on table "public"."connected_online_services" to "service_role";
-- grant select on table "public"."connected_online_services" to "authenticated"; grant select on table "public"."connected_online_services" to "authenticated";
-- create type "audit"."request_status" as enum ('SUCCESS', 'FAIL'); create type "audit"."request_status" as enum ('SUCCESS', 'FAIL');
-- create table "audit"."request_entries" ( create table "audit"."request_entries" (
-- "id" bigint generated by default as identity not null, "id" bigint generated by default as identity not null,
-- "personal_code" bigint, "personal_code" bigint,
-- "request_api" text not null, "request_api" text not null,
-- "request_api_method" text not null, "request_api_method" text not null,
-- "status" audit.request_status not null, "status" audit.request_status not null,
-- "comment" text, "comment" text,
-- "service_provider_id" bigint, "service_provider_id" bigint,
-- "service_id" bigint, "service_id" bigint,
-- "requested_start_date" timestamp with time zone, "requested_start_date" timestamp with time zone,
-- "requested_end_date" timestamp with time zone, "requested_end_date" timestamp with time zone,
-- "created_at" timestamp with time zone not null default now() "created_at" timestamp with time zone not null default now()
-- ); );
-- alter table "audit"."request_entries" enable row level security; alter table "audit"."request_entries" enable row level security;
-- CREATE UNIQUE INDEX request_entries_pkey ON audit.request_entries USING btree (id); CREATE UNIQUE INDEX request_entries_pkey ON audit.request_entries USING btree (id);
-- alter table "audit"."request_entries" add constraint "request_entries_pkey" PRIMARY KEY using index "request_entries_pkey"; alter table "audit"."request_entries" add constraint "request_entries_pkey" PRIMARY KEY using index "request_entries_pkey";
-- grant delete on table "audit"."request_entries" to "service_role"; grant delete on table "audit"."request_entries" to "service_role";
-- grant insert on table "audit"."request_entries" to "service_role"; grant insert on table "audit"."request_entries" to "service_role";
-- grant references on table "audit"."request_entries" to "service_role"; grant references on table "audit"."request_entries" to "service_role";
-- grant select on table "audit"."request_entries" to "service_role"; grant select on table "audit"."request_entries" to "service_role";
-- grant trigger on table "audit"."request_entries" to "service_role"; grant trigger on table "audit"."request_entries" to "service_role";
-- grant truncate on table "audit"."request_entries" to "service_role"; grant truncate on table "audit"."request_entries" to "service_role";
-- grant update on table "audit"."request_entries" to "service_role"; grant update on table "audit"."request_entries" to "service_role";
-- create policy "service_role_all" create policy "service_role_all"
-- on "audit"."request_entries" on "audit"."request_entries"
-- as permissive as permissive
-- for all for all
-- to service_role to service_role
-- using (true); using (true);
-- create table "public"."connected_online_reservation" ( create table "public"."connected_online_reservation" (
-- "id" bigint generated by default as identity not null, "id" bigint generated by default as identity not null,
-- "user_id" uuid not null, "user_id" uuid not null,
-- "booking_code" text not null, "booking_code" text not null,
-- "service_id" bigint not null, "service_id" bigint not null,
-- "clinic_id" bigint not null, "clinic_id" bigint not null,
-- "service_user_id" bigint, "service_user_id" bigint,
-- "sync_user_id" bigint not null, "sync_user_id" bigint not null,
-- "requires_payment" boolean not null, "requires_payment" boolean not null,
-- "comments" text, "comments" text,
-- "start_time" timestamp with time zone not null, "start_time" timestamp with time zone not null,
-- "lang" text not null, "lang" text not null,
-- "discount_code" text, "discount_code" text,
-- "created_at" timestamp with time zone not null default now(), "created_at" timestamp with time zone not null default now(),
-- "updated_at" timestamp with time zone default now() "updated_at" timestamp with time zone default now()
-- ); );
-- alter table "public"."connected_online_reservation" enable row level security; alter table "public"."connected_online_reservation" enable row level security;
-- CREATE UNIQUE INDEX connected_online_reservation_booking_code_key ON public.connected_online_reservation USING btree (booking_code); CREATE UNIQUE INDEX connected_online_reservation_booking_code_key ON public.connected_online_reservation USING btree (booking_code);
-- CREATE UNIQUE INDEX connected_online_reservation_pkey ON public.connected_online_reservation USING btree (id); CREATE UNIQUE INDEX connected_online_reservation_pkey ON public.connected_online_reservation USING btree (id);
-- alter table "public"."connected_online_reservation" add constraint "connected_online_reservation_pkey" PRIMARY KEY using index "connected_online_reservation_pkey"; alter table "public"."connected_online_reservation" add constraint "connected_online_reservation_pkey" PRIMARY KEY using index "connected_online_reservation_pkey";
-- alter table "public"."connected_online_reservation" add constraint "connected_online_reservation_booking_code_key" UNIQUE using index "connected_online_reservation_booking_code_key"; alter table "public"."connected_online_reservation" add constraint "connected_online_reservation_booking_code_key" UNIQUE using index "connected_online_reservation_booking_code_key";
-- alter table "public"."connected_online_reservation" add constraint "connected_online_reservation_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."connected_online_reservation" add constraint "connected_online_reservation_user_id_fkey" FOREIGN KEY (user_id) REFERENCES auth.users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."connected_online_reservation" validate constraint "connected_online_reservation_user_id_fkey"; alter table "public"."connected_online_reservation" validate constraint "connected_online_reservation_user_id_fkey";
-- grant delete on table "public"."connected_online_reservation" to "service_role"; grant delete on table "public"."connected_online_reservation" to "service_role";
-- grant insert on table "public"."connected_online_reservation" to "service_role"; grant insert on table "public"."connected_online_reservation" to "service_role";
-- grant references on table "public"."connected_online_reservation" to "service_role"; grant references on table "public"."connected_online_reservation" to "service_role";
-- grant select on table "public"."connected_online_reservation" to "service_role"; grant select on table "public"."connected_online_reservation" to "service_role";
-- grant trigger on table "public"."connected_online_reservation" to "service_role"; grant trigger on table "public"."connected_online_reservation" to "service_role";
-- grant truncate on table "public"."connected_online_reservation" to "service_role"; grant truncate on table "public"."connected_online_reservation" to "service_role";
-- grant update on table "public"."connected_online_reservation" to "service_role"; grant update on table "public"."connected_online_reservation" to "service_role";
-- create policy "service_role_all" create policy "service_role_all"
-- on "public"."connected_online_reservation" on "public"."connected_online_reservation"
-- as permissive as permissive
-- for all for all
-- to service_role to service_role
-- using (true); using (true);
-- CREATE TRIGGER connected_online_providers_change_record_timestamps AFTER INSERT OR UPDATE ON public.connected_online_providers FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps(); CREATE TRIGGER connected_online_providers_change_record_timestamps AFTER INSERT OR UPDATE ON public.connected_online_providers FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps();
-- CREATE TRIGGER connected_online_services_change_record_timestamps AFTER INSERT OR UPDATE ON public.connected_online_services FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps(); CREATE TRIGGER connected_online_services_change_record_timestamps AFTER INSERT OR UPDATE ON public.connected_online_services FOR EACH ROW EXECUTE FUNCTION trigger_set_timestamps();
-- create policy "service_role_all" create policy "service_role_all"
-- on "public"."connected_online_providers" on "public"."connected_online_providers"
-- as permissive as permissive
-- for all for all
-- to service_role to service_role
-- using (true); using (true);
-- create policy "service_role_all" create policy "service_role_all"
-- on "public"."connected_online_services" on "public"."connected_online_services"
-- as permissive as permissive
-- for all for all
-- to service_role to service_role
-- using (true); using (true);
-- create policy "authenticated_select" create policy "authenticated_select"
-- on "public"."connected_online_providers" on "public"."connected_online_providers"
-- as permissive as permissive
-- for select for select
-- to authenticated to authenticated
-- using (true); using (true);
-- create policy "authenticated_select" create policy "authenticated_select"
-- on "public"."connected_online_services" on "public"."connected_online_services"
-- as permissive as permissive
-- for select for select
-- to authenticated to authenticated
-- using (true); using (true);
-- create policy "own_all" create policy "own_all"
-- on "public"."connected_online_reservation" on "public"."connected_online_reservation"
-- as permissive as permissive
-- for all for all
-- to authenticated to authenticated
-- using ((( SELECT auth.uid() AS uid) = user_id)); using ((( SELECT auth.uid() AS uid) = user_id));

View File

@@ -1,225 +1,225 @@
-- create table "public"."medreport_product_groups" ( create table "public"."medreport_product_groups" (
-- "id" bigint generated by default as identity not null, "id" bigint generated by default as identity not null,
-- "name" text not null, "name" text not null,
-- "created_at" timestamp with time zone not null default now(), "created_at" timestamp with time zone not null default now(),
-- "updated_at" timestamp with time zone "updated_at" timestamp with time zone
-- ); );
-- create table "public"."medreport_products" ( create table "public"."medreport_products" (
-- "id" bigint generated by default as identity not null, "id" bigint generated by default as identity not null,
-- "name" text not null, "name" text not null,
-- "product_group_id" bigint, "product_group_id" bigint,
-- "created_at" timestamp with time zone not null default now(), "created_at" timestamp with time zone not null default now(),
-- "updated_at" timestamp with time zone default now() "updated_at" timestamp with time zone default now()
-- ); );
-- alter table "public"."medreport_products" enable row level security; alter table "public"."medreport_products" enable row level security;
-- create table "public"."medreport_products_analyses_relations" ( create table "public"."medreport_products_analyses_relations" (
-- "product_id" bigint not null, "product_id" bigint not null,
-- "analysis_element_id" bigint, "analysis_element_id" bigint,
-- "analysis_id" bigint "analysis_id" bigint
-- ); );
-- alter table "public"."medreport_product_groups" enable row level security; alter table "public"."medreport_product_groups" enable row level security;
-- alter table "public"."medreport_products_analyses_relations" enable row level security; alter table "public"."medreport_products_analyses_relations" enable row level security;
-- CREATE UNIQUE INDEX medreport_product_groups_name_key ON public.medreport_product_groups USING btree (name); CREATE UNIQUE INDEX medreport_product_groups_name_key ON public.medreport_product_groups USING btree (name);
-- CREATE UNIQUE INDEX medreport_product_groups_pkey ON public.medreport_product_groups USING btree (id); CREATE UNIQUE INDEX medreport_product_groups_pkey ON public.medreport_product_groups USING btree (id);
-- alter table "public"."medreport_product_groups" add constraint "medreport_product_groups_pkey" PRIMARY KEY using index "medreport_product_groups_pkey"; alter table "public"."medreport_product_groups" add constraint "medreport_product_groups_pkey" PRIMARY KEY using index "medreport_product_groups_pkey";
-- alter table "public"."medreport_product_groups" add constraint "medreport_product_groups_name_key" UNIQUE using index "medreport_product_groups_name_key"; alter table "public"."medreport_product_groups" add constraint "medreport_product_groups_name_key" UNIQUE using index "medreport_product_groups_name_key";
-- alter table "public"."medreport_products" add constraint "medreport_products_product_groups_id_fkey" FOREIGN KEY (product_group_id) REFERENCES medreport_product_groups(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."medreport_products" add constraint "medreport_products_product_groups_id_fkey" FOREIGN KEY (product_group_id) REFERENCES medreport_product_groups(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."medreport_products" validate constraint "medreport_products_product_groups_id_fkey"; alter table "public"."medreport_products" validate constraint "medreport_products_product_groups_id_fkey";
-- grant select on table "public"."medreport_product_groups" to "anon"; grant select on table "public"."medreport_product_groups" to "anon";
-- grant select on table "public"."medreport_product_groups" to "authenticated"; grant select on table "public"."medreport_product_groups" to "authenticated";
-- grant delete on table "public"."medreport_product_groups" to "service_role"; grant delete on table "public"."medreport_product_groups" to "service_role";
-- grant insert on table "public"."medreport_product_groups" to "service_role"; grant insert on table "public"."medreport_product_groups" to "service_role";
-- grant references on table "public"."medreport_product_groups" to "service_role"; grant references on table "public"."medreport_product_groups" to "service_role";
-- grant select on table "public"."medreport_product_groups" to "service_role"; grant select on table "public"."medreport_product_groups" to "service_role";
-- grant trigger on table "public"."medreport_product_groups" to "service_role"; grant trigger on table "public"."medreport_product_groups" to "service_role";
-- grant truncate on table "public"."medreport_product_groups" to "service_role"; grant truncate on table "public"."medreport_product_groups" to "service_role";
-- grant update on table "public"."medreport_product_groups" to "service_role"; grant update on table "public"."medreport_product_groups" to "service_role";
-- CREATE UNIQUE INDEX medreport_products_analyses_analysis_element_id_key ON public.medreport_products_analyses_relations USING btree (analysis_element_id); CREATE UNIQUE INDEX medreport_products_analyses_analysis_element_id_key ON public.medreport_products_analyses_relations USING btree (analysis_element_id);
-- CREATE UNIQUE INDEX medreport_products_analyses_analysis_id_key ON public.medreport_products_analyses_relations USING btree (analysis_id); CREATE UNIQUE INDEX medreport_products_analyses_analysis_id_key ON public.medreport_products_analyses_relations USING btree (analysis_id);
-- CREATE UNIQUE INDEX medreport_products_analyses_pkey ON public.medreport_products_analyses_relations USING btree (product_id); CREATE UNIQUE INDEX medreport_products_analyses_pkey ON public.medreport_products_analyses_relations USING btree (product_id);
-- CREATE UNIQUE INDEX medreport_products_name_key ON public.medreport_products USING btree (name); CREATE UNIQUE INDEX medreport_products_name_key ON public.medreport_products USING btree (name);
-- CREATE UNIQUE INDEX medreport_products_pkey ON public.medreport_products USING btree (id); CREATE UNIQUE INDEX medreport_products_pkey ON public.medreport_products USING btree (id);
-- alter table "public"."medreport_products" add constraint "medreport_products_pkey" PRIMARY KEY using index "medreport_products_pkey"; alter table "public"."medreport_products" add constraint "medreport_products_pkey" PRIMARY KEY using index "medreport_products_pkey";
-- alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_pkey" PRIMARY KEY using index "medreport_products_analyses_pkey"; alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_pkey" PRIMARY KEY using index "medreport_products_analyses_pkey";
-- alter table "public"."medreport_products" add constraint "medreport_products_name_key" UNIQUE using index "medreport_products_name_key"; alter table "public"."medreport_products" add constraint "medreport_products_name_key" UNIQUE using index "medreport_products_name_key";
-- alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_element_id_fkey" FOREIGN KEY (analysis_element_id) REFERENCES analysis_elements(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_element_id_fkey" FOREIGN KEY (analysis_element_id) REFERENCES analysis_elements(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."medreport_products_analyses_relations" validate constraint "medreport_products_analyses_analysis_element_id_fkey"; alter table "public"."medreport_products_analyses_relations" validate constraint "medreport_products_analyses_analysis_element_id_fkey";
-- alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_element_id_key" UNIQUE using index "medreport_products_analyses_analysis_element_id_key"; alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_element_id_key" UNIQUE using index "medreport_products_analyses_analysis_element_id_key";
-- alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES analyses(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_id_fkey" FOREIGN KEY (analysis_id) REFERENCES analyses(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."medreport_products_analyses_relations" validate constraint "medreport_products_analyses_analysis_id_fkey"; alter table "public"."medreport_products_analyses_relations" validate constraint "medreport_products_analyses_analysis_id_fkey";
-- alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_id_key" UNIQUE using index "medreport_products_analyses_analysis_id_key"; alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_analysis_id_key" UNIQUE using index "medreport_products_analyses_analysis_id_key";
-- alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_product_id_fkey" FOREIGN KEY (product_id) REFERENCES medreport_products(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."medreport_products_analyses_relations" add constraint "medreport_products_analyses_product_id_fkey" FOREIGN KEY (product_id) REFERENCES medreport_products(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."medreport_products_analyses_relations" validate constraint "medreport_products_analyses_product_id_fkey"; alter table "public"."medreport_products_analyses_relations" validate constraint "medreport_products_analyses_product_id_fkey";
-- alter table "public"."medreport_products_analyses_relations" add constraint "product_can_be_tied_to_only_one_external_item" CHECK (((analysis_id IS NULL) OR (analysis_element_id IS NULL))) not valid; alter table "public"."medreport_products_analyses_relations" add constraint "product_can_be_tied_to_only_one_external_item" CHECK (((analysis_id IS NULL) OR (analysis_element_id IS NULL))) not valid;
-- alter table "public"."medreport_products_analyses_relations" validate constraint "product_can_be_tied_to_only_one_external_item"; alter table "public"."medreport_products_analyses_relations" validate constraint "product_can_be_tied_to_only_one_external_item";
-- grant select on table "public"."medreport_products" to "anon"; grant select on table "public"."medreport_products" to "anon";
-- grant select on table "public"."medreport_products" to "authenticated"; grant select on table "public"."medreport_products" to "authenticated";
-- grant delete on table "public"."medreport_products" to "service_role"; grant delete on table "public"."medreport_products" to "service_role";
-- grant insert on table "public"."medreport_products" to "service_role"; grant insert on table "public"."medreport_products" to "service_role";
-- grant references on table "public"."medreport_products" to "service_role"; grant references on table "public"."medreport_products" to "service_role";
-- grant select on table "public"."medreport_products" to "service_role"; grant select on table "public"."medreport_products" to "service_role";
-- grant trigger on table "public"."medreport_products" to "service_role"; grant trigger on table "public"."medreport_products" to "service_role";
-- grant truncate on table "public"."medreport_products" to "service_role"; grant truncate on table "public"."medreport_products" to "service_role";
-- grant update on table "public"."medreport_products" to "service_role"; grant update on table "public"."medreport_products" to "service_role";
-- grant select on table "public"."medreport_products_analyses_relations" to "anon"; grant select on table "public"."medreport_products_analyses_relations" to "anon";
-- grant select on table "public"."medreport_products_analyses_relations" to "authenticated"; grant select on table "public"."medreport_products_analyses_relations" to "authenticated";
-- grant delete on table "public"."medreport_products_analyses_relations" to "service_role"; grant delete on table "public"."medreport_products_analyses_relations" to "service_role";
-- grant insert on table "public"."medreport_products_analyses_relations" to "service_role"; grant insert on table "public"."medreport_products_analyses_relations" to "service_role";
-- grant references on table "public"."medreport_products_analyses_relations" to "service_role"; grant references on table "public"."medreport_products_analyses_relations" to "service_role";
-- grant select on table "public"."medreport_products_analyses_relations" to "service_role"; grant select on table "public"."medreport_products_analyses_relations" to "service_role";
-- grant trigger on table "public"."medreport_products_analyses_relations" to "service_role"; grant trigger on table "public"."medreport_products_analyses_relations" to "service_role";
-- grant truncate on table "public"."medreport_products_analyses_relations" to "service_role"; grant truncate on table "public"."medreport_products_analyses_relations" to "service_role";
-- grant update on table "public"."medreport_products_analyses_relations" to "service_role"; grant update on table "public"."medreport_products_analyses_relations" to "service_role";
-- create policy "Enable read access for all users" create policy "Enable read access for all users"
-- on "public"."medreport_products_analyses_relations" on "public"."medreport_products_analyses_relations"
-- as permissive as permissive
-- for select for select
-- to public to public
-- using (true); using (true);
-- ALTER TABLE medreport_products_analyses_relations ALTER TABLE medreport_products_analyses_relations
-- ADD CONSTRAINT product_can_be_tied_to_only_one_analysis_item ADD CONSTRAINT product_can_be_tied_to_only_one_analysis_item
-- CHECK (analysis_id IS NULL OR analysis_element_id IS NULL); CHECK (analysis_id IS NULL OR analysis_element_id IS NULL);
-- create table "public"."medreport_products_external_services_relations" ( create table "public"."medreport_products_external_services_relations" (
-- "product_id" bigint not null, "product_id" bigint not null,
-- "connected_online_service_id" bigint not null "connected_online_service_id" bigint not null
-- ); );
-- alter table "public"."medreport_products_external_services_relations" enable row level security; alter table "public"."medreport_products_external_services_relations" enable row level security;
-- CREATE UNIQUE INDEX medreport_products_connected_online_services_id_key ON public.medreport_products_external_services_relations USING btree (connected_online_service_id); CREATE UNIQUE INDEX medreport_products_connected_online_services_id_key ON public.medreport_products_external_services_relations USING btree (connected_online_service_id);
-- CREATE UNIQUE INDEX medreport_products_connected_online_services_pkey ON public.medreport_products_external_services_relations USING btree (connected_online_service_id); CREATE UNIQUE INDEX medreport_products_connected_online_services_pkey ON public.medreport_products_external_services_relations USING btree (connected_online_service_id);
-- alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_pkey" PRIMARY KEY using index "medreport_products_connected_online_services_pkey"; alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_pkey" PRIMARY KEY using index "medreport_products_connected_online_services_pkey";
-- alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_id_fkey" FOREIGN KEY (connected_online_service_id) REFERENCES connected_online_services(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_id_fkey" FOREIGN KEY (connected_online_service_id) REFERENCES connected_online_services(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."medreport_products_external_services_relations" validate constraint "medreport_products_connected_online_services_id_fkey"; alter table "public"."medreport_products_external_services_relations" validate constraint "medreport_products_connected_online_services_id_fkey";
-- alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_id_key" UNIQUE using index "medreport_products_connected_online_services_id_key"; alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_id_key" UNIQUE using index "medreport_products_connected_online_services_id_key";
-- alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_product_id_fkey" FOREIGN KEY (product_id) REFERENCES medreport_products(id) ON UPDATE CASCADE ON DELETE CASCADE not valid; alter table "public"."medreport_products_external_services_relations" add constraint "medreport_products_connected_online_services_product_id_fkey" FOREIGN KEY (product_id) REFERENCES medreport_products(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;
-- alter table "public"."medreport_products_external_services_relations" validate constraint "medreport_products_connected_online_services_product_id_fkey"; alter table "public"."medreport_products_external_services_relations" validate constraint "medreport_products_connected_online_services_product_id_fkey";
-- grant select on table "public"."medreport_products_external_services_relations" to "anon"; grant select on table "public"."medreport_products_external_services_relations" to "anon";
-- grant select on table "public"."medreport_products_external_services_relations" to "authenticated"; grant select on table "public"."medreport_products_external_services_relations" to "authenticated";
-- grant delete on table "public"."medreport_products_external_services_relations" to "service_role"; grant delete on table "public"."medreport_products_external_services_relations" to "service_role";
-- grant insert on table "public"."medreport_products_external_services_relations" to "service_role"; grant insert on table "public"."medreport_products_external_services_relations" to "service_role";
-- grant references on table "public"."medreport_products_external_services_relations" to "service_role"; grant references on table "public"."medreport_products_external_services_relations" to "service_role";
-- grant select on table "public"."medreport_products_external_services_relations" to "service_role"; grant select on table "public"."medreport_products_external_services_relations" to "service_role";
-- grant trigger on table "public"."medreport_products_external_services_relations" to "service_role"; grant trigger on table "public"."medreport_products_external_services_relations" to "service_role";
-- grant truncate on table "public"."medreport_products_external_services_relations" to "service_role"; grant truncate on table "public"."medreport_products_external_services_relations" to "service_role";
-- grant update on table "public"."medreport_products_external_services_relations" to "service_role"; grant update on table "public"."medreport_products_external_services_relations" to "service_role";
-- CREATE OR REPLACE FUNCTION check_tied_to_connected_online() CREATE OR REPLACE FUNCTION check_tied_to_connected_online()
-- RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
-- BEGIN BEGIN
-- IF EXISTS ( IF EXISTS (
-- SELECT 1 SELECT 1
-- FROM medreport_products_external_services_relations FROM medreport_products_external_services_relations
-- WHERE product_id = NEW.product_id WHERE product_id = NEW.product_id
-- ) THEN ) THEN
-- RAISE EXCEPTION 'Value "%" already exists in medreport_products_external_services_relations', NEW.product_id; RAISE EXCEPTION 'Value "%" already exists in medreport_products_external_services_relations', NEW.product_id;
-- END IF; END IF;
-- RETURN NEW; RETURN NEW;
-- END; END;
-- $$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
-- CREATE OR REPLACE FUNCTION check_tied_to_analysis_item() CREATE OR REPLACE FUNCTION check_tied_to_analysis_item()
-- RETURNS TRIGGER AS $$ RETURNS TRIGGER AS $$
-- BEGIN BEGIN
-- IF EXISTS ( IF EXISTS (
-- SELECT 1 SELECT 1
-- FROM medreport_products_analyses_relations FROM medreport_products_analyses_relations
-- WHERE product_id = NEW.product_id WHERE product_id = NEW.product_id
-- ) THEN ) THEN
-- RAISE EXCEPTION 'Value "%" already exists in medreport_products_analyses_relations', NEW.product_id; RAISE EXCEPTION 'Value "%" already exists in medreport_products_analyses_relations', NEW.product_id;
-- END IF; END IF;
-- RETURN NEW; RETURN NEW;
-- END; END;
-- $$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
-- CREATE TRIGGER check_not_already_tied_to_connected_online BEFORE INSERT OR UPDATE ON public.medreport_products_analyses_relations FOR EACH ROW EXECUTE FUNCTION check_tied_to_connected_online(); CREATE TRIGGER check_not_already_tied_to_connected_online BEFORE INSERT OR UPDATE ON public.medreport_products_analyses_relations FOR EACH ROW EXECUTE FUNCTION check_tied_to_connected_online();
-- CREATE TRIGGER check_not_already_tied_to_analysis BEFORE INSERT OR UPDATE ON public.medreport_products_external_services_relations FOR EACH ROW EXECUTE FUNCTION check_tied_to_analysis_item(); CREATE TRIGGER check_not_already_tied_to_analysis BEFORE INSERT OR UPDATE ON public.medreport_products_external_services_relations FOR EACH ROW EXECUTE FUNCTION check_tied_to_analysis_item();
-- create policy "read_all" create policy "read_all"
-- on "public"."medreport_product_groups" on "public"."medreport_product_groups"
-- as permissive as permissive
-- for select for select
-- to public to public
-- using (true); using (true);

View File

@@ -680,17 +680,17 @@ drop policy "accounts_self_update" on "public"."accounts";
drop policy "create_org_account" on "public"."accounts"; drop policy "create_org_account" on "public"."accounts";
-- drop policy "restrict_mfa_accounts" on "public"."accounts"; drop policy "restrict_mfa_accounts" on "public"."accounts";
-- drop policy "super_admins_access_accounts" on "public"."accounts"; drop policy "super_admins_access_accounts" on "public"."accounts";
drop policy "accounts_memberships_delete" on "public"."accounts_memberships"; drop policy "accounts_memberships_delete" on "public"."accounts_memberships";
drop policy "accounts_memberships_read" on "public"."accounts_memberships"; drop policy "accounts_memberships_read" on "public"."accounts_memberships";
-- drop policy "restrict_mfa_accounts_memberships" on "public"."accounts_memberships"; drop policy "restrict_mfa_accounts_memberships" on "public"."accounts_memberships";
-- drop policy "super_admins_access_accounts_memberships" on "public"."accounts_memberships"; drop policy "super_admins_access_accounts_memberships" on "public"."accounts_memberships";
drop policy "analysis_all" on "public"."analyses"; drop policy "analysis_all" on "public"."analyses";
@@ -742,53 +742,53 @@ drop policy "invitations_read_self" on "public"."invitations";
drop policy "invitations_update" on "public"."invitations"; drop policy "invitations_update" on "public"."invitations";
-- drop policy "restrict_mfa_invitations" on "public"."invitations"; drop policy "restrict_mfa_invitations" on "public"."invitations";
-- drop policy "super_admins_access_invitations" on "public"."invitations"; drop policy "super_admins_access_invitations" on "public"."invitations";
drop policy "read_all" on "public"."medreport_product_groups"; drop policy "read_all" on "public"."medreport_product_groups";
drop policy "Enable read access for all users" on "public"."medreport_products_analyses_relations"; drop policy "Enable read access for all users" on "public"."medreport_products_analyses_relations";
-- drop policy "Users can read their own nonces" on "public"."nonces"; drop policy "Users can read their own nonces" on "public"."nonces";
drop policy "notifications_read_self" on "public"."notifications"; drop policy "notifications_read_self" on "public"."notifications";
drop policy "notifications_update_self" on "public"."notifications"; drop policy "notifications_update_self" on "public"."notifications";
-- drop policy "restrict_mfa_notifications" on "public"."notifications"; drop policy "restrict_mfa_notifications" on "public"."notifications";
drop policy "order_items_read_self" on "public"."order_items"; drop policy "order_items_read_self" on "public"."order_items";
-- drop policy "restrict_mfa_order_items" on "public"."order_items"; drop policy "restrict_mfa_order_items" on "public"."order_items";
-- drop policy "super_admins_access_order_items" on "public"."order_items"; drop policy "super_admins_access_order_items" on "public"."order_items";
drop policy "orders_read_self" on "public"."orders"; drop policy "orders_read_self" on "public"."orders";
-- drop policy "restrict_mfa_orders" on "public"."orders"; drop policy "restrict_mfa_orders" on "public"."orders";
-- drop policy "super_admins_access_orders" on "public"."orders"; drop policy "super_admins_access_orders" on "public"."orders";
-- drop policy "restrict_mfa_role_permissions" on "public"."role_permissions"; drop policy "restrict_mfa_role_permissions" on "public"."role_permissions";
drop policy "role_permissions_read" on "public"."role_permissions"; drop policy "role_permissions_read" on "public"."role_permissions";
-- drop policy "super_admins_access_role_permissions" on "public"."role_permissions"; drop policy "super_admins_access_role_permissions" on "public"."role_permissions";
drop policy "roles_read" on "public"."roles"; drop policy "roles_read" on "public"."roles";
-- drop policy "restrict_mfa_subscription_items" on "public"."subscription_items"; drop policy "restrict_mfa_subscription_items" on "public"."subscription_items";
drop policy "subscription_items_read_self" on "public"."subscription_items"; drop policy "subscription_items_read_self" on "public"."subscription_items";
-- drop policy "super_admins_access_subscription_items" on "public"."subscription_items"; drop policy "super_admins_access_subscription_items" on "public"."subscription_items";
-- drop policy "restrict_mfa_subscriptions" on "public"."subscriptions"; drop policy "restrict_mfa_subscriptions" on "public"."subscriptions";
drop policy "subscriptions_read_self" on "public"."subscriptions"; drop policy "subscriptions_read_self" on "public"."subscriptions";
-- drop policy "super_admins_access_subscriptions" on "public"."subscriptions"; drop policy "super_admins_access_subscriptions" on "public"."subscriptions";
alter table "public"."accounts" drop constraint "accounts_created_by_fkey"; alter table "public"."accounts" drop constraint "accounts_created_by_fkey";
@@ -888,7 +888,7 @@ alter table "public"."medreport_products_analyses_relations" drop constraint "pr
alter table "public"."medreport_products_analyses_relations" drop constraint "product_can_be_tied_to_only_one_external_item"; alter table "public"."medreport_products_analyses_relations" drop constraint "product_can_be_tied_to_only_one_external_item";
-- alter table "public"."nonces" drop constraint "nonces_user_id_fkey"; alter table "public"."nonces" drop constraint "nonces_user_id_fkey";
alter table "public"."notifications" drop constraint "notifications_account_id_fkey"; alter table "public"."notifications" drop constraint "notifications_account_id_fkey";
@@ -956,7 +956,7 @@ alter table "public"."medreport_products_analyses_relations" drop constraint "me
alter table "public"."medreport_products_external_services_relations" drop constraint "medreport_products_connected_online_services_pkey"; alter table "public"."medreport_products_external_services_relations" drop constraint "medreport_products_connected_online_services_pkey";
-- alter table "public"."nonces" drop constraint "nonces_pkey"; alter table "public"."nonces" drop constraint "nonces_pkey";
alter table "public"."notifications" drop constraint "notifications_pkey"; alter table "public"."notifications" drop constraint "notifications_pkey";
@@ -1876,11 +1876,12 @@ BEGIN
END;$function$ END;$function$
; ;
CREATE OR REPLACE FUNCTION medreport.create_team_account(account_name text) create
RETURNS medreport.accounts or replace function medreport.create_team_account (account_name text) returns medreport.accounts
LANGUAGE plpgsql SECURITY DEFINER
SET search_path TO '' set
AS $function$declare search_path = '' as $$
declare
new_account medreport.accounts; new_account medreport.accounts;
begin begin
if (not medreport.is_set('enable_team_accounts')) then if (not medreport.is_set('enable_team_accounts')) then
@@ -1898,8 +1899,13 @@ begin
return new_account; return new_account;
end;$function$ end;
;
$$ language plpgsql;
grant
execute on function medreport.create_team_account (text) to authenticated,
service_role;
CREATE OR REPLACE FUNCTION medreport.get_account_invitations(account_slug text) CREATE OR REPLACE FUNCTION medreport.get_account_invitations(account_slug text)
RETURNS TABLE(id integer, email character varying, account_id uuid, invited_by uuid, role character varying, created_at timestamp with time zone, updated_at timestamp with time zone, expires_at timestamp with time zone, inviter_name character varying, inviter_email character varying) RETURNS TABLE(id integer, email character varying, account_id uuid, invited_by uuid, role character varying, created_at timestamp with time zone, updated_at timestamp with time zone, expires_at timestamp with time zone, inviter_name character varying, inviter_email character varying)
@@ -3893,29 +3899,43 @@ to authenticated
using ((account_id = auth.uid())); using ((account_id = auth.uid()));
create policy "accounts_read" create policy accounts_read on medreport.accounts for
on "medreport"."accounts" select
as permissive to authenticated using (
for select (
to authenticated (
using (((( SELECT auth.uid() AS uid) = primary_owner_user_id) OR medreport.has_role_on_account(id) OR medreport.is_account_team_member(id))); select
auth.uid ()
) = primary_owner_user_id
)
or medreport.has_role_on_account (id)
or medreport.is_account_team_member (id)
);
create policy "accounts_self_update" create policy accounts_self_update on medreport.accounts
on "medreport"."accounts" for update
as permissive to authenticated using (
for update (
to authenticated select
using ((( SELECT auth.uid() AS uid) = primary_owner_user_id)) auth.uid ()
with check ((( SELECT auth.uid() AS uid) = primary_owner_user_id)); ) = primary_owner_user_id
)
with
check (
(
select
auth.uid ()
) = primary_owner_user_id
);
create policy "create_org_account" create policy create_org_account on medreport.accounts for insert to authenticated
on "medreport"."accounts" with
as permissive check (
for insert medreport.is_set ('enable_team_accounts')
to authenticated and is_personal_account = false
with check ((medreport.is_set('enable_team_accounts'::text) AND (is_personal_account = false))); );
create policy "restrict_mfa_accounts" create policy "restrict_mfa_accounts"
@@ -5160,47 +5180,47 @@ revoke truncate on table "public"."medreport_products_external_services_relation
revoke update on table "public"."medreport_products_external_services_relations" from "service_role"; revoke update on table "public"."medreport_products_external_services_relations" from "service_role";
-- revoke delete on table "public"."nonces" from "anon"; revoke delete on table "public"."nonces" from "anon";
-- revoke insert on table "public"."nonces" from "anon"; revoke insert on table "public"."nonces" from "anon";
-- revoke references on table "public"."nonces" from "anon"; revoke references on table "public"."nonces" from "anon";
-- revoke select on table "public"."nonces" from "anon"; revoke select on table "public"."nonces" from "anon";
-- revoke trigger on table "public"."nonces" from "anon"; revoke trigger on table "public"."nonces" from "anon";
-- revoke truncate on table "public"."nonces" from "anon"; revoke truncate on table "public"."nonces" from "anon";
-- revoke update on table "public"."nonces" from "anon"; revoke update on table "public"."nonces" from "anon";
-- revoke delete on table "public"."nonces" from "authenticated"; revoke delete on table "public"."nonces" from "authenticated";
-- revoke insert on table "public"."nonces" from "authenticated"; revoke insert on table "public"."nonces" from "authenticated";
-- revoke references on table "public"."nonces" from "authenticated"; revoke references on table "public"."nonces" from "authenticated";
-- revoke select on table "public"."nonces" from "authenticated"; revoke select on table "public"."nonces" from "authenticated";
-- revoke trigger on table "public"."nonces" from "authenticated"; revoke trigger on table "public"."nonces" from "authenticated";
-- revoke truncate on table "public"."nonces" from "authenticated"; revoke truncate on table "public"."nonces" from "authenticated";
-- revoke update on table "public"."nonces" from "authenticated"; revoke update on table "public"."nonces" from "authenticated";
-- revoke delete on table "public"."nonces" from "service_role"; revoke delete on table "public"."nonces" from "service_role";
-- revoke insert on table "public"."nonces" from "service_role"; revoke insert on table "public"."nonces" from "service_role";
-- revoke references on table "public"."nonces" from "service_role"; revoke references on table "public"."nonces" from "service_role";
-- revoke select on table "public"."nonces" from "service_role"; revoke select on table "public"."nonces" from "service_role";
-- revoke trigger on table "public"."nonces" from "service_role"; revoke trigger on table "public"."nonces" from "service_role";
-- revoke truncate on table "public"."nonces" from "service_role"; revoke truncate on table "public"."nonces" from "service_role";
-- revoke update on table "public"."nonces" from "service_role"; revoke update on table "public"."nonces" from "service_role";
revoke delete on table "public"."notifications" from "anon"; revoke delete on table "public"."notifications" from "anon";
@@ -5410,7 +5430,7 @@ drop table "public"."medreport_products_analyses_relations";
drop table "public"."medreport_products_external_services_relations"; drop table "public"."medreport_products_external_services_relations";
-- drop table "public"."nonces"; drop table "public"."nonces";
drop table "public"."notifications"; drop table "public"."notifications";

View File

@@ -40,4 +40,8 @@ END;$function$
grant execute on function medreport.has_consent_personal_data(uuid) grant execute on function medreport.has_consent_personal_data(uuid)
to authenticated, anon; to authenticated, anon;
-- we allow the authenticated role to execute functions in the medreport schema
grant usage on schema medreport to authenticated; grant usage on schema medreport to authenticated;
-- we allow the service_role role to execute functions in the medreport schema
grant usage on schema medreport to service_role;

View File

@@ -1 +1 @@
alter type public.billing_provider add value 'montonio'; alter type medreport.billing_provider add value 'montonio';

View File

@@ -0,0 +1,32 @@
grant
execute on function medreport.get_account_members (text) to authenticated,
service_role;
create or replace function medreport.is_company_admin(account_slug text)
returns boolean
set search_path = ''
language plpgsql
as $$
declare
is_owner boolean;
begin
select exists (
select 1
from medreport.accounts_memberships am
join medreport.accounts a on a.id = am.account_id
where am.user_id = auth.uid()
and am.account_role = 'owner'
and a.slug = account_slug
) into is_owner;
return is_owner;
end;
$$;
grant execute on function medreport.is_company_admin(text) to authenticated, service_role;
grant
execute on function medreport.team_account_workspace (text) to authenticated,
service_role;
grant execute on function medreport.get_account_invitations(text) to authenticated, service_role;

View File

@@ -0,0 +1,67 @@
create table "medreport"."company_params" (
"id" uuid not null default gen_random_uuid(),
"benefit_occurance" text,
"benefit_amount" numeric,
"account_id" uuid,
"created_at" timestamp with time zone default now(),
"updated_at" timestamp with time zone default now(),
"slug" text
);
alter table "medreport"."company_params" enable row level security;
CREATE UNIQUE INDEX company_params_pkey ON medreport.company_params USING btree (id);
alter table "medreport"."company_params" add constraint "company_params_pkey" PRIMARY KEY using index "company_params_pkey";
alter table "medreport"."company_params" add constraint "company_params_account_id_fkey" FOREIGN KEY (account_id) REFERENCES medreport.accounts(id) ON DELETE CASCADE not valid;
alter table "medreport"."company_params" validate constraint "company_params_account_id_fkey";
set check_function_bodies = off;
CREATE OR REPLACE FUNCTION medreport.insert_company_params_on_new_company()
RETURNS trigger
LANGUAGE plpgsql
AS $function$begin
insert into medreport.company_params (
account_id,
slug,
benefit_occurance,
benefit_amount
) values (
new.id,
new.slug,
null, -- or a default value like 'monthly'
null -- or a default numeric like 0
);
return new;
end;$function$
;
grant execute on function medreport.insert_company_params_on_new_company() to authenticated,
service_role;
CREATE TRIGGER trigger_create_company_params AFTER INSERT ON medreport.accounts FOR EACH ROW EXECUTE FUNCTION medreport.insert_company_params_on_new_company();
create policy "Allow select and update if user is account's primary owner"
on medreport.company_params
for all
using (
exists (
select 1 from medreport.accounts
where
accounts.id = company_params.account_id
and accounts.primary_owner_user_id = auth.uid()
)
)
with check (
exists (
select 1 from medreport.accounts
where
accounts.id = company_params.account_id
and accounts.primary_owner_user_id = auth.uid()
)
);
grant select, update on medreport.company_params to authenticated;

View File

@@ -0,0 +1,16 @@
-- Update your user role to Super Admin
update auth.users set raw_app_meta_data='{"provider": "email", "providers": ["email"], "role": "super-admin" }' where email='test2@test.ee';
-- To create a new company user you need rows in Roles table
INSERT INTO medreport.roles (name, hierarchy_level)
VALUES
('owner', 1),
('member', 2);
-- Add role permissions
insert into medreport.role_permissions (role, permission) values
('owner', 'roles.manage'),
('owner', 'billing.manage'),
('owner', 'settings.manage'),
('owner', 'members.manage'),
('owner', 'invites.manage');