-- Vendor email mapping for inbound bill parsing CREATE TABLE public.vendor_email_mappings ( id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, email TEXT NOT NULL, association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE, vendor_id UUID REFERENCES public.vendors(id) ON DELETE SET NULL, vendor_name TEXT, notes TEXT, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), created_by UUID, UNIQUE (email, association_id) ); CREATE INDEX idx_vendor_email_mappings_email ON public.vendor_email_mappings (lower(email)); CREATE INDEX idx_vendor_email_mappings_association ON public.vendor_email_mappings (association_id); ALTER TABLE public.vendor_email_mappings ENABLE ROW LEVEL SECURITY; CREATE POLICY "Staff can view vendor email mappings" ON public.vendor_email_mappings FOR SELECT TO authenticated USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)); CREATE POLICY "Staff can manage vendor email mappings" ON public.vendor_email_mappings FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)) WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)); CREATE TRIGGER update_vendor_email_mappings_updated_at BEFORE UPDATE ON public.vendor_email_mappings FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); -- Quarantine table for inbound bill emails (unknown sender or parse failure) CREATE TABLE public.inbound_bill_emails ( id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, from_email TEXT, from_name TEXT, to_email TEXT, subject TEXT, body_text TEXT, attachment_url TEXT, attachment_filename TEXT, attachment_storage_path TEXT, parsed_data JSONB, status TEXT NOT NULL DEFAULT 'unassigned', -- unassigned | processed | rejected | error error_message TEXT, association_id UUID REFERENCES public.associations(id) ON DELETE SET NULL, bill_id UUID REFERENCES public.bills(id) ON DELETE SET NULL, raw_payload JSONB, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), processed_at TIMESTAMP WITH TIME ZONE, processed_by UUID ); CREATE INDEX idx_inbound_bill_emails_status ON public.inbound_bill_emails (status, created_at DESC); CREATE INDEX idx_inbound_bill_emails_assoc ON public.inbound_bill_emails (association_id); ALTER TABLE public.inbound_bill_emails ENABLE ROW LEVEL SECURITY; CREATE POLICY "Staff can view inbound bill emails" ON public.inbound_bill_emails FOR SELECT TO authenticated USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)); CREATE POLICY "Staff can manage inbound bill emails" ON public.inbound_bill_emails FOR ALL TO authenticated USING (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)) WITH CHECK (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)); CREATE TRIGGER update_inbound_bill_emails_updated_at BEFORE UPDATE ON public.inbound_bill_emails FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column(); -- Storage bucket for inbound email attachments (private) INSERT INTO storage.buckets (id, name, public) VALUES ('inbound-bill-attachments', 'inbound-bill-attachments', false) ON CONFLICT (id) DO NOTHING; CREATE POLICY "Staff can read inbound bill attachments" ON storage.objects FOR SELECT TO authenticated USING ( bucket_id = 'inbound-bill-attachments' AND (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)) ); CREATE POLICY "Staff can upload inbound bill attachments" ON storage.objects FOR INSERT TO authenticated WITH CHECK ( bucket_id = 'inbound-bill-attachments' AND (public.has_role(auth.uid(), 'admin'::public.app_role) OR public.has_role(auth.uid(), 'manager'::public.app_role)) );