template-saas-database-v2/ddl/schemas/mlm/02-tables.sql
Adrian Flores Cortes 2837480e17 [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 <noreply@anthropic.com>
2026-01-25 06:48:34 -06:00

236 lines
9.3 KiB
SQL

-- =============================================
-- 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';