diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index fd008f6..8e7d9c6 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -250,12 +250,145 @@ export type Database = { }, ] } + collection_tasks: { + Row: { + assignee_id: string | null + collection_id: string + created_at: string + created_by: string | null + done: boolean + done_at: string | null + due_date: string | null + id: string + sort_order: number + stage_key: string | null + title: string + updated_at: string + } + Insert: { + assignee_id?: string | null + collection_id: string + created_at?: string + created_by?: string | null + done?: boolean + done_at?: string | null + due_date?: string | null + id?: string + sort_order?: number + stage_key?: string | null + title: string + updated_at?: string + } + Update: { + assignee_id?: string | null + collection_id?: string + created_at?: string + created_by?: string | null + done?: boolean + done_at?: string | null + due_date?: string | null + id?: string + sort_order?: number + stage_key?: string | null + title?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "collection_tasks_assignee_id_fkey" + columns: ["assignee_id"] + isOneToOne: false + referencedRelation: "profiles" + referencedColumns: ["id"] + }, + { + foreignKeyName: "collection_tasks_collection_id_fkey" + columns: ["collection_id"] + isOneToOne: false + referencedRelation: "collections" + referencedColumns: ["id"] + }, + { + foreignKeyName: "collection_tasks_stage_key_fkey" + columns: ["stage_key"] + isOneToOne: false + referencedRelation: "collection_workflow_stages" + referencedColumns: ["key"] + }, + ] + } + collection_workflow_stages: { + Row: { + created_at: string + id: string + key: string + label: string + sort_order: number + updated_at: string + } + Insert: { + created_at?: string + id?: string + key: string + label: string + sort_order?: number + updated_at?: string + } + Update: { + created_at?: string + id?: string + key?: string + label?: string + sort_order?: number + updated_at?: string + } + Relationships: [] + } + collection_workflow_tasks: { + Row: { + created_at: string + days_to_due: number + id: string + sort_order: number + stage_key: string + title: string + updated_at: string + } + Insert: { + created_at?: string + days_to_due?: number + id?: string + sort_order?: number + stage_key: string + title: string + updated_at?: string + } + Update: { + created_at?: string + days_to_due?: number + id?: string + sort_order?: number + stage_key?: string + title?: string + updated_at?: string + } + Relationships: [ + { + foreignKeyName: "collection_workflow_tasks_stage_key_fkey" + columns: ["stage_key"] + isOneToOne: false + referencedRelation: "collection_workflow_stages" + referencedColumns: ["key"] + }, + ] + } collections: { Row: { case_id: string closed_at: string | null created_at: string created_by: string | null + current_stage: string | null homeowner_id: string id: string notes: string | null @@ -268,6 +401,7 @@ export type Database = { closed_at?: string | null created_at?: string created_by?: string | null + current_stage?: string | null homeowner_id: string id?: string notes?: string | null @@ -280,6 +414,7 @@ export type Database = { closed_at?: string | null created_at?: string created_by?: string | null + current_stage?: string | null homeowner_id?: string id?: string notes?: string | null @@ -295,6 +430,13 @@ export type Database = { referencedRelation: "cases" referencedColumns: ["id"] }, + { + foreignKeyName: "collections_current_stage_fkey" + columns: ["current_stage"] + isOneToOne: false + referencedRelation: "collection_workflow_stages" + referencedColumns: ["key"] + }, { foreignKeyName: "collections_homeowner_id_fkey" columns: ["homeowner_id"] diff --git a/supabase/migrations/20260417011322_d0143111-f431-43ae-bac6-df5c9315f6c1.sql b/supabase/migrations/20260417011322_d0143111-f431-43ae-bac6-df5c9315f6c1.sql new file mode 100644 index 0000000..e20e1f0 --- /dev/null +++ b/supabase/migrations/20260417011322_d0143111-f431-43ae-bac6-df5c9315f6c1.sql @@ -0,0 +1,73 @@ +CREATE TABLE public.collection_workflow_stages ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + key text NOT NULL UNIQUE, + label text NOT NULL, + sort_order integer NOT NULL DEFAULT 0, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +ALTER TABLE public.collection_workflow_stages ENABLE ROW LEVEL SECURITY; +CREATE POLICY "stages_select_auth" ON public.collection_workflow_stages FOR SELECT TO authenticated USING (true); +CREATE POLICY "stages_insert_admin" ON public.collection_workflow_stages FOR INSERT TO authenticated WITH CHECK (public.is_admin(auth.uid())); +CREATE POLICY "stages_update_admin" ON public.collection_workflow_stages FOR UPDATE TO authenticated USING (public.is_admin(auth.uid())); +CREATE POLICY "stages_delete_admin" ON public.collection_workflow_stages FOR DELETE TO authenticated USING (public.is_admin(auth.uid())); +CREATE TRIGGER tg_stages_updated_at BEFORE UPDATE ON public.collection_workflow_stages FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); + +CREATE TABLE public.collection_workflow_tasks ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + stage_key text NOT NULL REFERENCES public.collection_workflow_stages(key) ON UPDATE CASCADE ON DELETE CASCADE, + title text NOT NULL, + days_to_due integer NOT NULL DEFAULT 7, + sort_order integer NOT NULL DEFAULT 0, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +ALTER TABLE public.collection_workflow_tasks ENABLE ROW LEVEL SECURITY; +CREATE POLICY "wf_tasks_select_auth" ON public.collection_workflow_tasks FOR SELECT TO authenticated USING (true); +CREATE POLICY "wf_tasks_insert_admin" ON public.collection_workflow_tasks FOR INSERT TO authenticated WITH CHECK (public.is_admin(auth.uid())); +CREATE POLICY "wf_tasks_update_admin" ON public.collection_workflow_tasks FOR UPDATE TO authenticated USING (public.is_admin(auth.uid())); +CREATE POLICY "wf_tasks_delete_admin" ON public.collection_workflow_tasks FOR DELETE TO authenticated USING (public.is_admin(auth.uid())); +CREATE TRIGGER tg_wf_tasks_updated_at BEFORE UPDATE ON public.collection_workflow_tasks FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); + +ALTER TABLE public.collections ADD COLUMN current_stage text REFERENCES public.collection_workflow_stages(key) ON UPDATE CASCADE; + +CREATE TABLE public.collection_tasks ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + collection_id uuid NOT NULL REFERENCES public.collections(id) ON DELETE CASCADE, + stage_key text REFERENCES public.collection_workflow_stages(key) ON UPDATE CASCADE, + title text NOT NULL, + due_date date, + assignee_id uuid REFERENCES public.profiles(id), + done boolean NOT NULL DEFAULT false, + done_at timestamptz, + sort_order integer NOT NULL DEFAULT 0, + created_by uuid, + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now() +); +ALTER TABLE public.collection_tasks ENABLE ROW LEVEL SECURITY; +CREATE POLICY "ctasks_select_case" ON public.collection_tasks FOR SELECT TO authenticated USING (EXISTS (SELECT 1 FROM public.collections c WHERE c.id = collection_tasks.collection_id AND public.can_access_case(c.case_id, auth.uid()))); +CREATE POLICY "ctasks_insert_case" ON public.collection_tasks FOR INSERT TO authenticated WITH CHECK (EXISTS (SELECT 1 FROM public.collections c WHERE c.id = collection_tasks.collection_id AND public.can_access_case(c.case_id, auth.uid()))); +CREATE POLICY "ctasks_update_case" ON public.collection_tasks FOR UPDATE TO authenticated USING (EXISTS (SELECT 1 FROM public.collections c WHERE c.id = collection_tasks.collection_id AND public.can_access_case(c.case_id, auth.uid()))); +CREATE POLICY "ctasks_delete_case" ON public.collection_tasks FOR DELETE TO authenticated USING (EXISTS (SELECT 1 FROM public.collections c WHERE c.id = collection_tasks.collection_id AND public.can_access_case(c.case_id, auth.uid()))); +CREATE TRIGGER tg_ctasks_updated_at BEFORE UPDATE ON public.collection_tasks FOR EACH ROW EXECUTE FUNCTION public.tg_set_updated_at(); +CREATE INDEX idx_ctasks_collection ON public.collection_tasks(collection_id); +CREATE INDEX idx_ctasks_assignee_open ON public.collection_tasks(assignee_id) WHERE done = false; + +INSERT INTO public.collection_workflow_stages (key, label, sort_order) VALUES + ('notice_of_late_assessment', 'Notice of Late Assessment', 10), + ('notice_of_intent_to_lien', 'Notice of Intent to Lien', 20), + ('notice_of_intent_to_foreclose', 'Notice of Intent to Foreclose', 30), + ('lien_foreclosure', 'Lien Foreclosure', 40); + +INSERT INTO public.collection_workflow_tasks (stage_key, title, days_to_due, sort_order) VALUES + ('notice_of_late_assessment', 'Send Notice of Late Assessment letter', 7, 10), + ('notice_of_late_assessment', 'Confirm delivery / certified mail receipt', 14, 20), + ('notice_of_intent_to_lien', 'Prepare and send Notice of Intent to Lien', 7, 10), + ('notice_of_intent_to_lien', 'Wait statutory cure period', 30, 20), + ('notice_of_intent_to_foreclose', 'File and record lien with county recorder', 7, 10), + ('notice_of_intent_to_foreclose', 'Serve Notice of Intent to Foreclose', 14, 20), + ('lien_foreclosure', 'Engage trustee / file foreclosure complaint', 14, 10), + ('lien_foreclosure', 'Schedule foreclosure sale', 60, 20); + +UPDATE public.collections SET current_stage = 'notice_of_late_assessment' WHERE current_stage IS NULL; \ No newline at end of file