mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
|
||||
-- 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
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user