From 2837480e178dcbde2c82a6b5c4849744e03c1b1b Mon Sep 17 00:00:00 2001 From: Adrian Flores Cortes Date: Sun, 25 Jan 2026 06:48:34 -0600 Subject: [PATCH] [SAAS-021] feat: Add MLM module DDL - 6 tables: structures, ranks, nodes, commissions, bonuses, rank_history - 5 enums: structure_type, node_status, commission_type, commission_status, bonus_type - LTREE extension for hierarchical path queries - 24 RLS policies for multi-tenancy - GIST index for LTREE path column Co-Authored-By: Claude Opus 4.5 --- ddl/schemas/mlm/00-schema.sql | 20 +++ ddl/schemas/mlm/01-enums.sql | 55 ++++++++ ddl/schemas/mlm/02-tables.sql | 235 +++++++++++++++++++++++++++++++++ ddl/schemas/mlm/04-rls.sql | 126 ++++++++++++++++++ ddl/schemas/mlm/05-indexes.sql | 87 ++++++++++++ 5 files changed, 523 insertions(+) create mode 100644 ddl/schemas/mlm/00-schema.sql create mode 100644 ddl/schemas/mlm/01-enums.sql create mode 100644 ddl/schemas/mlm/02-tables.sql create mode 100644 ddl/schemas/mlm/04-rls.sql create mode 100644 ddl/schemas/mlm/05-indexes.sql diff --git a/ddl/schemas/mlm/00-schema.sql b/ddl/schemas/mlm/00-schema.sql new file mode 100644 index 0000000..cd10bd7 --- /dev/null +++ b/ddl/schemas/mlm/00-schema.sql @@ -0,0 +1,20 @@ +-- ============================================= +-- Schema: mlm +-- Module: SAAS-021 MLM (Multi-Level Marketing) +-- ============================================= + +-- Crear schema +CREATE SCHEMA IF NOT EXISTS mlm; + +-- Comentario +COMMENT ON SCHEMA mlm IS 'MLM multi-level marketing module - network structures, nodes, ranks, and commissions'; + +-- Grants +GRANT USAGE ON SCHEMA mlm TO template_saas_user; +GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA mlm TO template_saas_user; +GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA mlm TO template_saas_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA mlm GRANT ALL PRIVILEGES ON TABLES TO template_saas_user; +ALTER DEFAULT PRIVILEGES IN SCHEMA mlm GRANT ALL PRIVILEGES ON SEQUENCES TO template_saas_user; + +-- Enable LTREE extension (required for hierarchical path queries) +CREATE EXTENSION IF NOT EXISTS ltree; diff --git a/ddl/schemas/mlm/01-enums.sql b/ddl/schemas/mlm/01-enums.sql new file mode 100644 index 0000000..c69eaf8 --- /dev/null +++ b/ddl/schemas/mlm/01-enums.sql @@ -0,0 +1,55 @@ +-- ============================================= +-- Enums: mlm +-- Module: SAAS-021 MLM (Multi-Level Marketing) +-- ============================================= + +-- Structure type +CREATE TYPE mlm.structure_type AS ENUM ( + 'unilevel', -- Unlimited width, limited depth + 'binary', -- Max 2 children per node + 'matrix', -- Fixed width x depth + 'hybrid' -- Custom configuration +); + +COMMENT ON TYPE mlm.structure_type IS 'Types of MLM network structures'; + +-- Node status +CREATE TYPE mlm.node_status AS ENUM ( + 'pending', -- Awaiting activation + 'active', -- Active in network + 'inactive', -- Temporarily inactive + 'suspended' -- Administratively suspended +); + +COMMENT ON TYPE mlm.node_status IS 'Status of a node in the network'; + +-- Commission type +CREATE TYPE mlm.commission_type AS ENUM ( + 'level', -- Direct level commission (1st gen, 2nd gen, etc.) + 'matching', -- Matching bonus from downline earnings + 'infinity', -- Infinity bonus (unlimited depth after rank) + 'leadership', -- Leadership bonus for qualified ranks + 'pool' -- Pool share bonus +); + +COMMENT ON TYPE mlm.commission_type IS 'Types of MLM commissions'; + +-- Commission status +CREATE TYPE mlm.commission_status AS ENUM ( + 'pending', -- Awaiting approval + 'approved', -- Approved for payment + 'paid', -- Paid out + 'cancelled' -- Cancelled +); + +COMMENT ON TYPE mlm.commission_status IS 'Status of a commission entry'; + +-- Bonus type +CREATE TYPE mlm.bonus_type AS ENUM ( + 'rank_achievement', -- One-time bonus for reaching rank + 'rank_maintenance', -- Monthly bonus for maintaining rank + 'fast_start', -- Fast start bonus for quick enrollments + 'pool_share' -- Share of global pool +); + +COMMENT ON TYPE mlm.bonus_type IS 'Types of bonuses in MLM'; diff --git a/ddl/schemas/mlm/02-tables.sql b/ddl/schemas/mlm/02-tables.sql new file mode 100644 index 0000000..f2ccde3 --- /dev/null +++ b/ddl/schemas/mlm/02-tables.sql @@ -0,0 +1,235 @@ +-- ============================================= +-- Tables: mlm +-- Module: SAAS-021 MLM (Multi-Level Marketing) +-- ============================================= + +-- ───────────────────────────────────────────── +-- structures - MLM network structure configuration +-- ───────────────────────────────────────────── +CREATE TABLE mlm.structures ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, + + name VARCHAR(100) NOT NULL, + description TEXT, + type mlm.structure_type NOT NULL, + + -- Configuration per type (JSONB) + -- Unilevel: { max_width: null, max_depth: 10 } + -- Binary: { spillover: 'left_first' | 'weak_leg' | 'balanced' } + -- Matrix: { width: 3, depth: 7 } + config JSONB NOT NULL DEFAULT '{}', + + -- Commission rates by level (JSONB array) + -- [{ level: 1, rate: 0.10 }, { level: 2, rate: 0.05 }, ...] + level_rates JSONB NOT NULL DEFAULT '[]', + + -- Matching bonus rates (for matching commissions) + matching_rates JSONB DEFAULT '[]', + + is_active BOOLEAN DEFAULT true, + + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + created_by UUID REFERENCES users.users(id) ON DELETE SET NULL, + + CONSTRAINT unique_structure_name_per_tenant UNIQUE (tenant_id, name) +); + +COMMENT ON TABLE mlm.structures IS 'MLM network structure configurations'; +COMMENT ON COLUMN mlm.structures.config IS 'Structure-specific configuration (max_depth, spillover, etc.)'; +COMMENT ON COLUMN mlm.structures.level_rates IS 'Commission percentages by level depth'; + +-- Trigger for updated_at +CREATE TRIGGER set_structures_updated_at + BEFORE UPDATE ON mlm.structures + FOR EACH ROW + EXECUTE FUNCTION public.set_updated_at(); + +-- ───────────────────────────────────────────── +-- ranks - MLM qualification ranks +-- ───────────────────────────────────────────── +CREATE TABLE mlm.ranks ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, + structure_id UUID NOT NULL REFERENCES mlm.structures(id) ON DELETE CASCADE, + + name VARCHAR(100) NOT NULL, + level INTEGER NOT NULL, -- 1=Entry, 2=Bronze, 3=Silver, etc. + badge_url VARCHAR(500), + color VARCHAR(7), -- Hex color for UI + + -- Requirements to achieve rank (JSONB) + -- { + -- personal_volume: 1000, + -- group_volume: 10000, + -- direct_referrals: 3, + -- active_legs: 2, + -- rank_in_legs: { rank_level: 2, count: 1 } + -- } + requirements JSONB NOT NULL DEFAULT '{}', + + -- Benefits for this rank + bonus_rate DECIMAL(10,4), -- Additional bonus percentage + benefits JSONB DEFAULT '{}', -- Other benefits (discounts, access, etc.) + + is_active BOOLEAN DEFAULT true, + + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + CONSTRAINT unique_rank_level_per_structure UNIQUE (structure_id, level) +); + +COMMENT ON TABLE mlm.ranks IS 'MLM qualification ranks with requirements and benefits'; +COMMENT ON COLUMN mlm.ranks.requirements IS 'Conditions to achieve this rank'; + +-- Trigger for updated_at +CREATE TRIGGER set_ranks_updated_at + BEFORE UPDATE ON mlm.ranks + FOR EACH ROW + EXECUTE FUNCTION public.set_updated_at(); + +-- ───────────────────────────────────────────── +-- nodes - MLM network nodes (distributors) +-- ───────────────────────────────────────────── +CREATE TABLE mlm.nodes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, + structure_id UUID NOT NULL REFERENCES mlm.structures(id) ON DELETE CASCADE, + + user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE, + + -- Hierarchy + parent_id UUID REFERENCES mlm.nodes(id) ON DELETE SET NULL, + sponsor_id UUID REFERENCES mlm.nodes(id) ON DELETE SET NULL, -- Who referred them + position INTEGER, -- For binary: 1=left, 2=right. For matrix: 1,2,3...width + + -- Materialized path for efficient queries (LTREE) + path LTREE, + depth INTEGER DEFAULT 0, + + -- Current and highest rank + rank_id UUID REFERENCES mlm.ranks(id) ON DELETE SET NULL, + highest_rank_id UUID REFERENCES mlm.ranks(id) ON DELETE SET NULL, + + -- Performance metrics + personal_volume DECIMAL(15,2) DEFAULT 0, + group_volume DECIMAL(15,2) DEFAULT 0, + direct_referrals INTEGER DEFAULT 0, + total_downline INTEGER DEFAULT 0, + + -- Lifetime earnings + total_earnings DECIMAL(15,2) DEFAULT 0, + + -- Status + status mlm.node_status NOT NULL DEFAULT 'active', + joined_at TIMESTAMPTZ DEFAULT NOW(), + + -- Invitation + invite_code VARCHAR(20) UNIQUE, + + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + CONSTRAINT unique_user_per_structure UNIQUE (structure_id, user_id) +); + +COMMENT ON TABLE mlm.nodes IS 'MLM network nodes representing distributors in the hierarchy'; +COMMENT ON COLUMN mlm.nodes.path IS 'LTREE path for efficient ancestor/descendant queries'; +COMMENT ON COLUMN mlm.nodes.position IS 'Position under parent (left/right for binary, slot for matrix)'; + +-- Trigger for updated_at +CREATE TRIGGER set_nodes_updated_at + BEFORE UPDATE ON mlm.nodes + FOR EACH ROW + EXECUTE FUNCTION public.set_updated_at(); + +-- ───────────────────────────────────────────── +-- commissions - MLM commission entries +-- ───────────────────────────────────────────── +CREATE TABLE mlm.commissions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, + + node_id UUID NOT NULL REFERENCES mlm.nodes(id) ON DELETE CASCADE, -- Who receives + source_node_id UUID NOT NULL REFERENCES mlm.nodes(id) ON DELETE CASCADE, -- Who generated + + -- Commission type + type mlm.commission_type NOT NULL, + + -- Level difference (1 = direct, 2 = second level, etc.) + level INTEGER NOT NULL, + + -- Amounts + source_amount DECIMAL(15,2) NOT NULL, -- Original sale/volume amount + rate_applied DECIMAL(10,4) NOT NULL, -- Rate used for calculation + commission_amount DECIMAL(15,2) NOT NULL, -- Final commission + currency VARCHAR(3) DEFAULT 'USD', + + -- Reference to period and source + period_id UUID REFERENCES commissions.periods(id) ON DELETE SET NULL, + source_reference VARCHAR(200), -- Reference to sale/transaction + + -- Status + status mlm.commission_status NOT NULL DEFAULT 'pending', + paid_at TIMESTAMPTZ, + + notes TEXT, + + created_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE mlm.commissions IS 'MLM commission entries from downline activity'; +COMMENT ON COLUMN mlm.commissions.level IS 'Level depth from source to beneficiary'; + +-- ───────────────────────────────────────────── +-- bonuses - MLM bonus entries +-- ───────────────────────────────────────────── +CREATE TABLE mlm.bonuses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, + + node_id UUID NOT NULL REFERENCES mlm.nodes(id) ON DELETE CASCADE, + rank_id UUID REFERENCES mlm.ranks(id) ON DELETE SET NULL, + + type mlm.bonus_type NOT NULL, + amount DECIMAL(15,2) NOT NULL, + currency VARCHAR(3) DEFAULT 'USD', + + -- Reference to period + period_id UUID REFERENCES commissions.periods(id) ON DELETE SET NULL, + + -- Status + status mlm.commission_status NOT NULL DEFAULT 'pending', + paid_at TIMESTAMPTZ, + + achieved_at TIMESTAMPTZ DEFAULT NOW(), + notes TEXT, + + created_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE mlm.bonuses IS 'MLM bonus entries for rank achievements and other bonuses'; + +-- ───────────────────────────────────────────── +-- rank_history - Historical rank achievements +-- ───────────────────────────────────────────── +CREATE TABLE mlm.rank_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, + + node_id UUID NOT NULL REFERENCES mlm.nodes(id) ON DELETE CASCADE, + rank_id UUID NOT NULL REFERENCES mlm.ranks(id) ON DELETE CASCADE, + + previous_rank_id UUID REFERENCES mlm.ranks(id) ON DELETE SET NULL, + + -- Snapshot of metrics at achievement + personal_volume_at DECIMAL(15,2), + group_volume_at DECIMAL(15,2), + direct_referrals_at INTEGER, + + achieved_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE mlm.rank_history IS 'Historical record of rank achievements'; diff --git a/ddl/schemas/mlm/04-rls.sql b/ddl/schemas/mlm/04-rls.sql new file mode 100644 index 0000000..43ddca6 --- /dev/null +++ b/ddl/schemas/mlm/04-rls.sql @@ -0,0 +1,126 @@ +-- ============================================= +-- RLS Policies: mlm +-- Module: SAAS-021 MLM (Multi-Level Marketing) +-- ============================================= + +-- Enable RLS on all tables +ALTER TABLE mlm.structures ENABLE ROW LEVEL SECURITY; +ALTER TABLE mlm.ranks ENABLE ROW LEVEL SECURITY; +ALTER TABLE mlm.nodes ENABLE ROW LEVEL SECURITY; +ALTER TABLE mlm.commissions ENABLE ROW LEVEL SECURITY; +ALTER TABLE mlm.bonuses ENABLE ROW LEVEL SECURITY; +ALTER TABLE mlm.rank_history ENABLE ROW LEVEL SECURITY; + +-- ───────────────────────────────────────────── +-- structures policies +-- ───────────────────────────────────────────── +CREATE POLICY structures_tenant_isolation_select + ON mlm.structures FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY structures_tenant_isolation_insert + ON mlm.structures FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY structures_tenant_isolation_update + ON mlm.structures FOR UPDATE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY structures_tenant_isolation_delete + ON mlm.structures FOR DELETE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +-- ───────────────────────────────────────────── +-- ranks policies +-- ───────────────────────────────────────────── +CREATE POLICY ranks_tenant_isolation_select + ON mlm.ranks FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY ranks_tenant_isolation_insert + ON mlm.ranks FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY ranks_tenant_isolation_update + ON mlm.ranks FOR UPDATE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY ranks_tenant_isolation_delete + ON mlm.ranks FOR DELETE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +-- ───────────────────────────────────────────── +-- nodes policies +-- ───────────────────────────────────────────── +CREATE POLICY nodes_tenant_isolation_select + ON mlm.nodes FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY nodes_tenant_isolation_insert + ON mlm.nodes FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY nodes_tenant_isolation_update + ON mlm.nodes FOR UPDATE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY nodes_tenant_isolation_delete + ON mlm.nodes FOR DELETE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +-- ───────────────────────────────────────────── +-- commissions policies +-- ───────────────────────────────────────────── +CREATE POLICY commissions_tenant_isolation_select + ON mlm.commissions FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY commissions_tenant_isolation_insert + ON mlm.commissions FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY commissions_tenant_isolation_update + ON mlm.commissions FOR UPDATE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY commissions_tenant_isolation_delete + ON mlm.commissions FOR DELETE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +-- ───────────────────────────────────────────── +-- bonuses policies +-- ───────────────────────────────────────────── +CREATE POLICY bonuses_tenant_isolation_select + ON mlm.bonuses FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY bonuses_tenant_isolation_insert + ON mlm.bonuses FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY bonuses_tenant_isolation_update + ON mlm.bonuses FOR UPDATE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY bonuses_tenant_isolation_delete + ON mlm.bonuses FOR DELETE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +-- ───────────────────────────────────────────── +-- rank_history policies +-- ───────────────────────────────────────────── +CREATE POLICY rank_history_tenant_isolation_select + ON mlm.rank_history FOR SELECT + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY rank_history_tenant_isolation_insert + ON mlm.rank_history FOR INSERT + WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY rank_history_tenant_isolation_update + ON mlm.rank_history FOR UPDATE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); + +CREATE POLICY rank_history_tenant_isolation_delete + ON mlm.rank_history FOR DELETE + USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID); diff --git a/ddl/schemas/mlm/05-indexes.sql b/ddl/schemas/mlm/05-indexes.sql new file mode 100644 index 0000000..f91102f --- /dev/null +++ b/ddl/schemas/mlm/05-indexes.sql @@ -0,0 +1,87 @@ +-- ============================================= +-- Indexes: mlm +-- Module: SAAS-021 MLM (Multi-Level Marketing) +-- ============================================= + +-- ───────────────────────────────────────────── +-- structures indexes +-- ───────────────────────────────────────────── +CREATE INDEX idx_structures_tenant ON mlm.structures (tenant_id); +CREATE INDEX idx_structures_active ON mlm.structures (tenant_id, is_active) WHERE is_active = true; +CREATE INDEX idx_structures_type ON mlm.structures (tenant_id, type); + +-- ───────────────────────────────────────────── +-- ranks indexes +-- ───────────────────────────────────────────── +CREATE INDEX idx_ranks_tenant ON mlm.ranks (tenant_id); +CREATE INDEX idx_ranks_structure ON mlm.ranks (structure_id); +CREATE INDEX idx_ranks_level ON mlm.ranks (structure_id, level); +CREATE INDEX idx_ranks_active ON mlm.ranks (structure_id, is_active) WHERE is_active = true; + +-- ───────────────────────────────────────────── +-- nodes indexes (CRITICAL for performance) +-- ───────────────────────────────────────────── +-- Tenant isolation +CREATE INDEX idx_nodes_tenant ON mlm.nodes (tenant_id); + +-- Structure lookup +CREATE INDEX idx_nodes_structure ON mlm.nodes (structure_id); + +-- User lookup +CREATE INDEX idx_nodes_user ON mlm.nodes (user_id); + +-- Hierarchy lookups +CREATE INDEX idx_nodes_parent ON mlm.nodes (parent_id); +CREATE INDEX idx_nodes_sponsor ON mlm.nodes (sponsor_id); + +-- LTREE path index (for ancestor/descendant queries) +CREATE INDEX idx_nodes_path ON mlm.nodes USING GIST (path); + +-- Depth-based queries +CREATE INDEX idx_nodes_depth ON mlm.nodes (structure_id, depth); + +-- Status filtering +CREATE INDEX idx_nodes_status ON mlm.nodes (structure_id, status); +CREATE INDEX idx_nodes_active ON mlm.nodes (structure_id) WHERE status = 'active'; + +-- Rank lookups +CREATE INDEX idx_nodes_rank ON mlm.nodes (rank_id); + +-- Invite code lookup +CREATE INDEX idx_nodes_invite_code ON mlm.nodes (invite_code) WHERE invite_code IS NOT NULL; + +-- Combined for common queries +CREATE INDEX idx_nodes_structure_parent ON mlm.nodes (structure_id, parent_id); +CREATE INDEX idx_nodes_structure_user ON mlm.nodes (structure_id, user_id); + +-- ───────────────────────────────────────────── +-- commissions indexes +-- ───────────────────────────────────────────── +CREATE INDEX idx_commissions_tenant ON mlm.commissions (tenant_id); +CREATE INDEX idx_commissions_node ON mlm.commissions (node_id); +CREATE INDEX idx_commissions_source_node ON mlm.commissions (source_node_id); +CREATE INDEX idx_commissions_type ON mlm.commissions (tenant_id, type); +CREATE INDEX idx_commissions_level ON mlm.commissions (tenant_id, level); +CREATE INDEX idx_commissions_status ON mlm.commissions (tenant_id, status); +CREATE INDEX idx_commissions_period ON mlm.commissions (period_id); +CREATE INDEX idx_commissions_pending ON mlm.commissions (tenant_id) WHERE status = 'pending'; +CREATE INDEX idx_commissions_created ON mlm.commissions (tenant_id, created_at DESC); + +-- ───────────────────────────────────────────── +-- bonuses indexes +-- ───────────────────────────────────────────── +CREATE INDEX idx_bonuses_tenant ON mlm.bonuses (tenant_id); +CREATE INDEX idx_bonuses_node ON mlm.bonuses (node_id); +CREATE INDEX idx_bonuses_rank ON mlm.bonuses (rank_id); +CREATE INDEX idx_bonuses_type ON mlm.bonuses (tenant_id, type); +CREATE INDEX idx_bonuses_status ON mlm.bonuses (tenant_id, status); +CREATE INDEX idx_bonuses_period ON mlm.bonuses (period_id); +CREATE INDEX idx_bonuses_pending ON mlm.bonuses (tenant_id) WHERE status = 'pending'; + +-- ───────────────────────────────────────────── +-- rank_history indexes +-- ───────────────────────────────────────────── +CREATE INDEX idx_rank_history_tenant ON mlm.rank_history (tenant_id); +CREATE INDEX idx_rank_history_node ON mlm.rank_history (node_id); +CREATE INDEX idx_rank_history_rank ON mlm.rank_history (rank_id); +CREATE INDEX idx_rank_history_achieved ON mlm.rank_history (node_id, achieved_at DESC);