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