- Add 01-schema.sql: Create commissions schema - Add 02-tables.sql: commission_schemes, commission_assignments, commission_entries, commission_periods - Add 03-functions.sql: calculate_commission(), close_period() - Add 04-triggers.sql: Auto-calculate on insert, prevent double-counting - Add 05-indexes.sql: Performance indexes for queries - Add 06-seed.sql: Sample commission schemes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
196 lines
7.6 KiB
PL/PgSQL
196 lines
7.6 KiB
PL/PgSQL
-- ============================================
|
|
-- 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();
|