Major additions: - Territory manager system with application workflow - Custom pricing and page builder with Craft.js - Enhanced Stripe Connect onboarding - CodeReadr QR scanning integration - Kiosk mode for venue sales - Super admin dashboard and analytics - MCP integration for AI-powered operations Infrastructure improvements: - Centralized API client and routing system - Enhanced authentication with organization context - Comprehensive theme management system - Advanced event management with custom tabs - Performance monitoring and accessibility features Database schema updates: - Territory management tables - Custom pages and pricing structures - Kiosk PIN system - Enhanced organization profiles - CodeReadr integration tables 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
297 lines
14 KiB
PL/PgSQL
297 lines
14 KiB
PL/PgSQL
-- Territory Manager System Schema
|
|
-- This migration creates the core tables for the Territory Manager system
|
|
|
|
-- Create territories table
|
|
CREATE TABLE IF NOT EXISTS territories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(255) NOT NULL,
|
|
boundary JSONB, -- GeoJSON polygon for territory bounds
|
|
population INTEGER,
|
|
market_size VARCHAR(50),
|
|
assigned_to UUID REFERENCES users(id),
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create territory_managers table
|
|
CREATE TABLE IF NOT EXISTS territory_managers (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES users(id) UNIQUE NOT NULL,
|
|
territory_id UUID REFERENCES territories(id),
|
|
referral_code VARCHAR(50) UNIQUE NOT NULL,
|
|
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'suspended', 'inactive')),
|
|
application_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
approved_date TIMESTAMP WITH TIME ZONE,
|
|
approved_by UUID REFERENCES users(id),
|
|
profile JSONB, -- Personal info, preferences, etc.
|
|
documents JSONB, -- Uploaded documents like ID, W-9, etc.
|
|
earnings_data JSONB, -- Earnings statistics and history
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create territory_applications table
|
|
CREATE TABLE IF NOT EXISTS territory_applications (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email VARCHAR(255) NOT NULL,
|
|
full_name VARCHAR(255) NOT NULL,
|
|
phone VARCHAR(20),
|
|
address JSONB, -- Address information
|
|
desired_territory VARCHAR(255),
|
|
has_transportation BOOLEAN DEFAULT false,
|
|
has_event_experience BOOLEAN DEFAULT false,
|
|
motivation TEXT,
|
|
documents JSONB, -- File uploads
|
|
consent JSONB, -- Background check and data processing consent
|
|
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
|
|
reviewed_by UUID REFERENCES users(id),
|
|
reviewed_at TIMESTAMP WITH TIME ZONE,
|
|
review_notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create leads table
|
|
CREATE TABLE IF NOT EXISTS leads (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
territory_manager_id UUID REFERENCES territory_managers(id) NOT NULL,
|
|
event_name VARCHAR(255) NOT NULL,
|
|
organizer_contact JSONB, -- Contact information
|
|
event_details JSONB, -- Event details like date, type, etc.
|
|
status VARCHAR(20) DEFAULT 'cold' CHECK (status IN ('cold', 'contacted', 'confirmed', 'converted', 'dead')),
|
|
notes TEXT[],
|
|
follow_up_date TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create commissions table
|
|
CREATE TABLE IF NOT EXISTS commissions (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
territory_manager_id UUID REFERENCES territory_managers(id) NOT NULL,
|
|
event_id UUID REFERENCES events(id) NOT NULL,
|
|
tickets_sold INTEGER DEFAULT 0,
|
|
commission_per_ticket DECIMAL(10,2) DEFAULT 0.10,
|
|
total_commission DECIMAL(10,2) DEFAULT 0,
|
|
status VARCHAR(20) DEFAULT 'unpaid' CHECK (status IN ('unpaid', 'paid', 'pending')),
|
|
payout_date TIMESTAMP WITH TIME ZONE,
|
|
stripe_transfer_id VARCHAR(255),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create training_progress table
|
|
CREATE TABLE IF NOT EXISTS training_progress (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
territory_manager_id UUID REFERENCES territory_managers(id) NOT NULL,
|
|
module_id VARCHAR(255) NOT NULL,
|
|
completed_at TIMESTAMP WITH TIME ZONE,
|
|
score INTEGER,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
UNIQUE(territory_manager_id, module_id)
|
|
);
|
|
|
|
-- Create achievements table
|
|
CREATE TABLE IF NOT EXISTS achievements (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
requirements JSONB, -- Achievement requirements
|
|
reward_amount DECIMAL(10,2) DEFAULT 0,
|
|
badge_icon VARCHAR(255),
|
|
is_active BOOLEAN DEFAULT true,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create territory_manager_achievements table
|
|
CREATE TABLE IF NOT EXISTS territory_manager_achievements (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
territory_manager_id UUID REFERENCES territory_managers(id) NOT NULL,
|
|
achievement_id UUID REFERENCES achievements(id) NOT NULL,
|
|
earned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
UNIQUE(territory_manager_id, achievement_id)
|
|
);
|
|
|
|
-- Create expense_reports table
|
|
CREATE TABLE IF NOT EXISTS expense_reports (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
territory_manager_id UUID REFERENCES territory_managers(id) NOT NULL,
|
|
event_id UUID REFERENCES events(id),
|
|
mileage DECIMAL(10,2) DEFAULT 0,
|
|
receipts JSONB, -- File uploads
|
|
total_amount DECIMAL(10,2) DEFAULT 0,
|
|
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'paid', 'rejected')),
|
|
submitted_date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
approved_date TIMESTAMP WITH TIME ZONE,
|
|
approved_by UUID REFERENCES users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create notifications table
|
|
CREATE TABLE IF NOT EXISTS tm_notifications (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
territory_manager_id UUID REFERENCES territory_managers(id) NOT NULL,
|
|
type VARCHAR(50) NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
message TEXT,
|
|
data JSONB, -- Additional notification data
|
|
read BOOLEAN DEFAULT false,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create indexes for better performance
|
|
CREATE INDEX idx_territories_assigned_to ON territories(assigned_to);
|
|
CREATE INDEX idx_territory_managers_user_id ON territory_managers(user_id);
|
|
CREATE INDEX idx_territory_managers_territory_id ON territory_managers(territory_id);
|
|
CREATE INDEX idx_territory_managers_status ON territory_managers(status);
|
|
CREATE INDEX idx_territory_applications_status ON territory_applications(status);
|
|
CREATE INDEX idx_territory_applications_email ON territory_applications(email);
|
|
CREATE INDEX idx_leads_territory_manager_id ON leads(territory_manager_id);
|
|
CREATE INDEX idx_leads_status ON leads(status);
|
|
CREATE INDEX idx_commissions_territory_manager_id ON commissions(territory_manager_id);
|
|
CREATE INDEX idx_commissions_event_id ON commissions(event_id);
|
|
CREATE INDEX idx_commissions_status ON commissions(status);
|
|
CREATE INDEX idx_training_progress_territory_manager_id ON training_progress(territory_manager_id);
|
|
CREATE INDEX idx_tm_notifications_territory_manager_id ON tm_notifications(territory_manager_id);
|
|
CREATE INDEX idx_tm_notifications_read ON tm_notifications(read);
|
|
|
|
-- Add RLS policies
|
|
ALTER TABLE territories ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE territory_managers ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE territory_applications ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE leads ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE commissions ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE training_progress ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE achievements ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE territory_manager_achievements ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE expense_reports ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE tm_notifications ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Basic RLS policies (admin can see all, territory managers can only see their own data)
|
|
CREATE POLICY "Admin can view all territories" ON territories FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can view their territory" ON territories FOR SELECT USING (assigned_to = auth.uid());
|
|
|
|
CREATE POLICY "Admin can view all territory managers" ON territory_managers FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can view their own data" ON territory_managers FOR SELECT USING (user_id = auth.uid());
|
|
CREATE POLICY "Territory managers can update their own data" ON territory_managers FOR UPDATE USING (user_id = auth.uid());
|
|
|
|
CREATE POLICY "Admin can view all applications" ON territory_applications FOR ALL USING (is_admin());
|
|
CREATE POLICY "Public can create applications" ON territory_applications FOR INSERT WITH CHECK (true);
|
|
|
|
CREATE POLICY "Admin can view all leads" ON leads FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can manage their leads" ON leads FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = leads.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
|
|
CREATE POLICY "Admin can view all commissions" ON commissions FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can view their commissions" ON commissions FOR SELECT USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = commissions.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
|
|
CREATE POLICY "Admin can view all training progress" ON training_progress FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can manage their training progress" ON training_progress FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = training_progress.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
|
|
CREATE POLICY "Everyone can view achievements" ON achievements FOR SELECT USING (true);
|
|
CREATE POLICY "Admin can manage achievements" ON achievements FOR ALL USING (is_admin());
|
|
|
|
CREATE POLICY "Admin can view all territory manager achievements" ON territory_manager_achievements FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can view their achievements" ON territory_manager_achievements FOR SELECT USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = territory_manager_achievements.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
|
|
CREATE POLICY "Admin can view all expense reports" ON expense_reports FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can manage their expense reports" ON expense_reports FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = expense_reports.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
|
|
CREATE POLICY "Admin can view all notifications" ON tm_notifications FOR ALL USING (is_admin());
|
|
CREATE POLICY "Territory managers can view their notifications" ON tm_notifications FOR SELECT USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = tm_notifications.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
CREATE POLICY "Territory managers can update their notifications" ON tm_notifications FOR UPDATE USING (
|
|
EXISTS (SELECT 1 FROM territory_managers WHERE territory_managers.id = tm_notifications.territory_manager_id AND territory_managers.user_id = auth.uid())
|
|
);
|
|
|
|
-- Create trigger for updating updated_at timestamps
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
CREATE TRIGGER update_territories_updated_at BEFORE UPDATE ON territories
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_territory_managers_updated_at BEFORE UPDATE ON territory_managers
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_territory_applications_updated_at BEFORE UPDATE ON territory_applications
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_leads_updated_at BEFORE UPDATE ON leads
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_commissions_updated_at BEFORE UPDATE ON commissions
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_expense_reports_updated_at BEFORE UPDATE ON expense_reports
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Function to generate unique referral codes
|
|
CREATE OR REPLACE FUNCTION generate_referral_code() RETURNS TEXT AS $$
|
|
DECLARE
|
|
code TEXT;
|
|
exists_check INTEGER;
|
|
BEGIN
|
|
LOOP
|
|
code := UPPER(SUBSTRING(MD5(random()::text) FROM 1 FOR 8));
|
|
SELECT COUNT(*) INTO exists_check FROM territory_managers WHERE referral_code = code;
|
|
IF exists_check = 0 THEN
|
|
RETURN code;
|
|
END IF;
|
|
END LOOP;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Function to auto-assign referral code when creating territory manager
|
|
CREATE OR REPLACE FUNCTION auto_assign_referral_code() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.referral_code IS NULL OR NEW.referral_code = '' THEN
|
|
NEW.referral_code := generate_referral_code();
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER auto_assign_referral_code_trigger
|
|
BEFORE INSERT ON territory_managers
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION auto_assign_referral_code();
|
|
|
|
-- Function to calculate commission totals
|
|
CREATE OR REPLACE FUNCTION calculate_commission_total() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.total_commission := NEW.tickets_sold * NEW.commission_per_ticket;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER calculate_commission_total_trigger
|
|
BEFORE INSERT OR UPDATE ON commissions
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION calculate_commission_total();
|
|
|
|
-- Insert sample achievements
|
|
INSERT INTO achievements (name, description, requirements, reward_amount, badge_icon) VALUES
|
|
('First Referral', 'Successfully refer your first event to BCT', '{"referrals": 1}', 50.00, 'first-referral.svg'),
|
|
('Sales Champion', 'Refer 10 events in a single month', '{"monthly_referrals": 10}', 500.00, 'sales-champion.svg'),
|
|
('Territory Master', 'Maintain 95% event success rate for 3 months', '{"success_rate": 0.95, "months": 3}', 1000.00, 'territory-master.svg'),
|
|
('Training Graduate', 'Complete all training modules', '{"training_modules": "all"}', 100.00, 'training-graduate.svg'),
|
|
('Community Builder', 'Refer 5 recurring events', '{"recurring_events": 5}', 300.00, 'community-builder.svg'),
|
|
('Revenue Generator', 'Generate over $10,000 in commission in a quarter', '{"quarterly_commission": 10000}', 2000.00, 'revenue-generator.svg'); |