first commit

This commit is contained in:
2026-08-14 17:35:24 -03:00
commit 04472fcd2b
118 changed files with 19173 additions and 0 deletions
@@ -0,0 +1,451 @@
-- CreateEnum
CREATE TYPE "tenant_status" AS ENUM ('trial', 'ativo', 'inadimplente', 'suspenso', 'cancelado');
-- CreateEnum
CREATE TYPE "cobranca_status" AS ENUM ('pendente', 'pago', 'atrasado', 'cancelado');
-- CreateEnum
CREATE TYPE "user_role" AS ENUM ('tenant_admin', 'vendedor', 'financeiro', 'visualizador');
-- CreateEnum
CREATE TYPE "item_tipo" AS ENUM ('produto', 'servico');
-- CreateEnum
CREATE TYPE "venda_status" AS ENUM ('orcamento', 'confirmada', 'cancelada', 'concluida');
-- CreateEnum
CREATE TYPE "parcela_status" AS ENUM ('pendente', 'pago', 'atrasado', 'cancelado');
-- CreateTable
CREATE TABLE "master_admins" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"nome" VARCHAR(150) NOT NULL,
"email" VARCHAR(150) NOT NULL,
"senha_hash" TEXT NOT NULL,
"ativo" BOOLEAN NOT NULL DEFAULT true,
"mfa_secret" TEXT,
"ultimo_login" TIMESTAMPTZ(6),
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"atualizado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "master_admins_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "evolution_api_config" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"nome" VARCHAR(100) NOT NULL,
"base_url" VARCHAR(255) NOT NULL,
"api_key_cripto" TEXT NOT NULL,
"versao" VARCHAR(20) NOT NULL DEFAULT '2.3',
"ativo" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "evolution_api_config_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "master_email_config" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"smtp_host" VARCHAR(150) NOT NULL,
"smtp_porta" INTEGER NOT NULL,
"smtp_usuario" VARCHAR(150) NOT NULL,
"smtp_senha_cripto" TEXT NOT NULL,
"email_remetente" VARCHAR(150) NOT NULL,
"ativo" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "master_email_config_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "planos" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"nome" VARCHAR(100) NOT NULL,
"descricao" TEXT,
"valor_mensal" DECIMAL(10,2) NOT NULL,
"limite_customers" INTEGER,
"limite_usuarios" INTEGER,
"limite_instancias_whatsapp" INTEGER NOT NULL DEFAULT 1,
"ativo" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "planos_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tenants" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"razao_social" VARCHAR(200) NOT NULL,
"nome_fantasia" VARCHAR(200),
"documento" VARCHAR(20),
"email_contato" VARCHAR(150) NOT NULL,
"telefone" VARCHAR(20),
"logo_url" TEXT,
"cor_primaria" VARCHAR(7),
"plano_id" UUID,
"status" "tenant_status" NOT NULL DEFAULT 'trial',
"dia_vencimento_mensalidade" SMALLINT NOT NULL DEFAULT 10,
"trial_expira_em" TIMESTAMPTZ(6),
"criado_por" UUID,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"atualizado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tenants_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tenant_faturas" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"referencia" VARCHAR(7) NOT NULL,
"valor" DECIMAL(10,2) NOT NULL,
"vencimento" DATE NOT NULL,
"pago_em" TIMESTAMPTZ(6),
"status" "cobranca_status" NOT NULL DEFAULT 'pendente',
"alerta_enviado_em" TIMESTAMPTZ(6),
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tenant_faturas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tenant_users" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"nome" VARCHAR(150) NOT NULL,
"email" VARCHAR(150) NOT NULL,
"senha_hash" TEXT NOT NULL,
"role" "user_role" NOT NULL DEFAULT 'vendedor',
"ativo" BOOLEAN NOT NULL DEFAULT true,
"mfa_secret" TEXT,
"ultimo_login" TIMESTAMPTZ(6),
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tenant_users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "auth_refresh_tokens" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"usuario_tipo" VARCHAR(20) NOT NULL,
"usuario_id" UUID NOT NULL,
"token_hash" TEXT NOT NULL,
"expira_em" TIMESTAMPTZ(6) NOT NULL,
"revogado" BOOLEAN NOT NULL DEFAULT false,
"ip_origem" INET,
"user_agent" TEXT,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "auth_refresh_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "password_reset_tokens" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"usuario_tipo" VARCHAR(20) NOT NULL,
"usuario_id" UUID NOT NULL,
"token_hash" TEXT NOT NULL,
"expira_em" TIMESTAMPTZ(6) NOT NULL,
"usado" BOOLEAN NOT NULL DEFAULT false,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "password_reset_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "login_attempts" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"email" VARCHAR(150) NOT NULL,
"usuario_tipo" VARCHAR(20) NOT NULL,
"sucesso" BOOLEAN NOT NULL,
"ip_origem" INET,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "login_attempts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tenant_whatsapp_instances" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"evolution_config_id" UUID,
"instance_name" VARCHAR(100) NOT NULL,
"numero_conectado" VARCHAR(20),
"status" VARCHAR(30) NOT NULL DEFAULT 'desconectado',
"qr_code_gerado_em" TIMESTAMPTZ(6),
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tenant_whatsapp_instances_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tenant_email_config" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"smtp_host" VARCHAR(150),
"smtp_porta" INTEGER,
"smtp_usuario" VARCHAR(150),
"smtp_senha_cripto" TEXT,
"email_remetente" VARCHAR(150),
"usar_config_padrao" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tenant_email_config_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "alert_templates" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID,
"tipo" VARCHAR(50) NOT NULL,
"canal" VARCHAR(20) NOT NULL,
"assunto" VARCHAR(200),
"corpo" TEXT NOT NULL,
"ativo" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "alert_templates_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "alert_logs" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID,
"destinatario_tipo" VARCHAR(20) NOT NULL,
"destinatario_id" UUID NOT NULL,
"canal" VARCHAR(20) NOT NULL,
"template_id" UUID,
"conteudo_enviado" TEXT,
"status" VARCHAR(20) NOT NULL DEFAULT 'enviado',
"erro" TEXT,
"enviado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "alert_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "customers" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"razao_social" VARCHAR(200) NOT NULL,
"nome_fantasia" VARCHAR(200),
"documento" VARCHAR(20),
"email" VARCHAR(150),
"telefone_whatsapp" VARCHAR(20),
"endereco" TEXT,
"cidade" VARCHAR(100),
"uf" CHAR(2),
"observacoes" TEXT,
"ativo" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"atualizado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "customers_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "customer_contacts" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"customer_id" UUID NOT NULL,
"nome" VARCHAR(150) NOT NULL,
"cargo" VARCHAR(100),
"email" VARCHAR(150),
"telefone_whatsapp" VARCHAR(20),
"principal" BOOLEAN NOT NULL DEFAULT false,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "customer_contacts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "products_services" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"tipo" "item_tipo" NOT NULL,
"nome" VARCHAR(200) NOT NULL,
"descricao" TEXT,
"sku" VARCHAR(50),
"valor_venda" DECIMAL(12,2) NOT NULL,
"valor_custo" DECIMAL(12,2),
"unidade" VARCHAR(20) NOT NULL DEFAULT 'un',
"recorrente" BOOLEAN NOT NULL DEFAULT false,
"ativo" BOOLEAN NOT NULL DEFAULT true,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"atualizado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "products_services_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sales" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID NOT NULL,
"customer_id" UUID NOT NULL,
"vendedor_id" UUID,
"numero_venda" SERIAL NOT NULL,
"status" "venda_status" NOT NULL DEFAULT 'confirmada',
"valor_total" DECIMAL(12,2) NOT NULL DEFAULT 0,
"desconto" DECIMAL(12,2) NOT NULL DEFAULT 0,
"observacoes" TEXT,
"data_venda" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"atualizado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "sales_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sale_items" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"sale_id" UUID NOT NULL,
"product_service_id" UUID NOT NULL,
"quantidade" DECIMAL(10,2) NOT NULL DEFAULT 1,
"valor_unitario" DECIMAL(12,2) NOT NULL,
"valor_total" DECIMAL(12,2) NOT NULL,
CONSTRAINT "sale_items_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sale_payments" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"sale_id" UUID NOT NULL,
"numero_parcela" SMALLINT NOT NULL DEFAULT 1,
"valor" DECIMAL(12,2) NOT NULL,
"vencimento" DATE NOT NULL,
"pago_em" TIMESTAMPTZ(6),
"forma_pagamento" VARCHAR(30),
"status" "parcela_status" NOT NULL DEFAULT 'pendente',
"alerta_enviado_em" TIMESTAMPTZ(6),
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "sale_payments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "audit_logs" (
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
"tenant_id" UUID,
"usuario_tipo" VARCHAR(20) NOT NULL,
"usuario_id" UUID,
"acao" VARCHAR(100) NOT NULL,
"entidade" VARCHAR(50),
"entidade_id" UUID,
"detalhes" JSONB,
"ip_origem" INET,
"criado_em" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "master_admins_email_key" ON "master_admins"("email");
-- CreateIndex
CREATE INDEX "idx_tenants_status" ON "tenants"("status");
-- CreateIndex
CREATE UNIQUE INDEX "tenant_faturas_tenant_id_referencia_key" ON "tenant_faturas"("tenant_id", "referencia");
-- CreateIndex
CREATE INDEX "idx_tenant_users_tenant" ON "tenant_users"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "tenant_users_tenant_id_email_key" ON "tenant_users"("tenant_id", "email");
-- CreateIndex
CREATE INDEX "idx_refresh_tokens_usuario" ON "auth_refresh_tokens"("usuario_tipo", "usuario_id");
-- CreateIndex
CREATE INDEX "idx_login_attempts_email_data" ON "login_attempts"("email", "criado_em");
-- CreateIndex
CREATE UNIQUE INDEX "tenant_whatsapp_instances_instance_name_key" ON "tenant_whatsapp_instances"("instance_name");
-- CreateIndex
CREATE UNIQUE INDEX "tenant_email_config_tenant_id_key" ON "tenant_email_config"("tenant_id");
-- CreateIndex
CREATE INDEX "idx_alert_logs_tenant" ON "alert_logs"("tenant_id", "enviado_em");
-- CreateIndex
CREATE INDEX "idx_customers_tenant" ON "customers"("tenant_id");
-- CreateIndex
CREATE INDEX "idx_products_tenant" ON "products_services"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "products_services_tenant_id_sku_key" ON "products_services"("tenant_id", "sku");
-- CreateIndex
CREATE INDEX "idx_sales_tenant_data" ON "sales"("tenant_id", "data_venda");
-- CreateIndex
CREATE INDEX "idx_sales_customer" ON "sales"("customer_id");
-- CreateIndex
CREATE INDEX "idx_sale_items_sale" ON "sale_items"("sale_id");
-- CreateIndex
CREATE INDEX "idx_audit_tenant_data" ON "audit_logs"("tenant_id", "criado_em");
-- AddForeignKey
ALTER TABLE "tenants" ADD CONSTRAINT "tenants_plano_id_fkey" FOREIGN KEY ("plano_id") REFERENCES "planos"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tenants" ADD CONSTRAINT "tenants_criado_por_fkey" FOREIGN KEY ("criado_por") REFERENCES "master_admins"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tenant_faturas" ADD CONSTRAINT "tenant_faturas_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tenant_users" ADD CONSTRAINT "tenant_users_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tenant_whatsapp_instances" ADD CONSTRAINT "tenant_whatsapp_instances_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tenant_whatsapp_instances" ADD CONSTRAINT "tenant_whatsapp_instances_evolution_config_id_fkey" FOREIGN KEY ("evolution_config_id") REFERENCES "evolution_api_config"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "tenant_email_config" ADD CONSTRAINT "tenant_email_config_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "alert_templates" ADD CONSTRAINT "alert_templates_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "alert_logs" ADD CONSTRAINT "alert_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "alert_logs" ADD CONSTRAINT "alert_logs_template_id_fkey" FOREIGN KEY ("template_id") REFERENCES "alert_templates"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "customers" ADD CONSTRAINT "customers_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "customer_contacts" ADD CONSTRAINT "customer_contacts_customer_id_fkey" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "products_services" ADD CONSTRAINT "products_services_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sales" ADD CONSTRAINT "sales_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sales" ADD CONSTRAINT "sales_customer_id_fkey" FOREIGN KEY ("customer_id") REFERENCES "customers"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sales" ADD CONSTRAINT "sales_vendedor_id_fkey" FOREIGN KEY ("vendedor_id") REFERENCES "tenant_users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sale_items" ADD CONSTRAINT "sale_items_sale_id_fkey" FOREIGN KEY ("sale_id") REFERENCES "sales"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sale_items" ADD CONSTRAINT "sale_items_product_service_id_fkey" FOREIGN KEY ("product_service_id") REFERENCES "products_services"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "sale_payments" ADD CONSTRAINT "sale_payments_sale_id_fkey" FOREIGN KEY ("sale_id") REFERENCES "sales"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,119 @@
-- Migration manual: construtos que o Prisma DSL não expressa nativamente.
-- Fonte de verdade: Etapas/seconectai-vendas-schema.sql
-- Contém: extensão pgcrypto, CHECK constraint, índices parciais, views de
-- dashboard e Row Level Security (ENABLE + CREATE POLICY) para isolamento
-- entre tenants.
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================================
-- CHECK constraints
-- ============================================================
ALTER TABLE "tenants"
ADD CONSTRAINT "tenants_dia_vencimento_mensalidade_check"
CHECK ("dia_vencimento_mensalidade" BETWEEN 1 AND 28);
-- ============================================================
-- Índices parciais
-- ============================================================
CREATE INDEX "idx_tenant_faturas_venc"
ON "tenant_faturas"("vencimento")
WHERE "status" IN ('pendente', 'atrasado');
CREATE INDEX "idx_sale_payments_venc"
ON "sale_payments"("vencimento")
WHERE "status" IN ('pendente', 'atrasado');
-- ============================================================
-- Views de dashboard (painel do tenant)
-- ============================================================
CREATE VIEW "vw_resumo_vendas_semanal" AS
SELECT
s.tenant_id,
date_trunc('week', s.data_venda)::date AS semana,
COUNT(*) AS qtd_vendas,
SUM(s.valor_total) AS total_vendido
FROM sales s
WHERE s.status IN ('confirmada', 'concluida')
GROUP BY s.tenant_id, date_trunc('week', s.data_venda);
CREATE VIEW "vw_resumo_vendas_mensal" AS
SELECT
s.tenant_id,
date_trunc('month', s.data_venda)::date AS mes,
COUNT(*) AS qtd_vendas,
SUM(s.valor_total) AS total_vendido
FROM sales s
WHERE s.status IN ('confirmada', 'concluida')
GROUP BY s.tenant_id, date_trunc('month', s.data_venda);
CREATE VIEW "vw_recebimentos" AS
SELECT
sp.id,
s.tenant_id,
s.customer_id,
sp.valor,
sp.vencimento,
sp.status,
sp.pago_em
FROM sale_payments sp
JOIN sales s ON s.id = sp.sale_id;
-- ============================================================
-- Row Level Security — isolamento entre tenants
-- ============================================================
-- A aplicação deve definir, por conexão/transação:
-- SET app.current_tenant_id = '<uuid-do-tenant-logado>';
-- Usuários master (que enxergam todos os tenants) usam uma role de banco
-- separada (ex: app_master) que faz BYPASS RLS, ou uma policy adicional
-- que libera quando app.is_master = 'true'. Implementado na Etapa 2.
ALTER TABLE "customers" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "customer_contacts" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "products_services" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "sales" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "sale_items" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "sale_payments" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "tenant_users" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "tenant_whatsapp_instances" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "tenant_email_config" ENABLE ROW LEVEL SECURITY;
CREATE POLICY "tenant_isolation_customers" ON "customers"
USING ("tenant_id" = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY "tenant_isolation_products" ON "products_services"
USING ("tenant_id" = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY "tenant_isolation_sales" ON "sales"
USING ("tenant_id" = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY "tenant_isolation_tenant_users" ON "tenant_users"
USING ("tenant_id" = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY "tenant_isolation_whatsapp" ON "tenant_whatsapp_instances"
USING ("tenant_id" = current_setting('app.current_tenant_id', true)::uuid);
CREATE POLICY "tenant_isolation_email_config" ON "tenant_email_config"
USING ("tenant_id" = current_setting('app.current_tenant_id', true)::uuid);
-- sale_items e customer_contacts não têm tenant_id direto: isolar via join
CREATE POLICY "tenant_isolation_sale_items" ON "sale_items"
USING ("sale_id" IN (
SELECT id FROM sales
WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid
));
CREATE POLICY "tenant_isolation_sale_payments" ON "sale_payments"
USING ("sale_id" IN (
SELECT id FROM sales
WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid
));
CREATE POLICY "tenant_isolation_customer_contacts" ON "customer_contacts"
USING ("customer_id" IN (
SELECT id FROM customers
WHERE tenant_id = current_setting('app.current_tenant_id', true)::uuid
));
@@ -0,0 +1,53 @@
-- Migration manual: roles de aplicação usadas pela Etapa 2 (Autenticação/RLS).
--
-- Por que duas roles novas em vez de reaproveitar a role do DATABASE_URL
-- (`seconectai`, dono das tabelas): a role criada pelo `POSTGRES_USER` do
-- docker-compose é a role bootstrap do initdb, portanto é SUPERUSER — e
-- superusers do Postgres **sempre** ignoram Row Level Security, mesmo com
-- `ENABLE ROW LEVEL SECURITY`/`FORCE ROW LEVEL SECURITY` nas tabelas. Se a
-- aplicação continuasse consultando o banco com essa role em runtime, o
-- isolamento por tenant seria só decorativo. Por isso:
--
-- * `app_master` — LOGIN + BYPASSRLS, mas SEM ser superuser. Usada via
-- `DATABASE_URL_MASTER` pelo painel master (enxerga todos os tenants),
-- como pedido no prompt da etapa.
-- * `app_tenant` — LOGIN comum, SEM BYPASSRLS e SEM ser superuser. Usada
-- via `DATABASE_URL_TENANT` para toda query autenticada como
-- tenant_user, dentro da transação que executa
-- `SET LOCAL app.current_tenant_id`; é essa role que faz as policies de
-- RLS (`prisma/migrations/20260812161100_rls_views_constraints`) valerem
-- de fato. (Desvio registrado em Etapas/09-controle-de-progresso.md: o
-- prompt original só citava `DATABASE_URL_MASTER`.)
--
-- `DATABASE_URL` (role `seconectai`) continua sendo usada só para
-- migrations e para o `PrismaService` "admin" (health check, bootstrap de
-- login antes de existir um tenant_id de sessão — ver AuthService).
--
-- As senhas abaixo são placeholders de desenvolvimento, no mesmo espírito
-- de `POSTGRES_PASSWORD`/`JWT_SECRET` em `.env.example`: troque-as em
-- produção com `ALTER ROLE ... WITH PASSWORD '...'` e atualize
-- `DATABASE_URL_MASTER`/`DATABASE_URL_TENANT` de acordo.
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'app_master') THEN
CREATE ROLE app_master WITH LOGIN PASSWORD 'app_master_changeme' NOSUPERUSER BYPASSRLS;
END IF;
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'app_tenant') THEN
CREATE ROLE app_tenant WITH LOGIN PASSWORD 'app_tenant_changeme' NOSUPERUSER NOBYPASSRLS;
END IF;
END
$$;
GRANT USAGE ON SCHEMA public TO app_master, app_tenant;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_master, app_tenant;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_master, app_tenant;
-- Cobre tabelas/sequences criadas por migrations futuras (etapas 3+),
-- desde que aplicadas com a mesma role que rodou esta migration.
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_master, app_tenant;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO app_master, app_tenant;
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"