-- ============================================ -- TEMPLATE-SAAS: Sales Functions -- Version: 1.0.0 -- Module: SAAS-018 -- ============================================ -- ============================================ -- Function: Convert Lead to Opportunity -- ============================================ CREATE OR REPLACE FUNCTION sales.convert_lead_to_opportunity( p_lead_id UUID, p_opportunity_name VARCHAR(255) DEFAULT NULL, p_amount DECIMAL(15, 2) DEFAULT 0, p_expected_close_date DATE DEFAULT NULL ) RETURNS UUID AS $$ DECLARE v_lead RECORD; v_opportunity_id UUID; v_tenant_id UUID; BEGIN -- Get lead data SELECT * INTO v_lead FROM sales.leads WHERE id = p_lead_id AND deleted_at IS NULL AND status != 'converted'; IF NOT FOUND THEN RAISE EXCEPTION 'Lead not found or already converted'; END IF; v_tenant_id := v_lead.tenant_id; -- Create opportunity INSERT INTO sales.opportunities ( tenant_id, name, lead_id, stage, amount, currency, expected_close_date, assigned_to, contact_name, contact_email, contact_phone, company_name, notes, created_by ) VALUES ( v_tenant_id, COALESCE(p_opportunity_name, v_lead.company || ' - ' || v_lead.first_name || ' ' || v_lead.last_name), p_lead_id, 'prospecting', COALESCE(p_amount, 0), 'USD', COALESCE(p_expected_close_date, CURRENT_DATE + INTERVAL '30 days'), v_lead.assigned_to, v_lead.first_name || ' ' || v_lead.last_name, v_lead.email, v_lead.phone, v_lead.company, v_lead.notes, v_lead.created_by ) RETURNING id INTO v_opportunity_id; -- Update lead as converted UPDATE sales.leads SET status = 'converted', converted_at = NOW(), converted_to_opportunity_id = v_opportunity_id WHERE id = p_lead_id; RETURN v_opportunity_id; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION sales.convert_lead_to_opportunity IS 'Converts a lead to an opportunity, copying relevant data'; -- ============================================ -- Function: Update Opportunity Stage -- ============================================ CREATE OR REPLACE FUNCTION sales.update_opportunity_stage( p_opportunity_id UUID, p_new_stage sales.opportunity_stage, p_notes TEXT DEFAULT NULL ) RETURNS BOOLEAN AS $$ DECLARE v_opportunity RECORD; BEGIN -- Get current opportunity SELECT * INTO v_opportunity FROM sales.opportunities WHERE id = p_opportunity_id AND deleted_at IS NULL; IF NOT FOUND THEN RAISE EXCEPTION 'Opportunity not found'; END IF; -- Update stage UPDATE sales.opportunities SET stage = p_new_stage, notes = CASE WHEN p_notes IS NOT NULL THEN COALESCE(notes, '') || E'\n\n[Stage Change] ' || p_notes ELSE notes END, won_at = CASE WHEN p_new_stage = 'closed_won' THEN NOW() ELSE won_at END, lost_at = CASE WHEN p_new_stage = 'closed_lost' THEN NOW() ELSE lost_at END, actual_close_date = CASE WHEN p_new_stage IN ('closed_won', 'closed_lost') THEN CURRENT_DATE ELSE actual_close_date END WHERE id = p_opportunity_id; RETURN TRUE; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION sales.update_opportunity_stage IS 'Updates opportunity stage with automatic win/loss tracking'; -- ============================================ -- Function: Calculate Lead Score -- ============================================ CREATE OR REPLACE FUNCTION sales.calculate_lead_score( p_lead_id UUID ) RETURNS INT AS $$ DECLARE v_lead RECORD; v_score INT := 0; v_activity_count INT; BEGIN -- Get lead data SELECT * INTO v_lead FROM sales.leads WHERE id = p_lead_id AND deleted_at IS NULL; IF NOT FOUND THEN RETURN 0; END IF; -- Base score for having email IF v_lead.email IS NOT NULL THEN v_score := v_score + 10; END IF; -- Score for phone IF v_lead.phone IS NOT NULL THEN v_score := v_score + 10; END IF; -- Score for company IF v_lead.company IS NOT NULL THEN v_score := v_score + 15; END IF; -- Score based on source CASE v_lead.source WHEN 'referral' THEN v_score := v_score + 20; WHEN 'website' THEN v_score := v_score + 15; WHEN 'event' THEN v_score := v_score + 10; ELSE v_score := v_score + 5; END CASE; -- Score based on status CASE v_lead.status WHEN 'qualified' THEN v_score := v_score + 25; WHEN 'contacted' THEN v_score := v_score + 15; WHEN 'new' THEN v_score := v_score + 5; ELSE NULL; END CASE; -- Score for activities SELECT COUNT(*) INTO v_activity_count FROM sales.activities WHERE lead_id = p_lead_id AND deleted_at IS NULL; v_score := v_score + LEAST(v_activity_count * 5, 20); -- Cap at 100 RETURN LEAST(v_score, 100); END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION sales.calculate_lead_score IS 'Calculates a lead score based on completeness and engagement'; -- ============================================ -- Function: Get Pipeline Summary -- ============================================ CREATE OR REPLACE FUNCTION sales.get_pipeline_summary( p_tenant_id UUID ) RETURNS TABLE ( stage sales.opportunity_stage, count BIGINT, total_amount DECIMAL(15, 2), avg_probability NUMERIC ) AS $$ BEGIN RETURN QUERY SELECT o.stage, COUNT(*)::BIGINT, COALESCE(SUM(o.amount), 0)::DECIMAL(15, 2), COALESCE(AVG(o.probability), 0)::NUMERIC FROM sales.opportunities o WHERE o.tenant_id = p_tenant_id AND o.deleted_at IS NULL GROUP BY o.stage ORDER BY CASE o.stage WHEN 'prospecting' THEN 1 WHEN 'qualification' THEN 2 WHEN 'proposal' THEN 3 WHEN 'negotiation' THEN 4 WHEN 'closed_won' THEN 5 WHEN 'closed_lost' THEN 6 END; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION sales.get_pipeline_summary IS 'Returns summary of opportunities by stage for a tenant'; -- ============================================ -- Function: Initialize Default Pipeline Stages -- ============================================ CREATE OR REPLACE FUNCTION sales.initialize_default_stages( p_tenant_id UUID ) RETURNS VOID AS $$ BEGIN -- Insert default stages if none exist IF NOT EXISTS (SELECT 1 FROM sales.pipeline_stages WHERE tenant_id = p_tenant_id) THEN INSERT INTO sales.pipeline_stages (tenant_id, name, position, color, is_won, is_lost) VALUES (p_tenant_id, 'Prospecting', 1, '#94A3B8', FALSE, FALSE), (p_tenant_id, 'Qualification', 2, '#3B82F6', FALSE, FALSE), (p_tenant_id, 'Proposal', 3, '#8B5CF6', FALSE, FALSE), (p_tenant_id, 'Negotiation', 4, '#F59E0B', FALSE, FALSE), (p_tenant_id, 'Closed Won', 5, '#10B981', TRUE, FALSE), (p_tenant_id, 'Closed Lost', 6, '#EF4444', FALSE, TRUE); END IF; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION sales.initialize_default_stages IS 'Creates default pipeline stages for a new tenant';