mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
205 lines
6.3 KiB
PL/PgSQL
205 lines
6.3 KiB
PL/PgSQL
-- Flag rentals as owner-occupied (owner using their own lot)
|
|
ALTER TABLE public.rv_boat_lot_rentals
|
|
ADD COLUMN IF NOT EXISTS is_owner BOOLEAN NOT NULL DEFAULT false;
|
|
|
|
-- Helpers for rv_owner role (owner_ids and association_ids the user has access to)
|
|
CREATE OR REPLACE FUNCTION public.get_rv_owner_owner_ids(_user_id uuid DEFAULT auth.uid())
|
|
RETURNS SETOF uuid
|
|
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public
|
|
AS $$
|
|
SELECT DISTINCT r.owner_id
|
|
FROM public.rv_boat_lot_rentals r
|
|
WHERE r.user_id = _user_id
|
|
AND r.is_owner = true
|
|
AND r.owner_id IS NOT NULL
|
|
AND r.status = 'active'
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.get_rv_owner_association_ids(_user_id uuid DEFAULT auth.uid())
|
|
RETURNS SETOF uuid
|
|
LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public
|
|
AS $$
|
|
SELECT DISTINCT r.association_id
|
|
FROM public.rv_boat_lot_rentals r
|
|
WHERE r.user_id = _user_id
|
|
AND r.is_owner = true
|
|
AND r.status = 'active'
|
|
$$;
|
|
|
|
-- RV owners can read their own rental row (covers the is_owner = true rows)
|
|
CREATE POLICY "RV owners can view their own rental"
|
|
ON public.rv_boat_lot_rentals
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (user_id = auth.uid() AND is_owner = true);
|
|
|
|
-- RV owners can read their lot
|
|
CREATE POLICY "RV owners can view their lot"
|
|
ON public.rv_boat_lots
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.rv_boat_lot_rentals r
|
|
WHERE r.lot_id = rv_boat_lots.id
|
|
AND r.user_id = auth.uid()
|
|
AND r.is_owner = true
|
|
AND r.status = 'active'
|
|
)
|
|
);
|
|
|
|
-- RV owners can read their association
|
|
CREATE POLICY "RV owners can view their association"
|
|
ON public.associations
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (id IN (SELECT public.get_rv_owner_association_ids(auth.uid())));
|
|
|
|
-- RV owners can read the linked owner ledger
|
|
CREATE POLICY "RV owners can view linked owner ledger"
|
|
ON public.owner_ledger_entries
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (owner_id IN (SELECT public.get_rv_owner_owner_ids(auth.uid())));
|
|
|
|
-- RV owners can read the linked owner record
|
|
CREATE POLICY "RV owners can view linked owner"
|
|
ON public.owners
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (id IN (SELECT public.get_rv_owner_owner_ids(auth.uid())));
|
|
|
|
-- Intent to Vacate
|
|
CREATE TABLE IF NOT EXISTS public.rv_intent_to_vacate (
|
|
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
rental_id UUID NOT NULL REFERENCES public.rv_boat_lot_rentals(id) ON DELETE CASCADE,
|
|
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
|
lot_id UUID REFERENCES public.rv_boat_lots(id) ON DELETE SET NULL,
|
|
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
|
submitted_by_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
vacate_date DATE NOT NULL,
|
|
notes TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
acknowledged_at TIMESTAMP WITH TIME ZONE,
|
|
acknowledged_by_user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS rv_itv_rental_idx ON public.rv_intent_to_vacate(rental_id);
|
|
CREATE INDEX IF NOT EXISTS rv_itv_assoc_idx ON public.rv_intent_to_vacate(association_id);
|
|
CREATE INDEX IF NOT EXISTS rv_itv_user_idx ON public.rv_intent_to_vacate(submitted_by_user_id);
|
|
|
|
ALTER TABLE public.rv_intent_to_vacate ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE TRIGGER update_rv_intent_to_vacate_updated_at
|
|
BEFORE UPDATE ON public.rv_intent_to_vacate
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- RLS: RV owners manage their own intents
|
|
CREATE POLICY "RV owners can view their intents"
|
|
ON public.rv_intent_to_vacate
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (submitted_by_user_id = auth.uid());
|
|
|
|
CREATE POLICY "RV owners can submit intents for their rental"
|
|
ON public.rv_intent_to_vacate
|
|
FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (
|
|
submitted_by_user_id = auth.uid()
|
|
AND EXISTS (
|
|
SELECT 1 FROM public.rv_boat_lot_rentals r
|
|
WHERE r.id = rv_intent_to_vacate.rental_id
|
|
AND r.user_id = auth.uid()
|
|
AND r.is_owner = true
|
|
AND r.status = 'active'
|
|
)
|
|
);
|
|
|
|
CREATE POLICY "RV owners can cancel their pending intents"
|
|
ON public.rv_intent_to_vacate
|
|
FOR UPDATE
|
|
TO authenticated
|
|
USING (submitted_by_user_id = auth.uid() AND status = 'pending')
|
|
WITH CHECK (submitted_by_user_id = auth.uid());
|
|
|
|
-- Staff can manage all
|
|
CREATE POLICY "Staff can view all intents"
|
|
ON public.rv_intent_to_vacate
|
|
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 update all intents"
|
|
ON public.rv_intent_to_vacate
|
|
FOR UPDATE
|
|
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 POLICY "Staff can delete intents"
|
|
ON public.rv_intent_to_vacate
|
|
FOR DELETE
|
|
TO authenticated
|
|
USING (
|
|
public.has_role(auth.uid(), 'admin'::public.app_role)
|
|
OR public.has_role(auth.uid(), 'manager'::public.app_role)
|
|
);
|
|
|
|
-- Notify staff when an Intent to Vacate is submitted
|
|
CREATE OR REPLACE FUNCTION public.notify_staff_on_rv_intent_to_vacate()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public
|
|
AS $$
|
|
DECLARE
|
|
staff_user record;
|
|
v_renter text;
|
|
v_message text;
|
|
BEGIN
|
|
SELECT r.renter_name INTO v_renter
|
|
FROM public.rv_boat_lot_rentals r WHERE r.id = NEW.rental_id;
|
|
|
|
v_message := COALESCE(v_renter, 'An RV/Boat lot owner')
|
|
|| ' submitted an Intent to Vacate effective '
|
|
|| to_char(NEW.vacate_date, 'Mon DD, YYYY');
|
|
|
|
FOR staff_user IN
|
|
SELECT DISTINCT user_id FROM public.user_roles
|
|
WHERE role IN ('admin'::public.app_role, 'manager'::public.app_role)
|
|
AND user_id IS NOT NULL
|
|
LOOP
|
|
INSERT INTO public.in_app_notifications
|
|
(user_id, type, title, message, related_item_id, related_item_type, link)
|
|
VALUES (
|
|
staff_user.user_id,
|
|
'rv_intent_to_vacate',
|
|
'RV/Boat Lot — Intent to Vacate',
|
|
v_message,
|
|
NEW.id,
|
|
'rv_intent_to_vacate',
|
|
'/dashboard/rv-boat-lots'
|
|
);
|
|
END LOOP;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER trg_notify_staff_on_rv_intent_to_vacate
|
|
AFTER INSERT ON public.rv_intent_to_vacate
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.notify_staff_on_rv_intent_to_vacate(); |