From 4eb7da6427f9625f1b8ee94714db8f17d3e97f25 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 06:52:54 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/integrations/supabase/types.ts | 58 +++++++++++++ ...1_8122182d-2825-4d22-88c1-e6b62ab09ca5.sql | 87 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 supabase/migrations/20260418065251_8122182d-2825-4d22-88c1-e6b62ab09ca5.sql diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index ac52c0e..1f19e6e 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -183,10 +183,53 @@ export type Database = { }, ] } + case_team_members: { + Row: { + billing_rate: number | null + case_id: string + created_at: string + created_by: string | null + id: string + user_id: string + } + Insert: { + billing_rate?: number | null + case_id: string + created_at?: string + created_by?: string | null + id?: string + user_id: string + } + Update: { + billing_rate?: number | null + case_id?: string + created_at?: string + created_by?: string | null + id?: string + user_id?: string + } + Relationships: [ + { + foreignKeyName: "case_team_members_case_id_fkey" + columns: ["case_id"] + isOneToOne: false + referencedRelation: "cases" + referencedColumns: ["id"] + }, + { + foreignKeyName: "case_team_members_user_id_fkey" + columns: ["user_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + ] + } cases: { Row: { archived_at: string | null assigned_attorney_id: string | null + billing_method: string | null case_caption: string | null case_number: string case_summary: string | null @@ -201,6 +244,7 @@ export type Database = { description: string | null external_id: string | null filing_date: string | null + flat_fee_amount: number | null id: string judge: string | null jurisdiction: string | null @@ -214,6 +258,7 @@ export type Database = { opposing_counsel_firm: string | null opposing_counsel_phone: string | null opposing_party: string | null + originating_attorney_id: string | null practice_area: string | null settlement_amount: number | null status: Database["public"]["Enums"]["case_status"] @@ -224,6 +269,7 @@ export type Database = { Insert: { archived_at?: string | null assigned_attorney_id?: string | null + billing_method?: string | null case_caption?: string | null case_number: string case_summary?: string | null @@ -238,6 +284,7 @@ export type Database = { description?: string | null external_id?: string | null filing_date?: string | null + flat_fee_amount?: number | null id?: string judge?: string | null jurisdiction?: string | null @@ -251,6 +298,7 @@ export type Database = { opposing_counsel_firm?: string | null opposing_counsel_phone?: string | null opposing_party?: string | null + originating_attorney_id?: string | null practice_area?: string | null settlement_amount?: number | null status?: Database["public"]["Enums"]["case_status"] @@ -261,6 +309,7 @@ export type Database = { Update: { archived_at?: string | null assigned_attorney_id?: string | null + billing_method?: string | null case_caption?: string | null case_number?: string case_summary?: string | null @@ -275,6 +324,7 @@ export type Database = { description?: string | null external_id?: string | null filing_date?: string | null + flat_fee_amount?: number | null id?: string judge?: string | null jurisdiction?: string | null @@ -288,6 +338,7 @@ export type Database = { opposing_counsel_firm?: string | null opposing_counsel_phone?: string | null opposing_party?: string | null + originating_attorney_id?: string | null practice_area?: string | null settlement_amount?: number | null status?: Database["public"]["Enums"]["case_status"] @@ -310,6 +361,13 @@ export type Database = { referencedRelation: "clients" referencedColumns: ["id"] }, + { + foreignKeyName: "cases_originating_attorney_id_fkey" + columns: ["originating_attorney_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, ] } client_contacts: { diff --git a/supabase/migrations/20260418065251_8122182d-2825-4d22-88c1-e6b62ab09ca5.sql b/supabase/migrations/20260418065251_8122182d-2825-4d22-88c1-e6b62ab09ca5.sql new file mode 100644 index 0000000..1421c73 --- /dev/null +++ b/supabase/migrations/20260418065251_8122182d-2825-4d22-88c1-e6b62ab09ca5.sql @@ -0,0 +1,87 @@ +-- 1. Add billing fields and originating attorney to cases +ALTER TABLE public.cases + ADD COLUMN IF NOT EXISTS billing_method text, + ADD COLUMN IF NOT EXISTS flat_fee_amount numeric, + ADD COLUMN IF NOT EXISTS originating_attorney_id uuid REFERENCES public.profiles(id) ON DELETE SET NULL; + +-- 2. Case team members table +CREATE TABLE IF NOT EXISTS public.case_team_members ( + id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, + case_id uuid NOT NULL REFERENCES public.cases(id) ON DELETE CASCADE, + user_id uuid NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + billing_rate numeric, + created_at timestamptz NOT NULL DEFAULT now(), + created_by uuid, + UNIQUE (case_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_case_team_members_case ON public.case_team_members(case_id); +CREATE INDEX IF NOT EXISTS idx_case_team_members_user ON public.case_team_members(user_id); + +ALTER TABLE public.case_team_members ENABLE ROW LEVEL SECURITY; + +-- 3. Update can_access_case to also include team members +CREATE OR REPLACE FUNCTION public.can_access_case(_case_id uuid, _user_id uuid) +RETURNS boolean +LANGUAGE sql +STABLE SECURITY DEFINER +SET search_path TO 'public' +AS $function$ + SELECT + public.has_role(_user_id, 'admin') + OR EXISTS ( + SELECT 1 FROM public.cases c + WHERE c.id = _case_id + AND ( + c.assigned_attorney_id = _user_id + OR c.created_by = _user_id + OR c.originating_attorney_id = _user_id + ) + ) + OR EXISTS ( + SELECT 1 FROM public.case_team_members tm + WHERE tm.case_id = _case_id AND tm.user_id = _user_id + ) +$function$; + +-- 4. RLS policies for case_team_members +DROP POLICY IF EXISTS ctm_select ON public.case_team_members; +CREATE POLICY ctm_select ON public.case_team_members + FOR SELECT TO authenticated + USING (public.can_access_case(case_id, auth.uid())); + +DROP POLICY IF EXISTS ctm_insert ON public.case_team_members; +CREATE POLICY ctm_insert ON public.case_team_members + FOR INSERT TO authenticated + WITH CHECK ( + public.is_admin(auth.uid()) + OR EXISTS ( + SELECT 1 FROM public.cases c + WHERE c.id = case_id + AND (c.assigned_attorney_id = auth.uid() OR c.created_by = auth.uid() OR c.originating_attorney_id = auth.uid()) + ) + ); + +DROP POLICY IF EXISTS ctm_delete ON public.case_team_members; +CREATE POLICY ctm_delete ON public.case_team_members + FOR DELETE TO authenticated + USING ( + public.is_admin(auth.uid()) + OR EXISTS ( + SELECT 1 FROM public.cases c + WHERE c.id = case_id + AND (c.assigned_attorney_id = auth.uid() OR c.created_by = auth.uid() OR c.originating_attorney_id = auth.uid()) + ) + ); + +DROP POLICY IF EXISTS ctm_update ON public.case_team_members; +CREATE POLICY ctm_update ON public.case_team_members + FOR UPDATE TO authenticated + USING ( + public.is_admin(auth.uid()) + OR EXISTS ( + SELECT 1 FROM public.cases c + WHERE c.id = case_id + AND (c.assigned_attorney_id = auth.uid() OR c.created_by = auth.uid() OR c.originating_attorney_id = auth.uid()) + ) + ); \ No newline at end of file