mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
150 lines
6.8 KiB
PL/PgSQL
150 lines
6.8 KiB
PL/PgSQL
|
|
-- RV/Boat Lot inventory
|
|
CREATE TABLE public.rv_boat_lots (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
|
lot_number TEXT NOT NULL,
|
|
lot_type TEXT NOT NULL DEFAULT 'rv', -- 'rv' | 'boat' | 'either'
|
|
size TEXT,
|
|
monthly_rate NUMERIC(10,2),
|
|
notes TEXT,
|
|
status TEXT NOT NULL DEFAULT 'available', -- 'available' | 'occupied' | 'maintenance'
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE (association_id, lot_number)
|
|
);
|
|
|
|
-- Active rentals (assignments to owners)
|
|
CREATE TABLE public.rv_boat_lot_rentals (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
|
lot_id UUID NOT NULL REFERENCES public.rv_boat_lots(id) ON DELETE CASCADE,
|
|
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
|
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
|
renter_name TEXT NOT NULL,
|
|
renter_email TEXT,
|
|
renter_phone TEXT,
|
|
vehicle_description TEXT,
|
|
start_date DATE NOT NULL DEFAULT CURRENT_DATE,
|
|
end_date DATE,
|
|
monthly_rate NUMERIC(10,2),
|
|
status TEXT NOT NULL DEFAULT 'active', -- 'active' | 'ended'
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Waitlist
|
|
CREATE TABLE public.rv_boat_lot_waitlist (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
association_id UUID NOT NULL REFERENCES public.associations(id) ON DELETE CASCADE,
|
|
position INTEGER NOT NULL,
|
|
requester_name TEXT NOT NULL,
|
|
requester_email TEXT,
|
|
requester_phone TEXT,
|
|
unit_address TEXT,
|
|
owner_id UUID REFERENCES public.owners(id) ON DELETE SET NULL,
|
|
unit_id UUID REFERENCES public.units(id) ON DELETE SET NULL,
|
|
requested_lot_type TEXT DEFAULT 'either', -- 'rv' | 'boat' | 'either'
|
|
vehicle_description TEXT,
|
|
notes TEXT,
|
|
status TEXT NOT NULL DEFAULT 'waiting', -- 'waiting' | 'fulfilled' | 'removed'
|
|
source TEXT NOT NULL DEFAULT 'manual', -- 'manual' | 'public_form'
|
|
fulfilled_at TIMESTAMPTZ,
|
|
fulfilled_lot_number TEXT,
|
|
fulfilled_by UUID REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX rv_boat_lots_assoc_idx ON public.rv_boat_lots(association_id);
|
|
CREATE INDEX rv_boat_lot_rentals_assoc_idx ON public.rv_boat_lot_rentals(association_id);
|
|
CREATE INDEX rv_boat_lot_rentals_lot_idx ON public.rv_boat_lot_rentals(lot_id);
|
|
CREATE INDEX rv_boat_lot_waitlist_assoc_pos_idx ON public.rv_boat_lot_waitlist(association_id, position) WHERE status = 'waiting';
|
|
|
|
-- Updated_at triggers
|
|
CREATE TRIGGER trg_rv_boat_lots_updated BEFORE UPDATE ON public.rv_boat_lots
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
CREATE TRIGGER trg_rv_boat_lot_rentals_updated BEFORE UPDATE ON public.rv_boat_lot_rentals
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
CREATE TRIGGER trg_rv_boat_lot_waitlist_updated BEFORE UPDATE ON public.rv_boat_lot_waitlist
|
|
FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();
|
|
|
|
-- Auto-assign next position on insert (for waiting entries)
|
|
CREATE OR REPLACE FUNCTION public.assign_rv_boat_waitlist_position()
|
|
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
|
BEGIN
|
|
IF NEW.position IS NULL OR NEW.position = 0 THEN
|
|
SELECT COALESCE(MAX(position), 0) + 1 INTO NEW.position
|
|
FROM public.rv_boat_lot_waitlist
|
|
WHERE association_id = NEW.association_id;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER trg_rv_boat_waitlist_position
|
|
BEFORE INSERT ON public.rv_boat_lot_waitlist
|
|
FOR EACH ROW EXECUTE FUNCTION public.assign_rv_boat_waitlist_position();
|
|
|
|
-- When a rental is created, mark lot occupied; when ended, mark available
|
|
CREATE OR REPLACE FUNCTION public.sync_rv_boat_lot_status()
|
|
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' AND NEW.status = 'active' THEN
|
|
UPDATE public.rv_boat_lots SET status = 'occupied', updated_at = now() WHERE id = NEW.lot_id;
|
|
ELSIF TG_OP = 'UPDATE' AND OLD.status <> NEW.status THEN
|
|
IF NEW.status = 'active' THEN
|
|
UPDATE public.rv_boat_lots SET status = 'occupied', updated_at = now() WHERE id = NEW.lot_id;
|
|
ELSIF NEW.status = 'ended' THEN
|
|
UPDATE public.rv_boat_lots SET status = 'available', updated_at = now() WHERE id = NEW.lot_id;
|
|
END IF;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE TRIGGER trg_sync_rv_boat_lot_status
|
|
AFTER INSERT OR UPDATE ON public.rv_boat_lot_rentals
|
|
FOR EACH ROW EXECUTE FUNCTION public.sync_rv_boat_lot_status();
|
|
|
|
-- Enable RLS
|
|
ALTER TABLE public.rv_boat_lots ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.rv_boat_lot_rentals ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.rv_boat_lot_waitlist ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Lots policies
|
|
CREATE POLICY "Staff and members can view lots" ON public.rv_boat_lots
|
|
FOR SELECT USING (public.user_belongs_to_association(auth.uid(), association_id));
|
|
CREATE POLICY "Staff can manage lots" ON public.rv_boat_lots
|
|
FOR ALL 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));
|
|
|
|
-- Rentals policies
|
|
CREATE POLICY "Staff and members can view rentals" ON public.rv_boat_lot_rentals
|
|
FOR SELECT USING (public.user_belongs_to_association(auth.uid(), association_id));
|
|
CREATE POLICY "Staff can manage rentals" ON public.rv_boat_lot_rentals
|
|
FOR ALL 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));
|
|
|
|
-- Waitlist policies
|
|
CREATE POLICY "Staff and members can view waitlist" ON public.rv_boat_lot_waitlist
|
|
FOR SELECT USING (public.user_belongs_to_association(auth.uid(), association_id));
|
|
CREATE POLICY "Staff can manage waitlist" ON public.rv_boat_lot_waitlist
|
|
FOR ALL 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));
|
|
|
|
-- Public can submit waitlist entries via published associations
|
|
CREATE POLICY "Public can submit waitlist entries" ON public.rv_boat_lot_waitlist
|
|
FOR INSERT TO anon, authenticated
|
|
WITH CHECK (
|
|
source = 'public_form'
|
|
AND status = 'waiting'
|
|
AND EXISTS (
|
|
SELECT 1 FROM public.association_public_pages app
|
|
WHERE app.association_id = rv_boat_lot_waitlist.association_id
|
|
AND app.is_published = true
|
|
)
|
|
);
|