[SAAS-022] feat: Add Goals module DDL

- 00-schema.sql: Schema creation with grants
- 01-enums.sql: 8 enums (goal_type, metric_type, period_type, etc.)
- 02-tables.sql: 4 tables (definitions, assignments, progress_log, milestone_notifications)
- 04-rls.sql: 16 RLS policies for tenant isolation
- 05-indexes.sql: Performance indexes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-25 06:29:04 -06:00
parent a3f354528a
commit 6e5244b612
5 changed files with 420 additions and 0 deletions

View File

@ -0,0 +1,14 @@
-- ============================================
-- SAAS-022: Goals Schema
-- Sistema de metas y objetivos
-- ============================================
-- Create schema
CREATE SCHEMA IF NOT EXISTS goals;
-- Grant permissions
GRANT USAGE ON SCHEMA goals TO template_saas_user;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA goals TO template_saas_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA goals TO template_saas_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA goals GRANT ALL ON TABLES TO template_saas_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA goals GRANT ALL ON SEQUENCES TO template_saas_user;

View File

@ -0,0 +1,70 @@
-- ============================================
-- SAAS-022: Goals Enums
-- ============================================
-- Tipo de meta
CREATE TYPE goals.goal_type AS ENUM (
'target', -- Alcanzar un objetivo
'limit', -- No exceder un limite
'maintain' -- Mantener un valor
);
-- Tipo de metrica
CREATE TYPE goals.metric_type AS ENUM (
'number', -- Numero entero
'currency', -- Monto monetario
'percentage', -- Porcentaje
'boolean', -- Si/No
'count' -- Conteo
);
-- Tipo de periodo
CREATE TYPE goals.period_type AS ENUM (
'daily',
'weekly',
'monthly',
'quarterly',
'yearly',
'custom'
);
-- Fuente de datos para tracking automatico
CREATE TYPE goals.data_source AS ENUM (
'manual', -- Actualizado manualmente
'sales', -- Desde modulo Sales
'billing', -- Desde modulo Billing
'commissions', -- Desde modulo Commissions
'custom' -- Fuente personalizada
);
-- Estado de la meta
CREATE TYPE goals.goal_status AS ENUM (
'draft', -- Borrador
'active', -- Activa
'paused', -- Pausada
'completed', -- Completada
'cancelled' -- Cancelada
);
-- Tipo de asignado
CREATE TYPE goals.assignee_type AS ENUM (
'user', -- Usuario individual
'team', -- Equipo
'tenant' -- Todo el tenant
);
-- Estado de asignacion
CREATE TYPE goals.assignment_status AS ENUM (
'active', -- En progreso
'achieved', -- Lograda
'failed', -- No alcanzada
'cancelled' -- Cancelada
);
-- Fuente del progreso
CREATE TYPE goals.progress_source AS ENUM (
'manual', -- Entrada manual
'automatic', -- Tracking automatico
'import', -- Importacion
'api' -- Via API externa
);

View File

