344 lines
14 KiB
SQL
344 lines
14 KiB
SQL
-- ============================================================
|
|
-- Migration: Unified Billing System
|
|
-- Timestamp: 20260322260000
|
|
-- Covers: billing_companies, billing_clients, billing_invoices,
|
|
-- billing_line_items, billing_payments, billing_sync_log,
|
|
-- ad_campaigns table, billing settings
|
|
-- ============================================================
|
|
|
|
-- ─── 1. billing_companies ────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_companies (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
legal_name TEXT NOT NULL,
|
|
llc_entity TEXT,
|
|
address TEXT,
|
|
email TEXT,
|
|
invoice_prefix TEXT NOT NULL DEFAULT 'INV-',
|
|
stripe_account_id TEXT,
|
|
mooninvoice_company_id TEXT,
|
|
mooninvoice_api_key TEXT,
|
|
stripe_secret_key TEXT,
|
|
stripe_publishable_key TEXT,
|
|
default_payment_terms TEXT NOT NULL DEFAULT 'Net 30',
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_companies_active ON public.billing_companies(is_active);
|
|
|
|
ALTER TABLE public.billing_companies ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_companies" ON public.billing_companies;
|
|
CREATE POLICY "admin_manage_billing_companies"
|
|
ON public.billing_companies FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 2. billing_clients ──────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_clients (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
company_id UUID REFERENCES public.billing_companies(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
organization TEXT,
|
|
email TEXT,
|
|
phone TEXT,
|
|
billing_address TEXT,
|
|
notes TEXT,
|
|
mooninvoice_contact_id TEXT,
|
|
stripe_customer_id TEXT,
|
|
total_billed NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
total_paid NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
invoice_consolidation BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_clients_company_id ON public.billing_clients(company_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_clients_mooninvoice_id ON public.billing_clients(mooninvoice_contact_id);
|
|
|
|
ALTER TABLE public.billing_clients ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_clients" ON public.billing_clients;
|
|
CREATE POLICY "admin_manage_billing_clients"
|
|
ON public.billing_clients FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 3. billing_invoices ─────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_invoices (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
company_id UUID REFERENCES public.billing_companies(id) ON DELETE CASCADE,
|
|
client_id UUID REFERENCES public.billing_clients(id) ON DELETE SET NULL,
|
|
invoice_number TEXT NOT NULL,
|
|
issue_date DATE,
|
|
due_date DATE,
|
|
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','sent','viewed','paid','overdue','cancelled')),
|
|
subtotal NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
tax NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
total NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
amount_paid NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
currency TEXT NOT NULL DEFAULT 'USD',
|
|
notes TEXT,
|
|
internal_memo TEXT,
|
|
stripe_payment_link TEXT,
|
|
mooninvoice_invoice_id TEXT,
|
|
pdf_url TEXT,
|
|
paid_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_invoices_company_id ON public.billing_invoices(company_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_invoices_client_id ON public.billing_invoices(client_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_invoices_status ON public.billing_invoices(status);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_invoices_mooninvoice_id ON public.billing_invoices(mooninvoice_invoice_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_invoices_due_date ON public.billing_invoices(due_date);
|
|
|
|
ALTER TABLE public.billing_invoices ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_invoices" ON public.billing_invoices;
|
|
CREATE POLICY "admin_manage_billing_invoices"
|
|
ON public.billing_invoices FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 4. billing_line_items ───────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_line_items (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
invoice_id UUID REFERENCES public.billing_invoices(id) ON DELETE CASCADE,
|
|
description TEXT NOT NULL,
|
|
quantity NUMERIC(10,2) NOT NULL DEFAULT 1,
|
|
rate NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
campaign_id UUID,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_line_items_invoice_id ON public.billing_line_items(invoice_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_line_items_campaign_id ON public.billing_line_items(campaign_id);
|
|
|
|
ALTER TABLE public.billing_line_items ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_line_items" ON public.billing_line_items;
|
|
CREATE POLICY "admin_manage_billing_line_items"
|
|
ON public.billing_line_items FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 5. billing_payments ─────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_payments (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
invoice_id UUID REFERENCES public.billing_invoices(id) ON DELETE SET NULL,
|
|
client_id UUID REFERENCES public.billing_clients(id) ON DELETE SET NULL,
|
|
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
|
|
payment_date DATE,
|
|
payment_method TEXT,
|
|
stripe_charge_id TEXT,
|
|
stripe_payment_intent_id TEXT,
|
|
notes TEXT,
|
|
status TEXT NOT NULL DEFAULT 'succeeded' CHECK (status IN ('succeeded','failed','pending','refunded','disputed')),
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_payments_invoice_id ON public.billing_payments(invoice_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_payments_client_id ON public.billing_payments(client_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_payments_stripe_charge ON public.billing_payments(stripe_charge_id);
|
|
|
|
ALTER TABLE public.billing_payments ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_payments" ON public.billing_payments;
|
|
CREATE POLICY "admin_manage_billing_payments"
|
|
ON public.billing_payments FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 6. billing_sync_log ─────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_sync_log (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
sync_type TEXT NOT NULL,
|
|
company_id UUID REFERENCES public.billing_companies(id) ON DELETE SET NULL,
|
|
records_synced INTEGER NOT NULL DEFAULT 0,
|
|
errors JSONB,
|
|
started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
completed_at TIMESTAMPTZ,
|
|
status TEXT NOT NULL DEFAULT 'running' CHECK (status IN ('running','completed','failed'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_billing_sync_log_company_id ON public.billing_sync_log(company_id);
|
|
CREATE INDEX IF NOT EXISTS idx_billing_sync_log_started_at ON public.billing_sync_log(started_at);
|
|
|
|
ALTER TABLE public.billing_sync_log ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_sync_log" ON public.billing_sync_log;
|
|
CREATE POLICY "admin_manage_billing_sync_log"
|
|
ON public.billing_sync_log FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 7. billing_settings ─────────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.billing_settings (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
billing_mode TEXT NOT NULL DEFAULT 'mooninvoice' CHECK (billing_mode IN ('mooninvoice','native')),
|
|
sync_frequency_minutes INTEGER NOT NULL DEFAULT 15,
|
|
last_sync_rmp TIMESTAMPTZ,
|
|
last_sync_jtr TIMESTAMPTZ,
|
|
default_payment_terms TEXT NOT NULL DEFAULT 'Net 30',
|
|
default_currency TEXT NOT NULL DEFAULT 'USD',
|
|
late_payment_reminder_enabled BOOLEAN NOT NULL DEFAULT false,
|
|
late_payment_reminder_days INTEGER NOT NULL DEFAULT 7,
|
|
auto_send_reminders BOOLEAN NOT NULL DEFAULT false,
|
|
stripe_test_mode BOOLEAN NOT NULL DEFAULT true,
|
|
migration_import_status JSONB,
|
|
migration_last_run TIMESTAMPTZ,
|
|
migration_verified BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
ALTER TABLE public.billing_settings ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_billing_settings" ON public.billing_settings;
|
|
CREATE POLICY "admin_manage_billing_settings"
|
|
ON public.billing_settings FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 8. ad_campaigns table ───────────────────────────────────────────────────
|
|
CREATE TABLE IF NOT EXISTS public.ad_campaigns (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name TEXT NOT NULL,
|
|
client_name TEXT,
|
|
campaign_type TEXT NOT NULL DEFAULT 'banner' CHECK (campaign_type IN ('banner','email','sponsored','newsletter')),
|
|
site_id INTEGER REFERENCES public.sites(id) ON DELETE SET NULL,
|
|
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','pending_approval','approved','active','paused','completed','cancelled')),
|
|
start_date DATE,
|
|
end_date DATE,
|
|
budget NUMERIC(12,2),
|
|
rate NUMERIC(12,2),
|
|
notes TEXT,
|
|
billing_invoice_id UUID REFERENCES public.billing_invoices(id) ON DELETE SET NULL,
|
|
billing_company_id UUID REFERENCES public.billing_companies(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_ad_campaigns_status ON public.ad_campaigns(status);
|
|
CREATE INDEX IF NOT EXISTS idx_ad_campaigns_billing_invoice ON public.ad_campaigns(billing_invoice_id);
|
|
CREATE INDEX IF NOT EXISTS idx_ad_campaigns_billing_company ON public.ad_campaigns(billing_company_id);
|
|
CREATE INDEX IF NOT EXISTS idx_ad_campaigns_site_id ON public.ad_campaigns(site_id);
|
|
|
|
ALTER TABLE public.ad_campaigns ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "admin_manage_ad_campaigns" ON public.ad_campaigns;
|
|
CREATE POLICY "admin_manage_ad_campaigns"
|
|
ON public.ad_campaigns FOR ALL TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
)
|
|
WITH CHECK (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('administrator', 'admin')
|
|
)
|
|
);
|
|
|
|
-- ─── 9. Seed default companies and settings ──────────────────────────────────
|
|
DO $$
|
|
DECLARE
|
|
rmp_id UUID := gen_random_uuid();
|
|
jtr_id UUID := gen_random_uuid();
|
|
BEGIN
|
|
INSERT INTO public.billing_companies (id, name, legal_name, llc_entity, address, email, invoice_prefix, default_payment_terms, is_active)
|
|
VALUES
|
|
(rmp_id, 'Relevant Media Properties', 'Relevant Media Properties, LLC', 'RMP',
|
|
'1309 Coffeen Ave, Suite 1200, Sheridan, WY 82801',
|
|
'ryan.salazar@relevantmediaproperties.com', 'RMP-', 'Net 30', true),
|
|
(jtr_id, 'Just Two Roommates', 'Just Two Roommates, LLC', 'JTR',
|
|
'1309 Coffeen Ave, Suite 1200, Sheridan, WY 82801',
|
|
'ryan.salazar@relevantmediaproperties.com', 'JTR-', 'Net 30', true)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
INSERT INTO public.billing_settings (billing_mode, sync_frequency_minutes, default_payment_terms, default_currency)
|
|
VALUES ('mooninvoice', 15, 'Net 30', 'USD')
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE NOTICE 'Seed data insertion failed: %', SQLERRM;
|
|
END $$;
|