214 lines
7.7 KiB
PL/PgSQL
214 lines
7.7 KiB
PL/PgSQL
-- CREdispo Local PostgreSQL Schema (no Supabase dependencies)
|
|
|
|
-- Enable UUID extension
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- ============================================
|
|
-- USERS TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
email TEXT UNIQUE NOT NULL,
|
|
password_hash TEXT,
|
|
role TEXT NOT NULL DEFAULT 'agent' CHECK (role IN ('admin', 'agent', 'buyer')),
|
|
name TEXT NOT NULL,
|
|
company TEXT,
|
|
phone TEXT,
|
|
avatar_url TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- SELLER LEADS TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.seller_leads (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
agent_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
|
address TEXT NOT NULL,
|
|
asset_type TEXT NOT NULL CHECK (asset_type IN (
|
|
'Office', 'Industrial', 'Retail', 'Multifamily',
|
|
'Mixed-Use', 'Land', 'Hospitality', 'Other'
|
|
)),
|
|
asking_price NUMERIC(15, 2),
|
|
cap_rate NUMERIC(5, 2),
|
|
noi NUMERIC(15, 2),
|
|
sqft INTEGER,
|
|
units INTEGER,
|
|
year_built INTEGER,
|
|
market TEXT NOT NULL,
|
|
city TEXT,
|
|
state TEXT,
|
|
notes TEXT,
|
|
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'pending', 'matched', 'closed', 'withdrawn')),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- SELLER DOCUMENTS TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.seller_documents (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
lead_id UUID NOT NULL REFERENCES public.seller_leads(id) ON DELETE CASCADE,
|
|
file_url TEXT NOT NULL,
|
|
file_name TEXT NOT NULL,
|
|
file_type TEXT NOT NULL,
|
|
file_size INTEGER,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- BUYERS TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.buyers (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
agent_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
name TEXT NOT NULL,
|
|
company TEXT,
|
|
email TEXT NOT NULL,
|
|
phone TEXT,
|
|
asset_types TEXT[] NOT NULL DEFAULT '{}',
|
|
target_locations TEXT[] NOT NULL DEFAULT '{}',
|
|
price_min NUMERIC(15, 2),
|
|
price_max NUMERIC(15, 2),
|
|
cap_rate_min NUMERIC(5, 2),
|
|
cap_rate_max NUMERIC(5, 2),
|
|
preferences TEXT,
|
|
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'unresponsive', 'high-intent', 'inactive')),
|
|
engagement_score INTEGER DEFAULT 0,
|
|
last_contacted_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- MATCHES TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.matches (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
lead_id UUID NOT NULL REFERENCES public.seller_leads(id) ON DELETE CASCADE,
|
|
buyer_id UUID NOT NULL REFERENCES public.buyers(id) ON DELETE CASCADE,
|
|
match_score NUMERIC(5, 2) NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'sent', 'interested', 'passed', 'closed')),
|
|
agent_notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(lead_id, buyer_id)
|
|
);
|
|
|
|
-- ============================================
|
|
-- OUTREACH TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.outreach (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
match_id UUID NOT NULL REFERENCES public.matches(id) ON DELETE CASCADE,
|
|
channel TEXT NOT NULL CHECK (channel IN ('email', 'sms')),
|
|
subject TEXT,
|
|
body TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'queued', 'sent', 'delivered', 'opened', 'clicked', 'replied', 'bounced', 'failed')),
|
|
sent_at TIMESTAMPTZ,
|
|
delivered_at TIMESTAMPTZ,
|
|
opened_at TIMESTAMPTZ,
|
|
clicked_at TIMESTAMPTZ,
|
|
replied_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- NDAs TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.ndas (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
buyer_id UUID NOT NULL REFERENCES public.buyers(id) ON DELETE CASCADE,
|
|
lead_id UUID NOT NULL REFERENCES public.seller_leads(id) ON DELETE CASCADE,
|
|
signer_name TEXT NOT NULL,
|
|
signer_email TEXT NOT NULL,
|
|
signer_company TEXT,
|
|
signed_at TIMESTAMPTZ DEFAULT NOW(),
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(buyer_id, lead_id)
|
|
);
|
|
|
|
-- ============================================
|
|
-- SUBSCRIPTIONS TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.subscriptions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
|
stripe_customer_id TEXT UNIQUE,
|
|
stripe_subscription_id TEXT UNIQUE,
|
|
status TEXT NOT NULL DEFAULT 'inactive' CHECK (status IN ('active', 'inactive', 'past_due', 'canceled', 'trialing')),
|
|
plan TEXT DEFAULT 'pro',
|
|
current_period_start TIMESTAMPTZ,
|
|
current_period_end TIMESTAMPTZ,
|
|
cancel_at_period_end BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- PROMO CODES TABLE
|
|
-- ============================================
|
|
CREATE TABLE public.promo_codes (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
code TEXT UNIQUE NOT NULL,
|
|
discount_percent INTEGER CHECK (discount_percent BETWEEN 1 AND 100),
|
|
discount_amount NUMERIC(10, 2),
|
|
max_uses INTEGER,
|
|
current_uses INTEGER DEFAULT 0,
|
|
expires_at TIMESTAMPTZ,
|
|
active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- ============================================
|
|
-- INDEXES
|
|
-- ============================================
|
|
CREATE INDEX idx_seller_leads_agent ON public.seller_leads(agent_id);
|
|
CREATE INDEX idx_seller_leads_status ON public.seller_leads(status);
|
|
CREATE INDEX idx_seller_leads_asset_type ON public.seller_leads(asset_type);
|
|
CREATE INDEX idx_seller_leads_market ON public.seller_leads(market);
|
|
CREATE INDEX idx_buyers_agent ON public.buyers(agent_id);
|
|
CREATE INDEX idx_buyers_status ON public.buyers(status);
|
|
CREATE INDEX idx_matches_lead ON public.matches(lead_id);
|
|
CREATE INDEX idx_matches_buyer ON public.matches(buyer_id);
|
|
CREATE INDEX idx_matches_status ON public.matches(status);
|
|
CREATE INDEX idx_outreach_match ON public.outreach(match_id);
|
|
CREATE INDEX idx_outreach_status ON public.outreach(status);
|
|
CREATE INDEX idx_ndas_buyer ON public.ndas(buyer_id);
|
|
CREATE INDEX idx_ndas_lead ON public.ndas(lead_id);
|
|
CREATE INDEX idx_subscriptions_user ON public.subscriptions(user_id);
|
|
CREATE INDEX idx_subscriptions_status ON public.subscriptions(status);
|
|
|
|
-- ============================================
|
|
-- FUNCTIONS
|
|
-- ============================================
|
|
|
|
-- Auto-update updated_at timestamp
|
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON public.users
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE TRIGGER update_seller_leads_updated_at BEFORE UPDATE ON public.seller_leads
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE TRIGGER update_buyers_updated_at BEFORE UPDATE ON public.buyers
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE TRIGGER update_matches_updated_at BEFORE UPDATE ON public.matches
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
CREATE TRIGGER update_subscriptions_updated_at BEFORE UPDATE ON public.subscriptions
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|