@ -0,0 +1,155 @@
-- ============================================
-- SAAS-022: Goals Tables
-- ============================================
-- ─────────────────────────────────────────────
-- Definiciones de metas
-- ─────────────────────────────────────────────
CREATE TABLE goals.definitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
-- Info basica
name VARCHAR(200) NOT NULL,
description TEXT,
category VARCHAR(100),
-- Tipo de meta
type goals.goal_type NOT NULL DEFAULT 'target',
metric goals.metric_type NOT NULL DEFAULT 'number',
-- Objetivo
target_value DECIMAL(15,2) NOT NULL,
unit VARCHAR(50), -- 'USD', 'units', '%', etc.
-- Periodo
period goals.period_type NOT NULL DEFAULT 'monthly',
starts_at DATE NOT NULL,
ends_at DATE NOT NULL,
-- Fuente de datos (para tracking automatico)
source goals.data_source NOT NULL DEFAULT 'manual',
source_config JSONB DEFAULT '{}',
-- Ejemplo:
-- {
-- "module": "sales",
-- "entity": "opportunities",
-- "filter": { "status": "won" },
-- "aggregation": "sum",
-- "field": "amount"
-- }
-- Hitos para notificaciones
milestones JSONB DEFAULT '[]',
-- Ejemplo: [{ "percentage": 25, "notify": true }, { "percentage": 50, "notify": true }]
-- Estado
status goals.goal_status NOT NULL DEFAULT 'draft',
-- Metadata
tags JSONB DEFAULT '[]',
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID REFERENCES users.users(id),
-- Constraints
CONSTRAINT valid_date_range CHECK (ends_at >= starts_at),
CONSTRAINT positive_target CHECK (target_value > 0)
);
-- ─────────────────────────────────────────────
-- Asignaciones de metas
-- ─────────────────────────────────────────────
CREATE TABLE goals.assignments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
definition_id UUID NOT NULL REFERENCES goals.definitions(id) ON DELETE CASCADE,
-- Asignado a
assignee_type goals.assignee_type NOT NULL DEFAULT 'user',
user_id UUID REFERENCES users.users(id) ON DELETE CASCADE,
team_id UUID, -- Para uso futuro con modulo de equipos
-- Objetivo personalizado (override del definition)
custom_target DECIMAL(15,2),
-- Progreso
current_value DECIMAL(15,2) NOT NULL DEFAULT 0,
progress_percentage DECIMAL(5,2) NOT NULL DEFAULT 0,
last_updated_at TIMESTAMPTZ,
-- Estado
status goals.assignment_status NOT NULL DEFAULT 'active',
achieved_at TIMESTAMPTZ,
-- Metadata
notes TEXT,
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Constraints
CONSTRAINT valid_progress CHECK (progress_percentage >= 0 AND progress_percentage <= 100),
CONSTRAINT valid_assignee CHECK (
(assignee_type = 'user' AND user_id IS NOT NULL) OR
(assignee_type = 'team' AND team_id IS NOT NULL) OR
(assignee_type = 'tenant')
)
);
-- ─────────────────────────────────────────────
-- Historial de progreso
-- ─────────────────────────────────────────────
CREATE TABLE goals.progress_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
assignment_id UUID NOT NULL REFERENCES goals.assignments(id) ON DELETE CASCADE,
-- Valores
previous_value DECIMAL(15,2),
new_value DECIMAL(15,2) NOT NULL,
change_amount DECIMAL(15,2),
-- Fuente del cambio
source goals.progress_source NOT NULL DEFAULT 'manual',
source_reference VARCHAR(200), -- ID de la transaccion origen
notes TEXT,
-- Timestamps
logged_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
logged_by UUID REFERENCES users.users(id)
);
-- ─────────────────────────────────────────────
-- Notificaciones de hitos alcanzados
-- ─────────────────────────────────────────────
CREATE TABLE goals.milestone_notifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
assignment_id UUID NOT NULL REFERENCES goals.assignments(id) ON DELETE CASCADE,
milestone_percentage INTEGER NOT NULL,
achieved_value DECIMAL(15,2) NOT NULL,
notified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Evitar duplicados
CONSTRAINT unique_milestone_notification UNIQUE (assignment_id, milestone_percentage)
);
-- ─────────────────────────────────────────────
-- Triggers para updated_at
-- ─────────────────────────────────────────────
CREATE TRIGGER update_definitions_updated_at
BEFORE UPDATE ON goals.definitions
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_assignments_updated_at
BEFORE UPDATE ON goals.assignments
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();

View File

@ -0,0 +1,72 @@
-- ============================================
-- SAAS-022: Goals RLS Policies
-- Tenant isolation for all tables
-- ============================================
-- ─────────────────────────────────────────────
-- Definitions RLS
-- ─────────────────────────────────────────────
ALTER TABLE goals.definitions ENABLE ROW LEVEL SECURITY;
CREATE POLICY definitions_tenant_isolation_select ON goals.definitions
FOR SELECT USING (tenant_id = auth.get_current_tenant());
CREATE POLICY definitions_tenant_isolation_insert ON goals.definitions
FOR INSERT WITH CHECK (tenant_id = auth.get_current_tenant());
CREATE POLICY definitions_tenant_isolation_update ON goals.definitions
FOR UPDATE USING (tenant_id = auth.get_current_tenant());
CREATE POLICY definitions_tenant_isolation_delete ON goals.definitions
FOR DELETE USING (tenant_id = auth.get_current_tenant());
-- ─────────────────────────────────────────────
-- Assignments RLS
-- ─────────────────────────────────────────────
ALTER TABLE goals.assignments ENABLE ROW LEVEL SECURITY;
CREATE POLICY assignments_tenant_isolation_select ON goals.assignments
FOR SELECT USING (tenant_id = auth.get_current_tenant());
CREATE POLICY assignments_tenant_isolation_insert ON goals.assignments
FOR INSERT WITH CHECK (tenant_id = auth.get_current_tenant());
CREATE POLICY assignments_tenant_isolation_update ON goals.assignments
FOR UPDATE USING (tenant_id = auth.get_current_tenant());
CREATE POLICY assignments_tenant_isolation_delete ON goals.assignments
FOR DELETE USING (tenant_id = auth.get_current_tenant());
-- ─────────────────────────────────────────────
-- Progress Log RLS
-- ─────────────────────────────────────────────
ALTER TABLE goals.progress_log ENABLE ROW LEVEL SECURITY;
CREATE POLICY progress_log_tenant_isolation_select ON goals.progress_log
FOR SELECT USING (tenant_id = auth.get_current_tenant());
CREATE POLICY progress_log_tenant_isolation_insert ON goals.progress_log
FOR INSERT WITH CHECK (tenant_id = auth.get_current_tenant());
CREATE POLICY progress_log_tenant_isolation_update ON goals.progress_log
FOR UPDATE USING (tenant_id = auth.get_current_tenant());
CREATE POLICY progress_log_tenant_isolation_delete ON goals.progress_log
FOR DELETE USING (tenant_id = auth.get_current_tenant());
-- ─────────────────────────────────────────────
-- Milestone Notifications RLS
-- ─────────────────────────────────────────────
ALTER TABLE goals.milestone_notifications ENABLE ROW LEVEL SECURITY;
CREATE POLICY milestone_notifications_tenant_isolation_select ON goals.milestone_notifications
FOR SELECT USING (tenant_id = auth.get_current_tenant());
CREATE POLICY milestone_notifications_tenant_isolation_insert ON goals.milestone_notifications
FOR INSERT WITH CHECK (tenant_id = auth.get_current_tenant());
CREATE POLICY milestone_notifications_tenant_isolation_update ON goals.milestone_notifications
FOR UPDATE USING (tenant_id = auth.get_current_tenant());
CREATE POLICY milestone_notifications_tenant_isolation_delete ON goals.milestone_notifications
FOR DELETE USING (tenant_id = auth.get_current_tenant());

