agendapro/database/init.sql

414 lines
18 KiB
SQL

-- =============================================
-- AgendaPRO SaaS - Database Schema
-- PostgreSQL 16+
-- =============================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- =============================================
-- 1. PLANOS DE ASSINATURA
-- =============================================
CREATE TABLE IF NOT EXISTS plans (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) UNIQUE NOT NULL,
description TEXT,
price_monthly NUMERIC(10,2) NOT NULL DEFAULT 0,
price_yearly NUMERIC(10,2) NOT NULL DEFAULT 0,
max_professionals INT NOT NULL DEFAULT 1,
max_services INT NOT NULL DEFAULT 5,
max_appointments_month INT DEFAULT NULL, -- NULL = unlimited
features JSONB DEFAULT '[]',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 2. TENANTS (ESTABELECIMENTOS)
-- =============================================
CREATE TABLE IF NOT EXISTS tenants (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
plan_id UUID REFERENCES plans(id),
name VARCHAR(200) NOT NULL,
slug VARCHAR(200) UNIQUE NOT NULL,
business_type VARCHAR(50) NOT NULL DEFAULT 'barbearia', -- barbearia, manicure, massagem, estetica, outro
logo_url TEXT,
cover_url TEXT,
phone VARCHAR(20),
email VARCHAR(200),
address TEXT,
city VARCHAR(100),
state VARCHAR(2),
zip_code VARCHAR(10),
description TEXT,
settings JSONB DEFAULT '{
"timezone": "America/Sao_Paulo",
"currency": "BRL",
"booking_advance_min": 60,
"booking_advance_max": 43200,
"cancellation_limit_min": 120,
"allow_online_booking": true,
"require_confirmation": false,
"send_reminders": true,
"reminder_minutes_before": 60,
"theme_color": "#6C63FF",
"working_days": [1,2,3,4,5,6]
}',
subscription_status VARCHAR(20) DEFAULT 'trial', -- trial, active, past_due, cancelled, suspended
trial_ends_at TIMESTAMPTZ,
subscription_ends_at TIMESTAMPTZ,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 3. USUÁRIOS (OWNERS, ADMINS, PROFISSIONAIS)
-- =============================================
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
email VARCHAR(200) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
phone VARCHAR(20),
avatar_url TEXT,
role VARCHAR(20) NOT NULL DEFAULT 'professional', -- owner, admin, professional
is_active BOOLEAN DEFAULT true,
last_login_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(email, tenant_id)
);
-- =============================================
-- 4. CATEGORIAS DE SERVIÇO
-- =============================================
CREATE TABLE IF NOT EXISTS service_categories (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
icon VARCHAR(50),
sort_order INT DEFAULT 0,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 5. SERVIÇOS
-- =============================================
CREATE TABLE IF NOT EXISTS services (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
category_id UUID REFERENCES service_categories(id) ON DELETE SET NULL,
name VARCHAR(200) NOT NULL,
description TEXT,
duration_minutes INT NOT NULL DEFAULT 30,
price NUMERIC(10,2) NOT NULL DEFAULT 0,
price_type VARCHAR(20) DEFAULT 'fixed', -- fixed, starting_at, per_hour
image_url TEXT,
is_active BOOLEAN DEFAULT true,
sort_order INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 6. PROFISSIONAIS <-> SERVIÇOS
-- =============================================
CREATE TABLE IF NOT EXISTS professional_services (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
professional_id UUID REFERENCES users(id) ON DELETE CASCADE,
service_id UUID REFERENCES services(id) ON DELETE CASCADE,
custom_price NUMERIC(10,2),
custom_duration INT,
commission_percent NUMERIC(5,2) DEFAULT 0,
UNIQUE(professional_id, service_id)
);
-- =============================================
-- 7. HORÁRIOS DE TRABALHO
-- =============================================
CREATE TABLE IF NOT EXISTS working_hours (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
professional_id UUID REFERENCES users(id) ON DELETE CASCADE,
day_of_week INT NOT NULL CHECK (day_of_week BETWEEN 0 AND 6), -- 0=domingo
start_time TIME NOT NULL,
end_time TIME NOT NULL,
break_start TIME,
break_end TIME,
is_active BOOLEAN DEFAULT true,
UNIQUE(professional_id, day_of_week)
);
-- =============================================
-- 8. BLOQUEIOS / FOLGAS
-- =============================================
CREATE TABLE IF NOT EXISTS schedule_blocks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
professional_id UUID REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(200),
start_at TIMESTAMPTZ NOT NULL,
end_at TIMESTAMPTZ NOT NULL,
is_all_day BOOLEAN DEFAULT false,
recurrence VARCHAR(20) DEFAULT 'none', -- none, daily, weekly, monthly
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 9. CLIENTES
-- =============================================
CREATE TABLE IF NOT EXISTS clients (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
email VARCHAR(200),
phone VARCHAR(20),
cpf VARCHAR(14),
birth_date DATE,
gender VARCHAR(20),
notes TEXT,
avatar_url TEXT,
total_visits INT DEFAULT 0,
total_spent NUMERIC(10,2) DEFAULT 0,
last_visit_at TIMESTAMPTZ,
tags JSONB DEFAULT '[]',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 10. AGENDAMENTOS
-- =============================================
CREATE TABLE IF NOT EXISTS appointments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
client_id UUID REFERENCES clients(id) ON DELETE SET NULL,
professional_id UUID REFERENCES users(id) ON DELETE SET NULL,
service_id UUID REFERENCES services(id) ON DELETE SET NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending, confirmed, in_progress, completed, cancelled, no_show
date DATE NOT NULL,
start_time TIME NOT NULL,
end_time TIME NOT NULL,
price NUMERIC(10,2) NOT NULL DEFAULT 0,
discount NUMERIC(10,2) DEFAULT 0,
notes TEXT,
client_name VARCHAR(200), -- for walk-ins
client_phone VARCHAR(20),
source VARCHAR(20) DEFAULT 'manual', -- manual, online, whatsapp
cancelled_at TIMESTAMPTZ,
cancellation_reason TEXT,
completed_at TIMESTAMPTZ,
reminder_sent BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 11. PAGAMENTOS
-- =============================================
CREATE TABLE IF NOT EXISTS payments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
appointment_id UUID REFERENCES appointments(id) ON DELETE SET NULL,
client_id UUID REFERENCES clients(id) ON DELETE SET NULL,
amount NUMERIC(10,2) NOT NULL,
method VARCHAR(30) NOT NULL DEFAULT 'cash', -- cash, credit, debit, pix, transfer
status VARCHAR(20) DEFAULT 'completed', -- pending, completed, refunded
notes TEXT,
paid_at TIMESTAMPTZ DEFAULT NOW(),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 12. ASSINATURAS SaaS
-- =============================================
CREATE TABLE IF NOT EXISTS subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
plan_id UUID REFERENCES plans(id),
status VARCHAR(20) DEFAULT 'active', -- active, past_due, cancelled, suspended
billing_cycle VARCHAR(10) DEFAULT 'monthly', -- monthly, yearly
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
payment_method VARCHAR(30),
payment_gateway VARCHAR(30), -- stripe, asaas, manual
gateway_subscription_id VARCHAR(200),
amount NUMERIC(10,2),
last_payment_at TIMESTAMPTZ,
next_payment_at TIMESTAMPTZ,
cancelled_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 13. HISTÓRICO DE PAGAMENTOS SaaS
-- =============================================
CREATE TABLE IF NOT EXISTS subscription_payments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
subscription_id UUID REFERENCES subscriptions(id) ON DELETE CASCADE,
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
amount NUMERIC(10,2) NOT NULL,
status VARCHAR(20) DEFAULT 'paid', -- paid, failed, refunded
gateway_payment_id VARCHAR(200),
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- 14. NOTIFICAÇÕES
-- =============================================
CREATE TABLE IF NOT EXISTS notifications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id UUID REFERENCES tenants(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL,
title VARCHAR(200) NOT NULL,
message TEXT,
data JSONB DEFAULT '{}',
is_read BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- =============================================
-- INDEXES
-- =============================================
CREATE INDEX IF NOT EXISTS idx_appointments_tenant_date ON appointments(tenant_id, date);
CREATE INDEX IF NOT EXISTS idx_appointments_professional ON appointments(professional_id, date);
CREATE INDEX IF NOT EXISTS idx_appointments_client ON appointments(client_id);
CREATE INDEX IF NOT EXISTS idx_appointments_status ON appointments(status);
CREATE INDEX IF NOT EXISTS idx_clients_tenant ON clients(tenant_id);
CREATE INDEX IF NOT EXISTS idx_clients_phone ON clients(phone);
CREATE INDEX IF NOT EXISTS idx_services_tenant ON services(tenant_id);
CREATE INDEX IF NOT EXISTS idx_payments_tenant ON payments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_notifications_user ON notifications(user_id, is_read);
CREATE INDEX IF NOT EXISTS idx_users_tenant ON users(tenant_id);
CREATE INDEX IF NOT EXISTS idx_tenants_slug ON tenants(slug);
-- =============================================
-- SEED: DEFAULT PLANS
-- =============================================
INSERT INTO plans (name, slug, description, price_monthly, price_yearly, max_professionals, max_services, max_appointments_month, features) VALUES
('Starter', 'starter', 'Ideal para profissionais autônomos', 49.90, 479.00, 1, 10, 200, '["Agendamento online", "Página de reservas", "Lembretes por e-mail", "Relatórios básicos"]'),
('Professional', 'professional', 'Para pequenos estabelecimentos', 99.90, 959.00, 5, 30, NULL, '["Tudo do Starter", "Múltiplos profissionais", "Comissionamento", "Relatórios avançados", "Personalização da marca"]'),
('Business', 'business', 'Para estabelecimentos em crescimento', 199.90, 1919.00, 15, 100, NULL, '["Tudo do Professional", "API de integração", "Suporte prioritário", "Multi-unidades", "Automações avançadas"]'),
('Enterprise', 'enterprise', 'Solução completa e personalizada', 399.90, 3839.00, 999, 999, NULL, '["Tudo do Business", "Gerente de conta dedicado", "SLA garantido", "Integrações customizadas", "White-label"]')
ON CONFLICT (slug) DO NOTHING;
-- =============================================
-- SEED: DEMO TENANT
-- =============================================
DO $$
DECLARE
v_plan_id UUID;
v_tenant_id UUID;
v_owner_id UUID;
v_cat_barba_id UUID;
v_cat_cabelo_id UUID;
v_cat_combo_id UUID;
v_svc1_id UUID;
v_svc2_id UUID;
v_svc3_id UUID;
v_svc4_id UUID;
v_svc5_id UUID;
v_prof1_id UUID;
v_prof2_id UUID;
BEGIN
SELECT id INTO v_plan_id FROM plans WHERE slug = 'professional';
INSERT INTO tenants (id, plan_id, name, slug, business_type, phone, email, description, subscription_status, trial_ends_at)
VALUES (
uuid_generate_v4(), v_plan_id, 'Barbearia Premium', 'barbearia-premium', 'barbearia',
'(11) 99999-1234', 'contato@barbeariapremium.com.br',
'A melhor barbearia da cidade. Cortes modernos, barbas impecáveis e ambiente premium.',
'active', NOW() + INTERVAL '30 days'
)
ON CONFLICT (slug) DO NOTHING
RETURNING id INTO v_tenant_id;
IF v_tenant_id IS NOT NULL THEN
-- Owner
INSERT INTO users (id, tenant_id, name, email, password_hash, role)
VALUES (uuid_generate_v4(), v_tenant_id, 'Admin Demo', 'admin@demo.com', crypt('admin123', gen_salt('bf')), 'owner')
RETURNING id INTO v_owner_id;
-- Professionals
INSERT INTO users (id, tenant_id, name, email, password_hash, role, phone)
VALUES (uuid_generate_v4(), v_tenant_id, 'Carlos Silva', 'carlos@demo.com', crypt('prof123', gen_salt('bf')), 'professional', '(11) 98888-1111')
RETURNING id INTO v_prof1_id;
INSERT INTO users (id, tenant_id, name, email, password_hash, role, phone)
VALUES (uuid_generate_v4(), v_tenant_id, 'Rafael Santos', 'rafael@demo.com', crypt('prof123', gen_salt('bf')), 'professional', '(11) 98888-2222')
RETURNING id INTO v_prof2_id;
-- Categories
INSERT INTO service_categories (id, tenant_id, name, icon, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, 'Cabelo', 'scissors', 1) RETURNING id INTO v_cat_cabelo_id;
INSERT INTO service_categories (id, tenant_id, name, icon, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, 'Barba', 'sparkles', 2) RETURNING id INTO v_cat_barba_id;
INSERT INTO service_categories (id, tenant_id, name, icon, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, 'Combos', 'star', 3) RETURNING id INTO v_cat_combo_id;
-- Services
INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, v_cat_cabelo_id, 'Corte Masculino', 'Corte moderno com lavagem e finalização', 40, 55.00, 1) RETURNING id INTO v_svc1_id;
INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, v_cat_cabelo_id, 'Corte Infantil', 'Corte para crianças até 12 anos', 30, 40.00, 2) RETURNING id INTO v_svc2_id;
INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, v_cat_barba_id, 'Barba Completa', 'Aparar, modelar e hidratação', 30, 40.00, 3) RETURNING id INTO v_svc3_id;
INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, v_cat_barba_id, 'Design de Sobrancelha', 'Alinhamento com navalha', 15, 25.00, 4) RETURNING id INTO v_svc4_id;
INSERT INTO services (id, tenant_id, category_id, name, description, duration_minutes, price, sort_order) VALUES
(uuid_generate_v4(), v_tenant_id, v_cat_combo_id, 'Combo Corte + Barba', 'Corte masculino + barba completa', 60, 85.00, 5) RETURNING id INTO v_svc5_id;
-- Professional <-> Services
INSERT INTO professional_services (professional_id, service_id, commission_percent) VALUES
(v_prof1_id, v_svc1_id, 40), (v_prof1_id, v_svc2_id, 40), (v_prof1_id, v_svc3_id, 40),
(v_prof1_id, v_svc4_id, 40), (v_prof1_id, v_svc5_id, 40),
(v_prof2_id, v_svc1_id, 35), (v_prof2_id, v_svc3_id, 35), (v_prof2_id, v_svc5_id, 35);
-- Working Hours (Mon-Sat for both professionals)
FOR i IN 1..6 LOOP
INSERT INTO working_hours (tenant_id, professional_id, day_of_week, start_time, end_time, break_start, break_end) VALUES
(v_tenant_id, v_prof1_id, i, '09:00', '19:00', '12:00', '13:00'),
(v_tenant_id, v_prof2_id, i, '10:00', '20:00', '13:00', '14:00');
END LOOP;
-- Sample Clients
INSERT INTO clients (tenant_id, name, email, phone, total_visits) VALUES
(v_tenant_id, 'João Mendes', 'joao@email.com', '(11) 97777-1111', 12),
(v_tenant_id, 'Pedro Oliveira', 'pedro@email.com', '(11) 97777-2222', 8),
(v_tenant_id, 'Lucas Ferreira', 'lucas@email.com', '(11) 97777-3333', 5),
(v_tenant_id, 'Mateus Costa', 'mateus@email.com', '(11) 97777-4444', 3),
(v_tenant_id, 'Gabriel Souza', 'gabriel@email.com', '(11) 97777-5555', 15);
-- Sample appointments for today
INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source)
SELECT v_tenant_id, c.id, v_prof1_id, v_svc1_id, 'confirmed', CURRENT_DATE, '10:00', '10:40', 55.00, 'online'
FROM clients c WHERE c.name = 'João Mendes' AND c.tenant_id = v_tenant_id LIMIT 1;
INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source)
SELECT v_tenant_id, c.id, v_prof1_id, v_svc5_id, 'confirmed', CURRENT_DATE, '11:00', '12:00', 85.00, 'manual'
FROM clients c WHERE c.name = 'Pedro Oliveira' AND c.tenant_id = v_tenant_id LIMIT 1;
INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source)
SELECT v_tenant_id, c.id, v_prof2_id, v_svc3_id, 'pending', CURRENT_DATE, '14:00', '14:30', 40.00, 'online'
FROM clients c WHERE c.name = 'Lucas Ferreira' AND c.tenant_id = v_tenant_id LIMIT 1;
INSERT INTO appointments (tenant_id, client_id, professional_id, service_id, status, date, start_time, end_time, price, source)
SELECT v_tenant_id, c.id, v_prof2_id, v_svc1_id, 'completed', CURRENT_DATE, '10:00', '10:40', 55.00, 'manual'
FROM clients c WHERE c.name = 'Gabriel Souza' AND c.tenant_id = v_tenant_id LIMIT 1;
-- Subscription record
INSERT INTO subscriptions (tenant_id, plan_id, status, billing_cycle, current_period_start, current_period_end, amount, next_payment_at)
VALUES (v_tenant_id, v_plan_id, 'active', 'monthly', NOW(), NOW() + INTERVAL '30 days', 99.90, NOW() + INTERVAL '30 days');
END IF;
END $$;