From 86b86c6752a723110ab8da91985ec23b0ef7414b Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Wed, 23 Jul 2025 16:33:24 +0300 Subject: [PATCH 1/2] add health benefit form fix super admin --- .env | 2 +- README.md | 8 +- .../site-header-account-section.tsx | 8 +- app/(marketing)/_components/site-header.tsx | 10 +- app/(marketing)/layout.tsx | 8 +- app/admin/_components/admin-sidebar.tsx | 12 +- app/admin/accounts/page.tsx | 15 +- app/admin/layout.tsx | 5 +- .../_components/update-account-form.tsx | 5 +- app/home/(user)/(dashboard)/layout.tsx | 2 +- .../_components/health-benefit-fields.tsx | 83 +++++ .../_components/health-benefit-form.tsx | 111 +++++++ .../_components/yearly-expenses-overview.tsx | 90 +++++ .../billing/_lib/server/server-actions.ts | 12 + .../_lib/server/team-billing.service.ts | 20 ++ app/home/[account]/billing/page.tsx | 118 +------ app/home/[account]/layout.tsx | 10 +- app/home/[account]/page.tsx | 9 +- packages/billing/core/src/schema/index.ts | 1 + .../schema/update-health-benefit.schema.ts | 10 + .../components/personal-account-dropdown.tsx | 36 +- .../src/components/admin-account-page.tsx | 5 - .../admin-create-company-account.service.ts | 10 +- .../src/lib/server/utils/is-super-admin.ts | 4 +- packages/features/auth/package.json | 4 +- .../src/components/update-account-form.tsx | 225 +++++++++++++ .../src}/schemas/update-account.schema.ts | 0 .../server/actions/update-account-actions.ts | 5 +- .../features/team-accounts/src/server/api.ts | 41 +++ .../team-accounts/src/server/types.ts | 5 + .../src/server/utils/is-company-admin.ts | 13 +- packages/supabase/src/database.types.ts | 113 +++++-- public/locales/et/billing.json | 21 +- public/locales/et/common.json | 4 +- styles/globals.css | 4 +- ...0616142604_add_connected_online_tables.sql | 310 +++++++++--------- ...619070038_add_medreport_product_tables.sql | 274 ++++++++-------- .../20250703145757_add_medreport_schema.sql | 156 +++++---- ...20250707150416_membership_confirmation.sql | 4 + .../20250717075135_montonio_type.sql | 2 +- .../20250722110506_super_admin_fix.sql | 32 ++ .../20250723114200_company_params.sql | 67 ++++ supabase/sql/super-admin.sql | 16 + 43 files changed, 1329 insertions(+), 561 deletions(-) create mode 100644 app/home/[account]/billing/_components/health-benefit-fields.tsx create mode 100644 app/home/[account]/billing/_components/health-benefit-form.tsx create mode 100644 app/home/[account]/billing/_components/yearly-expenses-overview.tsx create mode 100644 packages/billing/core/src/schema/update-health-benefit.schema.ts create mode 100644 packages/features/auth/src/components/update-account-form.tsx rename {app/auth/update-account/_lib => packages/features/auth/src}/schemas/update-account.schema.ts (100%) rename app/auth/update-account/_lib/server/update-account.ts => packages/features/auth/src/server/actions/update-account-actions.ts (95%) create mode 100644 packages/features/team-accounts/src/server/types.ts create mode 100644 supabase/migrations/20250722110506_super_admin_fix.sql create mode 100644 supabase/migrations/20250723114200_company_params.sql create mode 100644 supabase/sql/super-admin.sql diff --git a/.env b/.env index 38867a6..478c417 100644 --- a/.env +++ b/.env @@ -35,7 +35,7 @@ NEXT_PUBLIC_ENABLE_THEME_TOGGLE=true NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_DELETION=true NEXT_PUBLIC_ENABLE_PERSONAL_ACCOUNT_BILLING=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_CREATION=true NEXT_PUBLIC_LANGUAGE_PRIORITY=application diff --git a/README.md b/README.md index 2a986d8..1a602b4 100644 --- a/README.md +++ b/README.md @@ -85,5 +85,11 @@ To access admin pages follow these steps: - Register new user - Go to Profile and add Multi-Factor Authentication -- Sign out and Sign in - 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` diff --git a/app/(marketing)/_components/site-header-account-section.tsx b/app/(marketing)/_components/site-header-account-section.tsx index 02fcc5c..0407884 100644 --- a/app/(marketing)/_components/site-header-account-section.tsx +++ b/app/(marketing)/_components/site-header-account-section.tsx @@ -3,6 +3,7 @@ import dynamic from 'next/dynamic'; import Link from 'next/link'; +import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace'; import { useQuery } from '@tanstack/react-query'; import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown'; @@ -29,7 +30,11 @@ const features = { enableThemeToggle: featuresFlagConfig.enableThemeToggle, }; -export function SiteHeaderAccountSection() { +export function SiteHeaderAccountSection({ + accounts, +}: { + accounts: UserWorkspace['accounts']; +}) { const session = useSession(); const signOut = useSignOut(); @@ -41,6 +46,7 @@ export function SiteHeaderAccountSection() { features={features} user={session.data.user} signOutRequested={() => signOut.mutateAsync()} + accounts={accounts} /> ); } diff --git a/app/(marketing)/_components/site-header.tsx b/app/(marketing)/_components/site-header.tsx index 5f40931..9507b18 100644 --- a/app/(marketing)/_components/site-header.tsx +++ b/app/(marketing)/_components/site-header.tsx @@ -1,3 +1,5 @@ +import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace'; + import { Header } from '@kit/ui/marketing'; import { AppLogo } from '~/components/app-logo'; @@ -5,12 +7,16 @@ import { AppLogo } from '~/components/app-logo'; import { SiteHeaderAccountSection } from './site-header-account-section'; import { SiteNavigation } from './site-navigation'; -export function SiteHeader() { +export function SiteHeader({ + accounts, +}: { + accounts: UserWorkspace['accounts']; +}) { return (
} navigation={} - actions={} + actions={} /> ); } diff --git a/app/(marketing)/layout.tsx b/app/(marketing)/layout.tsx index 44d32e0..10027a7 100644 --- a/app/(marketing)/layout.tsx +++ b/app/(marketing)/layout.tsx @@ -1,11 +1,17 @@ +import { use } from 'react'; + import { SiteFooter } from '~/(marketing)/_components/site-footer'; import { SiteHeader } from '~/(marketing)/_components/site-header'; import { withI18n } from '~/lib/i18n/with-i18n'; +import { loadUserWorkspace } from '../home/(user)/_lib/server/load-user-workspace'; + function SiteLayout(props: React.PropsWithChildren) { + const workspace = use(loadUserWorkspace()); + return (
- + {props.children} diff --git a/app/admin/_components/admin-sidebar.tsx b/app/admin/_components/admin-sidebar.tsx index 5b6c230..e66c622 100644 --- a/app/admin/_components/admin-sidebar.tsx +++ b/app/admin/_components/admin-sidebar.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; +import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace'; import { LayoutDashboard, Users } from 'lucide-react'; import { @@ -21,10 +22,13 @@ import { import { AppLogo } from '~/components/app-logo'; import { ProfileAccountDropdownContainer } from '~/components/personal-account-dropdown-container'; -export function AdminSidebar() { +export function AdminSidebar({ + accounts, +}: { + accounts: UserWorkspace['accounts']; +}) { const path = usePathname(); const { open } = useSidebar(); - return ( @@ -62,8 +66,8 @@ export function AdminSidebar() { - + ); -} \ No newline at end of file +} diff --git a/app/admin/accounts/page.tsx b/app/admin/accounts/page.tsx index 688987f..54cb3c6 100644 --- a/app/admin/accounts/page.tsx +++ b/app/admin/accounts/page.tsx @@ -1,8 +1,8 @@ import { ServerDataLoader } from '@makerkit/data-loader-supabase-nextjs'; 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 { AdminCreateUserDialog } from '@kit/admin/components/admin-create-user-dialog'; import { AdminGuard } from '@kit/admin/components/admin-guard'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; @@ -24,7 +24,7 @@ export const metadata = { }; async function AccountsPage(props: AdminAccountsPageProps) { - const client = getSupabaseServerClient(); + const client = getSupabaseServerClient().schema('medreport'); const searchParams = await props.searchParams; const page = searchParams.page ? parseInt(searchParams.page) : 1; @@ -33,18 +33,19 @@ async function AccountsPage(props: AdminAccountsPageProps) { }>
- + -
{ @@ -55,7 +56,9 @@ async function AccountsPage(props: AdminAccountsPageProps) { } 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; diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index 41f8df0..8317da4 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -8,6 +8,8 @@ import { SidebarProvider } from '@kit/ui/shadcn-sidebar'; import { AdminSidebar } from '~/admin/_components/admin-sidebar'; import { AdminMobileNavigation } from '~/admin/_components/mobile-navigation'; +import { loadUserWorkspace } from '../home/(user)/_lib/server/load-user-workspace'; + export const metadata = { title: `Super Admin`, }; @@ -16,12 +18,13 @@ export const dynamic = 'force-dynamic'; export default function AdminLayout(props: React.PropsWithChildren) { const state = use(getLayoutState()); + const workspace = use(loadUserWorkspace()); return ( - + diff --git a/app/auth/update-account/_components/update-account-form.tsx b/app/auth/update-account/_components/update-account-form.tsx index 5caf2e0..d286e88 100644 --- a/app/auth/update-account/_components/update-account-form.tsx +++ b/app/auth/update-account/_components/update-account-form.tsx @@ -8,6 +8,8 @@ import { ExternalLink } from '@/public/assets/external-link'; import { zodResolver } from '@hookform/resolvers/zod'; 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 { Checkbox } from '@kit/ui/checkbox'; import { @@ -21,9 +23,6 @@ import { import { Input } from '@kit/ui/input'; 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 }) { const form = useForm({ resolver: zodResolver(UpdateAccountSchema), diff --git a/app/home/(user)/(dashboard)/layout.tsx b/app/home/(user)/(dashboard)/layout.tsx index 5e08ec6..9e5bb8f 100644 --- a/app/home/(user)/(dashboard)/layout.tsx +++ b/app/home/(user)/(dashboard)/layout.tsx @@ -2,6 +2,7 @@ import { use } from 'react'; import { cookies } from 'next/headers'; +import { retrieveCart } from '@lib/data'; import { z } from 'zod'; 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 { HomeSidebar } from '../_components/home-sidebar'; import { loadUserWorkspace } from '../_lib/server/load-user-workspace'; -import { retrieveCart } from '@lib/data'; function UserHomeLayout({ children }: React.PropsWithChildren) { const state = use(getLayoutState()); diff --git a/app/home/[account]/billing/_components/health-benefit-fields.tsx b/app/home/[account]/billing/_components/health-benefit-fields.tsx new file mode 100644 index 0000000..7428f82 --- /dev/null +++ b/app/home/[account]/billing/_components/health-benefit-fields.tsx @@ -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 ( +
+ ( + + + + + + + + + + )} + /> + + ( + + + + + + + field.onChange( + e.target.value === '' ? null : Number(e.target.value), + ) + } + /> + + + + )} + /> +
+ ); +}; + +export default HealthBenefitFields; diff --git a/app/home/[account]/billing/_components/health-benefit-form.tsx b/app/home/[account]/billing/_components/health-benefit-form.tsx new file mode 100644 index 0000000..8c11bc4 --- /dev/null +++ b/app/home/[account]/billing/_components/health-benefit-form.tsx @@ -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 ( +
+ { + toast.promise( + () => updateHealthBenefit({ ...data, accountId: account.id }), + { + success: 'success', + error: 'error', + }, + ); + })} + > +
+
+

+ +

+

+ +

+
+ +
+
+
+
+
+ +
+

+ +

+

+ {companyParams.benefit_amount || 0} € +

+
+ + + +
+ +
+
+ +
+ +

+ +

+
+
+
+ + ); +}; + +export default HealthBenefitForm; diff --git a/app/home/[account]/billing/_components/yearly-expenses-overview.tsx b/app/home/[account]/billing/_components/yearly-expenses-overview.tsx new file mode 100644 index 0000000..71d6592 --- /dev/null +++ b/app/home/[account]/billing/_components/yearly-expenses-overview.tsx @@ -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 ( +
+
+ +
+
+

+ +

+ + {monthlyExpensePerEmployee} € + +
+
+

+ +

+ + {maxYearlyExpensePerEmployee} € + +
+
+

+ +

+ + {(Number(maxYearlyExpensePerEmployee) * employeeCount).toFixed(2)} € + +
+ +
+

Kokku

+ 13 200,00 € +
+
+ ); +}; + +export default YearlyExpensesOverview; diff --git a/app/home/[account]/billing/_lib/server/server-actions.ts b/app/home/[account]/billing/_lib/server/server-actions.ts index 443bfa8..f7024a8 100644 --- a/app/home/[account]/billing/_lib/server/server-actions.ts +++ b/app/home/[account]/billing/_lib/server/server-actions.ts @@ -2,6 +2,8 @@ import { redirect } from 'next/navigation'; +import { UpdateHealthBenefitData } from '@/packages/features/team-accounts/src/server/types'; + import { enhanceAction } from '@kit/next/actions'; 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); + }, + {}, +); diff --git a/app/home/[account]/billing/_lib/server/team-billing.service.ts b/app/home/[account]/billing/_lib/server/team-billing.service.ts index b2c285d..e0decb6 100644 --- a/app/home/[account]/billing/_lib/server/team-billing.service.ts +++ b/app/home/[account]/billing/_lib/server/team-billing.service.ts @@ -2,6 +2,7 @@ import 'server-only'; 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 { z } from 'zod'; @@ -292,6 +293,25 @@ class TeamBillingService { return Promise.reject(error as Error); } } + + async updateHealthBenefit(data: UpdateHealthBenefitData): Promise { + 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) { diff --git a/app/home/[account]/billing/page.tsx b/app/home/[account]/billing/page.tsx index 63ce466..8342c98 100644 --- a/app/home/[account]/billing/page.tsx +++ b/app/home/[account]/billing/page.tsx @@ -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 { 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 { withI18n } from '~/lib/i18n/with-i18n'; -// local imports -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'; +import HealthBenefitForm from './_components/health-benefit-form'; interface TeamAccountBillingPageProps { params: Promise<{ account: string }>; @@ -37,99 +22,16 @@ export const generateMetadata = async () => { }; async function TeamAccountBillingPage({ params }: TeamAccountBillingPageProps) { - const account = (await params).account; - const workspace = await loadTeamWorkspace(account); - const accountId = workspace.account.id; - - const [data, customerId] = await loadTeamAccountBillingPage(accountId); - - const canManageBilling = - workspace.account.permissions.includes('billing.manage'); - - const Checkout = () => { - if (!canManageBilling) { - return ; - } - - return ( - - ); - }; - - const BillingPortal = () => { - if (!canManageBilling || !customerId) { - return null; - } - - return ( -
- - - - - - ); - }; + const accountSlug = (await params).account; + const api = createTeamAccountsApi(getSupabaseServerClient()); + const account = await api.getTeamAccount(accountSlug); + const companyParams = await api.getTeamAccountParams(account.id); return ( - <> - } - description={} - /> - - -
- - -
- } - > - {(data) => { - if ('active' in data) { - return ( - - ); - } - - return ( - - ); - }} - - - -
- - + + + ); } export default withI18n(TeamAccountBillingPage); - -function CannotManageBillingAlert() { - return ( - - - - - - - - - - - - ); -} diff --git a/app/home/[account]/layout.tsx b/app/home/[account]/layout.tsx index f597a3b..9ab2acc 100644 --- a/app/home/[account]/layout.tsx +++ b/app/home/[account]/layout.tsx @@ -4,7 +4,10 @@ import { cookies } from 'next/headers'; 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 { SidebarProvider } from '@kit/ui/shadcn-sidebar'; @@ -124,9 +127,10 @@ function HeaderLayout({ /> - + -
; @@ -26,7 +27,7 @@ export const generateMetadata = async () => { function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { const account = use(params).account; - + console.log('TeamAccountHomePage account', account); return ( <> } description={} /> - + @@ -42,4 +43,4 @@ function TeamAccountHomePage({ params }: TeamAccountHomePageProps) { ); } -export default withI18n(CompanyGuard(TeamAccountHomePage)); +export default withI18n(CompanyGuard(TeamAccountHomePage)); diff --git a/packages/billing/core/src/schema/index.ts b/packages/billing/core/src/schema/index.ts index e679477..fec8982 100644 --- a/packages/billing/core/src/schema/index.ts +++ b/packages/billing/core/src/schema/index.ts @@ -5,3 +5,4 @@ export * from './cancel-subscription-params.schema'; export * from './report-billing-usage.schema'; export * from './update-subscription-params.schema'; export * from './query-billing-usage.schema'; +export * from './update-health-benefit.schema'; diff --git a/packages/billing/core/src/schema/update-health-benefit.schema.ts b/packages/billing/core/src/schema/update-health-benefit.schema.ts new file mode 100644 index 0000000..d9a66ce --- /dev/null +++ b/packages/billing/core/src/schema/update-health-benefit.schema.ts @@ -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' }), +}); diff --git a/packages/features/accounts/src/components/personal-account-dropdown.tsx b/packages/features/accounts/src/components/personal-account-dropdown.tsx index fd2bab8..98812d5 100644 --- a/packages/features/accounts/src/components/personal-account-dropdown.tsx +++ b/packages/features/accounts/src/components/personal-account-dropdown.tsx @@ -6,14 +6,9 @@ import Link from 'next/link'; import type { User } from '@supabase/supabase-js'; -import { - ChevronsUpDown, - Home, - LogOut, - UserCircle, - Shield, -} from 'lucide-react'; +import { ChevronsUpDown, Home, LogOut, Shield, UserCircle } from 'lucide-react'; +import { Avatar, AvatarFallback, AvatarImage } from '@kit/ui/avatar'; import { DropdownMenu, DropdownMenuContent, @@ -26,11 +21,11 @@ import { SubMenuModeToggle } from '@kit/ui/mode-toggle'; import { ProfileAvatar } from '@kit/ui/profile-avatar'; import { Trans } from '@kit/ui/trans'; 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 { usePersonalAccountData } from '../hooks/use-personal-account-data'; + const PERSONAL_ACCOUNT_SLUG = 'personal'; export function PersonalAccountDropdown({ @@ -41,7 +36,7 @@ export function PersonalAccountDropdown({ paths, features, account, - accounts = [] + accounts = [], }: { user: User; @@ -104,7 +99,8 @@ export function PersonalAccountDropdown({ className ?? '', { ['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)} -
0}> - +
- - + + - - {account.label} - + {account.label}
diff --git a/packages/features/admin/src/components/admin-account-page.tsx b/packages/features/admin/src/components/admin-account-page.tsx index 540a932..9902bcd 100644 --- a/packages/features/admin/src/components/admin-account-page.tsx +++ b/packages/features/admin/src/components/admin-account-page.tsx @@ -3,11 +3,6 @@ import { BadgeX, Ban, ShieldPlus, VenetianMask } from 'lucide-react'; import { Tables } from '@kit/supabase/database'; import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-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 { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs'; import { Badge } from '@kit/ui/badge'; diff --git a/packages/features/admin/src/lib/server/services/admin-create-company-account.service.ts b/packages/features/admin/src/lib/server/services/admin-create-company-account.service.ts index db59f34..566fe8c 100644 --- a/packages/features/admin/src/lib/server/services/admin-create-company-account.service.ts +++ b/packages/features/admin/src/lib/server/services/admin-create-company-account.service.ts @@ -22,9 +22,11 @@ class CreateTeamAccountService { logger.info(ctx, `Creating new company account...`); - const { error, data } = await this.client.rpc('create_team_account', { - account_name: params.name, - }); + const { error, data } = await this.client + .schema('medreport') + .rpc('create_team_account', { + account_name: params.name, + }); if (error) { logger.error( @@ -35,7 +37,7 @@ class CreateTeamAccountService { `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`); diff --git a/packages/features/admin/src/lib/server/utils/is-super-admin.ts b/packages/features/admin/src/lib/server/utils/is-super-admin.ts index d683659..71b88dd 100644 --- a/packages/features/admin/src/lib/server/utils/is-super-admin.ts +++ b/packages/features/admin/src/lib/server/utils/is-super-admin.ts @@ -9,7 +9,9 @@ import { Database } from '@kit/supabase/database'; */ export async function isSuperAdmin(client: SupabaseClient) { try { - const { data, error } = await client.rpc('is_super_admin'); + const { data, error } = await client + .schema('medreport') + .rpc('is_super_admin'); if (error) { throw error; diff --git a/packages/features/auth/package.json b/packages/features/auth/package.json index 148aeb4..562591f 100644 --- a/packages/features/auth/package.json +++ b/packages/features/auth/package.json @@ -18,7 +18,9 @@ "./captcha/server": "./src/captcha/server/index.ts", "./resend-email-link": "./src/components/resend-auth-link-form.tsx", "./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": { "@hookform/resolvers": "^5.0.1", diff --git a/packages/features/auth/src/components/update-account-form.tsx b/packages/features/auth/src/components/update-account-form.tsx new file mode 100644 index 0000000..2cad0f0 --- /dev/null +++ b/packages/features/auth/src/components/update-account-form.tsx @@ -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 ( +
+ + ( + + + + + + + + + + )} + /> + + ( + + + + + + + + + + )} + /> + + ( + + + + + + + + + + )} + /> + + ( + + + + + + + + + + )} + /> + + ( + + + + + + + + + + )} + /> + + ( + + + + + + + + + + )} + /> + +
+ ( + + + + + + + field.onChange( + e.target.value === '' ? null : Number(e.target.value), + ) + } + /> + + + + )} + /> + + ( + + + + + + + field.onChange( + e.target.value === '' ? null : Number(e.target.value), + ) + } + /> + + + + )} + /> +
+ + ( + +
+ + + + + + +
+ + + + + +
+ )} + /> + + + + + ); +} diff --git a/app/auth/update-account/_lib/schemas/update-account.schema.ts b/packages/features/auth/src/schemas/update-account.schema.ts similarity index 100% rename from app/auth/update-account/_lib/schemas/update-account.schema.ts rename to packages/features/auth/src/schemas/update-account.schema.ts diff --git a/app/auth/update-account/_lib/server/update-account.ts b/packages/features/auth/src/server/actions/update-account-actions.ts similarity index 95% rename from app/auth/update-account/_lib/server/update-account.ts rename to packages/features/auth/src/server/actions/update-account-actions.ts index 9a14f36..89191f6 100644 --- a/app/auth/update-account/_lib/server/update-account.ts +++ b/packages/features/auth/src/server/actions/update-account-actions.ts @@ -2,14 +2,15 @@ import { redirect } from 'next/navigation'; +import { updateCustomer } from '@lib/data/customer'; + import { createAuthApi } from '@kit/auth/api'; import { enhanceAction } from '@kit/next/actions'; import { getSupabaseServerClient } from '@kit/supabase/server-client'; 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 { firstName: string; diff --git a/packages/features/team-accounts/src/server/api.ts b/packages/features/team-accounts/src/server/api.ts index 68599d2..442735d 100644 --- a/packages/features/team-accounts/src/server/api.ts +++ b/packages/features/team-accounts/src/server/api.ts @@ -2,6 +2,8 @@ import { SupabaseClient } from '@supabase/supabase-js'; import { Database } from '@kit/supabase/database'; +import { UpdateHealthBenefitData } from './types'; + /** * Class representing an API for interacting with team accounts. * @constructor @@ -254,6 +256,45 @@ export class TeamAccountsApi { 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) { diff --git a/packages/features/team-accounts/src/server/types.ts b/packages/features/team-accounts/src/server/types.ts new file mode 100644 index 0000000..dd02558 --- /dev/null +++ b/packages/features/team-accounts/src/server/types.ts @@ -0,0 +1,5 @@ +export interface UpdateHealthBenefitData { + accountId: string; + occurance: string; + amount: number; +} diff --git a/packages/features/team-accounts/src/server/utils/is-company-admin.ts b/packages/features/team-accounts/src/server/utils/is-company-admin.ts index 9ac4a22..6754d4a 100644 --- a/packages/features/team-accounts/src/server/utils/is-company-admin.ts +++ b/packages/features/team-accounts/src/server/utils/is-company-admin.ts @@ -7,11 +7,16 @@ import { Database } from '@kit/supabase/database'; * @description Check if the current user is a super admin. * @param client */ -export async function isCompanyAdmin(client: SupabaseClient, accountSlug: string) { +export async function isCompanyAdmin( + client: SupabaseClient, + accountSlug: string, +) { try { - const { data, error } = await client.rpc('is_company_admin', { - account_slug: accountSlug, - }); + const { data, error } = await client + .schema('medreport') + .rpc('is_company_admin', { + account_slug: accountSlug, + }); if (error) { throw error; diff --git a/packages/supabase/src/database.types.ts b/packages/supabase/src/database.types.ts index 9a735ee..429e41f 100644 --- a/packages/supabase/src/database.types.ts +++ b/packages/supabase/src/database.types.ts @@ -9,6 +9,39 @@ export type Json = export type Database = { audit: { 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: { Row: { changed_at: string @@ -116,29 +149,7 @@ export type Database = { status?: string } 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: { [_ 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: { Row: { billing_provider: Database["medreport"]["Enums"]["billing_provider"] @@ -1628,6 +1691,10 @@ export type Database = { Args: { target_account_id: string } Returns: boolean } + is_company_admin: { + Args: { account_slug: string } + Returns: boolean + } is_mfa_compliant: { Args: Record Returns: boolean diff --git a/public/locales/et/billing.json b/public/locales/et/billing.json index 0f7b472..a071540 100644 --- a/public/locales/et/billing.json +++ b/public/locales/et/billing.json @@ -119,5 +119,24 @@ }, "cart": { "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 *" } -} \ No newline at end of file +} diff --git a/public/locales/et/common.json b/public/locales/et/common.json index 05008e4..d7f2cad 100644 --- a/public/locales/et/common.json +++ b/public/locales/et/common.json @@ -119,7 +119,9 @@ "personalCode": "Isikukood", "city": "Linn", "weight": "Kaal", - "height": "Pikkus" + "height": "Pikkus", + "occurance": "Toetuse sagedus", + "amount": "Summa" }, "wallet": { "balance": "Sinu MedReporti konto seis", diff --git a/styles/globals.css b/styles/globals.css index 50b8740..45f8195 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -71,11 +71,11 @@ } h4 { - @apply text-2xl; + @apply text-xl; } h5 { - @apply text-xl; + @apply text-base; } h6 { diff --git a/supabase/migrations/20250616142604_add_connected_online_tables.sql b/supabase/migrations/20250616142604_add_connected_online_tables.sql index 890eb2b..74f701a 100644 --- a/supabase/migrations/20250616142604_add_connected_online_tables.sql +++ b/supabase/migrations/20250616142604_add_connected_online_tables.sql @@ -1,227 +1,227 @@ --- create table "public"."connected_online_providers" ( --- "id" bigint not null, --- "name" text not null, --- "email" text, --- "phone_number" text, --- "can_select_worker" boolean not null, --- "personal_code_required" boolean not null, --- "created_at" timestamp with time zone not null default now(), --- "updated_at" timestamp without time zone default now() --- ); +create table "public"."connected_online_providers" ( + "id" bigint not null, + "name" text not null, + "email" text, + "phone_number" text, + "can_select_worker" boolean not null, + "personal_code_required" boolean not null, + "created_at" timestamp with time zone not null 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" ( --- "id" bigint not null, --- "clinic_id" bigint not null, --- "sync_id" bigint not null, --- "name" text not null, --- "description" text, --- "price" double precision not null, --- "requires_payment" boolean not null, --- "duration" bigint not null, --- "neto_duration" bigint, --- "display" text, --- "price_periods" text, --- "online_hide_duration" bigint, --- "online_hide_price" bigint, --- "code" text not null, --- "has_free_codes" boolean not null, --- "created_at" timestamp with time zone not null default now(), --- "updated_at" timestamp with time zone default now() --- ); +create table "public"."connected_online_services" ( + "id" bigint not null, + "clinic_id" bigint not null, + "sync_id" bigint not null, + "name" text not null, + "description" text, + "price" double precision not null, + "requires_payment" boolean not null, + "duration" bigint not null, + "neto_duration" bigint, + "display" text, + "price_periods" text, + "online_hide_duration" bigint, + "online_hide_price" bigint, + "code" text not null, + "has_free_codes" boolean not null, + "created_at" timestamp with time zone not null 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" ( --- "id" bigint generated by default as identity not null, --- "personal_code" bigint, --- "request_api" text not null, --- "request_api_method" text not null, --- "status" audit.request_status not null, --- "comment" text, --- "service_provider_id" bigint, --- "service_id" bigint, --- "requested_start_date" timestamp with time zone, --- "requested_end_date" timestamp with time zone, --- "created_at" timestamp with time zone not null default now() --- ); +create table "audit"."request_entries" ( + "id" bigint generated by default as identity not null, + "personal_code" bigint, + "request_api" text not null, + "request_api_method" text not null, + "status" audit.request_status not null, + "comment" text, + "service_provider_id" bigint, + "service_id" bigint, + "requested_start_date" timestamp with time zone, + "requested_end_date" timestamp with time zone, + "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" --- on "audit"."request_entries" --- as permissive --- for all --- to service_role --- using (true); +create policy "service_role_all" +on "audit"."request_entries" +as permissive +for all +to service_role +using (true); --- create table "public"."connected_online_reservation" ( --- "id" bigint generated by default as identity not null, --- "user_id" uuid not null, --- "booking_code" text not null, --- "service_id" bigint not null, --- "clinic_id" bigint not null, --- "service_user_id" bigint, --- "sync_user_id" bigint not null, --- "requires_payment" boolean not null, --- "comments" text, --- "start_time" timestamp with time zone not null, --- "lang" text not null, --- "discount_code" text, --- "created_at" timestamp with time zone not null default now(), --- "updated_at" timestamp with time zone default now() --- ); +create table "public"."connected_online_reservation" ( + "id" bigint generated by default as identity not null, + "user_id" uuid not null, + "booking_code" text not null, + "service_id" bigint not null, + "clinic_id" bigint not null, + "service_user_id" bigint, + "sync_user_id" bigint not null, + "requires_payment" boolean not null, + "comments" text, + "start_time" timestamp with time zone not null, + "lang" text not null, + "discount_code" text, + "created_at" timestamp with time zone not null 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" --- on "public"."connected_online_reservation" --- as permissive --- for all --- to service_role --- using (true); +create policy "service_role_all" +on "public"."connected_online_reservation" +as permissive +for all +to service_role +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" --- on "public"."connected_online_providers" --- as permissive --- for all --- to service_role --- using (true); +create policy "service_role_all" +on "public"."connected_online_providers" +as permissive +for all +to service_role +using (true); --- create policy "service_role_all" --- on "public"."connected_online_services" --- as permissive --- for all --- to service_role --- using (true); +create policy "service_role_all" +on "public"."connected_online_services" +as permissive +for all +to service_role +using (true); --- create policy "authenticated_select" --- on "public"."connected_online_providers" --- as permissive --- for select --- to authenticated --- using (true); +create policy "authenticated_select" +on "public"."connected_online_providers" +as permissive +for select +to authenticated +using (true); --- create policy "authenticated_select" --- on "public"."connected_online_services" --- as permissive --- for select --- to authenticated --- using (true); +create policy "authenticated_select" +on "public"."connected_online_services" +as permissive +for select +to authenticated +using (true); --- create policy "own_all" --- on "public"."connected_online_reservation" --- as permissive --- for all --- to authenticated --- using ((( SELECT auth.uid() AS uid) = user_id)); \ No newline at end of file +create policy "own_all" +on "public"."connected_online_reservation" +as permissive +for all +to authenticated +using ((( SELECT auth.uid() AS uid) = user_id)); \ No newline at end of file diff --git a/supabase/migrations/20250619070038_add_medreport_product_tables.sql b/supabase/migrations/20250619070038_add_medreport_product_tables.sql index d861040..938fe6e 100644 --- a/supabase/migrations/20250619070038_add_medreport_product_tables.sql +++ b/supabase/migrations/20250619070038_add_medreport_product_tables.sql @@ -1,225 +1,225 @@ --- create table "public"."medreport_product_groups" ( --- "id" bigint generated by default as identity not null, --- "name" text not null, --- "created_at" timestamp with time zone not null default now(), --- "updated_at" timestamp with time zone --- ); +create table "public"."medreport_product_groups" ( + "id" bigint generated by default as identity not null, + "name" text not null, + "created_at" timestamp with time zone not null default now(), + "updated_at" timestamp with time zone +); --- create table "public"."medreport_products" ( --- "id" bigint generated by default as identity not null, --- "name" text not null, --- "product_group_id" bigint, --- "created_at" timestamp with time zone not null default now(), --- "updated_at" timestamp with time zone default now() --- ); +create table "public"."medreport_products" ( + "id" bigint generated by default as identity not null, + "name" text not null, + "product_group_id" bigint, + "created_at" timestamp with time zone not null 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" ( --- "product_id" bigint not null, --- "analysis_element_id" bigint, --- "analysis_id" bigint --- ); +create table "public"."medreport_products_analyses_relations" ( + "product_id" bigint not null, + "analysis_element_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" --- on "public"."medreport_products_analyses_relations" --- as permissive --- for select --- to public --- using (true); +create policy "Enable read access for all users" +on "public"."medreport_products_analyses_relations" +as permissive +for select +to public +using (true); --- ALTER TABLE medreport_products_analyses_relations --- ADD CONSTRAINT product_can_be_tied_to_only_one_analysis_item --- CHECK (analysis_id IS NULL OR analysis_element_id IS NULL); +ALTER TABLE medreport_products_analyses_relations +ADD CONSTRAINT product_can_be_tied_to_only_one_analysis_item +CHECK (analysis_id IS NULL OR analysis_element_id IS NULL); --- create table "public"."medreport_products_external_services_relations" ( --- "product_id" bigint not null, --- "connected_online_service_id" bigint not null --- ); +create table "public"."medreport_products_external_services_relations" ( + "product_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() --- RETURNS TRIGGER AS $$ --- BEGIN --- IF EXISTS ( --- SELECT 1 --- FROM medreport_products_external_services_relations --- WHERE product_id = NEW.product_id --- ) THEN --- RAISE EXCEPTION 'Value "%" already exists in medreport_products_external_services_relations', NEW.product_id; --- END IF; +CREATE OR REPLACE FUNCTION check_tied_to_connected_online() +RETURNS TRIGGER AS $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM medreport_products_external_services_relations + WHERE product_id = NEW.product_id + ) THEN + RAISE EXCEPTION 'Value "%" already exists in medreport_products_external_services_relations', NEW.product_id; + END IF; --- RETURN NEW; --- END; --- $$ LANGUAGE plpgsql; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; --- CREATE OR REPLACE FUNCTION check_tied_to_analysis_item() --- RETURNS TRIGGER AS $$ --- BEGIN --- IF EXISTS ( --- SELECT 1 --- FROM medreport_products_analyses_relations --- WHERE product_id = NEW.product_id --- ) THEN --- RAISE EXCEPTION 'Value "%" already exists in medreport_products_analyses_relations', NEW.product_id; --- END IF; +CREATE OR REPLACE FUNCTION check_tied_to_analysis_item() +RETURNS TRIGGER AS $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM medreport_products_analyses_relations + WHERE product_id = NEW.product_id + ) THEN + RAISE EXCEPTION 'Value "%" already exists in medreport_products_analyses_relations', NEW.product_id; + END IF; --- RETURN NEW; --- END; --- $$ LANGUAGE plpgsql; + RETURN NEW; +END; +$$ 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" --- on "public"."medreport_product_groups" --- as permissive --- for select --- to public --- using (true); +create policy "read_all" +on "public"."medreport_product_groups" +as permissive +for select +to public +using (true); diff --git a/supabase/migrations/20250703145757_add_medreport_schema.sql b/supabase/migrations/20250703145757_add_medreport_schema.sql index 7b7ac0e..19dc5cc 100644 --- a/supabase/migrations/20250703145757_add_medreport_schema.sql +++ b/supabase/migrations/20250703145757_add_medreport_schema.sql @@ -680,17 +680,17 @@ drop policy "accounts_self_update" 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_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"; @@ -742,53 +742,53 @@ drop policy "invitations_read_self" 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 "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_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 "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 "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 "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 "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 "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 "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"; @@ -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"."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"; @@ -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"."nonces" drop constraint "nonces_pkey"; +alter table "public"."nonces" drop constraint "nonces_pkey"; alter table "public"."notifications" drop constraint "notifications_pkey"; @@ -1876,11 +1876,12 @@ BEGIN END;$function$ ; -CREATE OR REPLACE FUNCTION medreport.create_team_account(account_name text) - RETURNS medreport.accounts - LANGUAGE plpgsql - SET search_path TO '' -AS $function$declare +create + or replace function medreport.create_team_account (account_name text) returns medreport.accounts + SECURITY DEFINER + set + search_path = '' as $$ +declare new_account medreport.accounts; begin if (not medreport.is_set('enable_team_accounts')) then @@ -1898,8 +1899,13 @@ begin 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) 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())); -create policy "accounts_read" -on "medreport"."accounts" -as permissive -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))); +create policy accounts_read on medreport.accounts for + select + to authenticated using ( + ( + ( + 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" -on "medreport"."accounts" -as permissive -for update -to authenticated -using ((( SELECT auth.uid() AS uid) = primary_owner_user_id)) -with check ((( SELECT auth.uid() AS uid) = primary_owner_user_id)); +create policy accounts_self_update on medreport.accounts + for update + to authenticated using ( + ( + select + auth.uid () + ) = primary_owner_user_id + ) + with + check ( + ( + select + auth.uid () + ) = primary_owner_user_id + ); -create policy "create_org_account" -on "medreport"."accounts" -as permissive -for insert -to authenticated -with check ((medreport.is_set('enable_team_accounts'::text) AND (is_personal_account = false))); +create policy create_org_account on medreport.accounts for insert to authenticated + with + check ( + medreport.is_set ('enable_team_accounts') + and is_personal_account = false + ); 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 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"; @@ -5410,7 +5430,7 @@ drop table "public"."medreport_products_analyses_relations"; drop table "public"."medreport_products_external_services_relations"; --- drop table "public"."nonces"; +drop table "public"."nonces"; drop table "public"."notifications"; diff --git a/supabase/migrations/20250707150416_membership_confirmation.sql b/supabase/migrations/20250707150416_membership_confirmation.sql index bcb738b..052c2a7 100644 --- a/supabase/migrations/20250707150416_membership_confirmation.sql +++ b/supabase/migrations/20250707150416_membership_confirmation.sql @@ -40,4 +40,8 @@ END;$function$ grant execute on function medreport.has_consent_personal_data(uuid) to authenticated, anon; +-- we allow the authenticated role to execute functions in the medreport schema 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; \ No newline at end of file diff --git a/supabase/migrations/20250717075135_montonio_type.sql b/supabase/migrations/20250717075135_montonio_type.sql index 5cc840f..e510d2a 100644 --- a/supabase/migrations/20250717075135_montonio_type.sql +++ b/supabase/migrations/20250717075135_montonio_type.sql @@ -1 +1 @@ -alter type public.billing_provider add value 'montonio'; +alter type medreport.billing_provider add value 'montonio'; diff --git a/supabase/migrations/20250722110506_super_admin_fix.sql b/supabase/migrations/20250722110506_super_admin_fix.sql new file mode 100644 index 0000000..9cfe948 --- /dev/null +++ b/supabase/migrations/20250722110506_super_admin_fix.sql @@ -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; \ No newline at end of file diff --git a/supabase/migrations/20250723114200_company_params.sql b/supabase/migrations/20250723114200_company_params.sql new file mode 100644 index 0000000..bdf8b54 --- /dev/null +++ b/supabase/migrations/20250723114200_company_params.sql @@ -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; diff --git a/supabase/sql/super-admin.sql b/supabase/sql/super-admin.sql new file mode 100644 index 0000000..aabb056 --- /dev/null +++ b/supabase/sql/super-admin.sql @@ -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'); \ No newline at end of file From 0bd67ec4e88265f0dee054c0cf2ff4f02f79ad60 Mon Sep 17 00:00:00 2001 From: Danel Kungla Date: Wed, 23 Jul 2025 17:01:13 +0300 Subject: [PATCH 2/2] refactor: move role creation and permissions to super_admin_fix.sql --- .../20250722110506_super_admin_fix.sql | 16 +++++++++++++++- supabase/sql/super-admin.sql | 16 +--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/supabase/migrations/20250722110506_super_admin_fix.sql b/supabase/migrations/20250722110506_super_admin_fix.sql index 9cfe948..eb12975 100644 --- a/supabase/migrations/20250722110506_super_admin_fix.sql +++ b/supabase/migrations/20250722110506_super_admin_fix.sql @@ -29,4 +29,18 @@ 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; \ No newline at end of file +grant execute on function medreport.get_account_invitations(text) to authenticated, service_role; + +-- 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'); \ No newline at end of file diff --git a/supabase/sql/super-admin.sql b/supabase/sql/super-admin.sql index aabb056..2133379 100644 --- a/supabase/sql/super-admin.sql +++ b/supabase/sql/super-admin.sql @@ -1,16 +1,2 @@ -- 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'); \ No newline at end of file +update auth.users set raw_app_meta_data='{"provider": "email", "providers": ["email"], "role": "super-admin" }' where email='test2@test.ee'; \ No newline at end of file