View File

@ -0,0 +1,109 @@
-- ============================================
-- SAAS-022: Goals Indexes
-- Performance optimization
-- ============================================
-- ─────────────────────────────────────────────
-- Definitions Indexes
-- ─────────────────────────────────────────────
-- Tenant + status (most common query)
CREATE INDEX idx_definitions_tenant_status
ON goals.definitions (tenant_id, status);
-- Period queries
CREATE INDEX idx_definitions_tenant_period
ON goals.definitions (tenant_id, period, starts_at, ends_at);
-- Date range queries
CREATE INDEX idx_definitions_dates
ON goals.definitions (starts_at, ends_at);
-- Category filtering
CREATE INDEX idx_definitions_category
ON goals.definitions (tenant_id, category)
WHERE category IS NOT NULL;
-- Active goals (partial index)
CREATE INDEX idx_definitions_active
ON goals.definitions (tenant_id, starts_at, ends_at)
WHERE status = 'active';
-- Created by user
CREATE INDEX idx_definitions_created_by
ON goals.definitions (created_by)
WHERE created_by IS NOT NULL;
-- Tags (GIN for JSONB array)
CREATE INDEX idx_definitions_tags
ON goals.definitions USING GIN (tags);
-- ─────────────────────────────────────────────
-- Assignments Indexes
-- ─────────────────────────────────────────────
-- Tenant + status (most common query)
CREATE INDEX idx_assignments_tenant_status
ON goals.assignments (tenant_id, status);
-- Definition lookup
CREATE INDEX idx_assignments_definition
ON goals.assignments (definition_id);
-- User assignments (most common filter)
CREATE INDEX idx_assignments_user
ON goals.assignments (user_id, status)
WHERE user_id IS NOT NULL;
-- Team assignments
CREATE INDEX idx_assignments_team
ON goals.assignments (team_id, status)
WHERE team_id IS NOT NULL;
-- Active assignments (partial index)
CREATE INDEX idx_assignments_active
ON goals.assignments (tenant_id, user_id)
WHERE status = 'active';
-- Progress percentage (for reports)
CREATE INDEX idx_assignments_progress
ON goals.assignments (tenant_id, progress_percentage);
-- Achieved goals
CREATE INDEX idx_assignments_achieved
ON goals.assignments (achieved_at)
WHERE status = 'achieved';
-- ─────────────────────────────────────────────
-- Progress Log Indexes
-- ─────────────────────────────────────────────
-- Assignment history
CREATE INDEX idx_progress_log_assignment
ON goals.progress_log (assignment_id, logged_at DESC);
-- Tenant + date range
CREATE INDEX idx_progress_log_tenant_date
ON goals.progress_log (tenant_id, logged_at);
-- Source reference (for deduplication)
CREATE INDEX idx_progress_log_source_ref
ON goals.progress_log (source_reference)
WHERE source_reference IS NOT NULL;
-- By user who logged
CREATE INDEX idx_progress_log_logged_by
ON goals.progress_log (logged_by)
WHERE logged_by IS NOT NULL;
-- ─────────────────────────────────────────────
-- Milestone Notifications Indexes
-- ─────────────────────────────────────────────
-- Assignment milestones
CREATE INDEX idx_milestone_notifications_assignment
ON goals.milestone_notifications (assignment_id);
-- Tenant notifications
CREATE INDEX idx_milestone_notifications_tenant
ON goals.milestone_notifications (tenant_id, notified_at);