-- ============================================ -- TEMPLATE-SAAS: Commissions Tables -- Version: 1.0.0 -- Module: SAAS-020 -- ============================================ -- ============================================ -- Commission Schemes (configuration templates) -- ============================================ CREATE TABLE commissions.schemes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Basic info name VARCHAR(100) NOT NULL, description TEXT, -- Commission type and rates type commissions.scheme_type NOT NULL DEFAULT 'percentage', rate DECIMAL(5, 2) DEFAULT 0, -- For percentage type (0-100%) fixed_amount DECIMAL(15, 2) DEFAULT 0, -- For fixed type tiers JSONB DEFAULT '[]'::jsonb, -- For tiered type: [{from: 0, to: 1000, rate: 5}, ...] -- Application scope applies_to commissions.applies_to DEFAULT 'all', product_ids UUID[] DEFAULT '{}', -- When applies_to = 'products' category_ids UUID[] DEFAULT '{}', -- When applies_to = 'categories' -- Amount constraints min_amount DECIMAL(15, 2) DEFAULT 0, -- Minimum sale amount to qualify max_amount DECIMAL(15, 2), -- Maximum commission per sale (cap) -- Status is_active BOOLEAN DEFAULT TRUE, -- Audit created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, created_by UUID REFERENCES users.users(id) ON DELETE SET NULL, deleted_at TIMESTAMPTZ, -- Constraints CONSTRAINT check_rate_range CHECK (rate >= 0 AND rate <= 100), CONSTRAINT check_fixed_positive CHECK (fixed_amount >= 0), CONSTRAINT check_min_max CHECK (min_amount >= 0 AND (max_amount IS NULL OR max_amount >= min_amount)) ); COMMENT ON TABLE commissions.schemes IS 'Commission scheme configurations'; COMMENT ON COLUMN commissions.schemes.rate IS 'Commission rate as percentage (0-100)'; COMMENT ON COLUMN commissions.schemes.tiers IS 'JSON array of tier configurations: [{from, to, rate}]'; -- ============================================ -- Commission Assignments (user-scheme mapping) -- ============================================ CREATE TABLE commissions.assignments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Assignment mapping user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE, scheme_id UUID NOT NULL REFERENCES commissions.schemes(id) ON DELETE CASCADE, -- Validity period starts_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, ends_at TIMESTAMPTZ, -- NULL = no end date -- Override custom_rate DECIMAL(5, 2), -- Override scheme rate for this user -- Status is_active BOOLEAN DEFAULT TRUE, -- Audit created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, created_by UUID REFERENCES users.users(id) ON DELETE SET NULL, -- Constraints CONSTRAINT check_custom_rate_range CHECK (custom_rate IS NULL OR (custom_rate >= 0 AND custom_rate <= 100)), CONSTRAINT check_date_range CHECK (ends_at IS NULL OR ends_at > starts_at), CONSTRAINT unique_active_assignment UNIQUE (tenant_id, user_id, scheme_id, starts_at) ); COMMENT ON TABLE commissions.assignments IS 'User to commission scheme assignments'; COMMENT ON COLUMN commissions.assignments.custom_rate IS 'Override rate for specific user (optional)'; -- ============================================ -- Commission Entries (generated commissions) -- ============================================ CREATE TABLE commissions.entries ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Who earned the commission user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE, scheme_id UUID NOT NULL REFERENCES commissions.schemes(id) ON DELETE SET NULL, assignment_id UUID REFERENCES commissions.assignments(id) ON DELETE SET NULL, -- Reference to source transaction reference_type VARCHAR(50) NOT NULL, -- 'sale', 'opportunity', 'order', etc. reference_id UUID NOT NULL, -- ID of the source record -- Calculation details base_amount DECIMAL(15, 2) NOT NULL, -- Original sale amount rate_applied DECIMAL(5, 2) NOT NULL, -- Rate that was applied commission_amount DECIMAL(15, 2) NOT NULL, -- Calculated commission currency VARCHAR(3) DEFAULT 'USD', -- Status tracking status commissions.entry_status DEFAULT 'pending' NOT NULL, period_id UUID REFERENCES commissions.periods(id) ON DELETE SET NULL, -- Payment tracking paid_at TIMESTAMPTZ, payment_reference VARCHAR(255), -- External payment reference -- Additional info notes TEXT, metadata JSONB DEFAULT '{}'::jsonb, -- Extra data (product info, etc.) -- Audit created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, approved_by UUID REFERENCES users.users(id) ON DELETE SET NULL, approved_at TIMESTAMPTZ, -- Constraints CONSTRAINT check_base_amount_positive CHECK (base_amount >= 0), CONSTRAINT check_rate_applied_range CHECK (rate_applied >= 0 AND rate_applied <= 100), CONSTRAINT check_commission_positive CHECK (commission_amount >= 0) ); COMMENT ON TABLE commissions.entries IS 'Individual commission entries for transactions'; COMMENT ON COLUMN commissions.entries.reference_type IS 'Type of source record (sale, opportunity, order)'; COMMENT ON COLUMN commissions.entries.metadata IS 'Additional context data in JSON format'; -- ============================================ -- Commission Periods (payment cycles) -- ============================================ CREATE TABLE commissions.periods ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE, -- Period identification name VARCHAR(100) NOT NULL, -- e.g., "January 2026", "Week 1-2026" starts_at TIMESTAMPTZ NOT NULL, ends_at TIMESTAMPTZ NOT NULL, -- Aggregated totals (calculated on close) total_entries INT DEFAULT 0, total_amount DECIMAL(15, 2) DEFAULT 0, currency VARCHAR(3) DEFAULT 'USD', -- Status tracking status commissions.period_status DEFAULT 'open' NOT NULL, closed_at TIMESTAMPTZ, closed_by UUID REFERENCES users.users(id) ON DELETE SET NULL, paid_at TIMESTAMPTZ, paid_by UUID REFERENCES users.users(id) ON DELETE SET NULL, -- Payment info payment_reference VARCHAR(255), payment_notes TEXT, -- Audit created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL, created_by UUID REFERENCES users.users(id) ON DELETE SET NULL, -- Constraints CONSTRAINT check_period_dates CHECK (ends_at > starts_at), CONSTRAINT unique_period_dates UNIQUE (tenant_id, starts_at, ends_at) ); COMMENT ON TABLE commissions.periods IS 'Commission payment periods/cycles'; COMMENT ON COLUMN commissions.periods.name IS 'Human-readable period name'; -- ============================================ -- Triggers for updated_at -- ============================================ CREATE OR REPLACE FUNCTION commissions.update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_schemes_updated_at BEFORE UPDATE ON commissions.schemes FOR EACH ROW EXECUTE FUNCTION commissions.update_updated_at(); CREATE TRIGGER trg_entries_updated_at BEFORE UPDATE ON commissions.entries FOR EACH ROW EXECUTE FUNCTION commissions.update_updated_at();