""" Contact API Router - Simplified (Read-Only) Only GET endpoints for now """ from fastapi import APIRouter, HTTPException, Query, Body, status from typing import Optional from pydantic import BaseModel, Field from app.core.database import execute_query, execute_insert import logging logger = logging.getLogger(__name__) router = APIRouter() class ContactCreate(BaseModel): """Schema for creating a contact""" first_name: str last_name: str = "" email: Optional[str] = None phone: Optional[str] = None title: Optional[str] = None company_id: Optional[int] = None class ContactUpdate(BaseModel): """Schema for updating a contact""" first_name: Optional[str] = None last_name: Optional[str] = None email: Optional[str] = None phone: Optional[str] = None mobile: Optional[str] = None title: Optional[str] = None department: Optional[str] = None is_active: Optional[bool] = None class ContactCompanyLink(BaseModel): customer_id: int is_primary: bool = True role: Optional[str] = None @router.get("/contacts-debug") async def debug_contacts(): """Debug endpoint: Check contact-company links""" try: # Count links links = execute_query("SELECT COUNT(*) as total FROM contact_companies") # Get sample with links sample = execute_query(""" SELECT c.id, c.first_name, c.last_name, COUNT(cc.customer_id) as company_count, ARRAY_AGG(cu.name) as company_names FROM contacts c LEFT JOIN contact_companies cc ON c.id = cc.contact_id LEFT JOIN customers cu ON cc.customer_id = cu.id GROUP BY c.id, c.first_name, c.last_name HAVING COUNT(cc.customer_id) > 0 LIMIT 10 """) # Test the actual query used in get_contacts test_query = """ SELECT c.id, c.first_name, c.last_name, COUNT(DISTINCT cc.customer_id) as company_count, ARRAY_AGG(DISTINCT cu.name ORDER BY cu.name) FILTER (WHERE cu.name IS NOT NULL) as company_names FROM contacts c LEFT JOIN contact_companies cc ON c.id = cc.contact_id LEFT JOIN customers cu ON cc.customer_id = cu.id GROUP BY c.id, c.first_name, c.last_name ORDER BY c.last_name, c.first_name LIMIT 10 """ test_result = execute_query(test_query) return { "total_links": links[0]['total'] if links else 0, "sample_contacts_with_companies": sample or [], "test_query_result": test_result or [], "note": "If company_count is 0, the JOIN might not be working" } except Exception as e: logger.error(f"Debug failed: {e}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) @router.get("/contacts") async def get_contacts( search: Optional[str] = None, customer_id: Optional[int] = None, is_active: Optional[bool] = None, limit: int = Query(default=100, le=1000), offset: int = Query(default=0, ge=0) ): """Get all contacts with optional filtering""" try: where_clauses = [] params = [] if search: where_clauses.append("(c.first_name ILIKE %s OR c.last_name ILIKE %s OR c.email ILIKE %s)") params.extend([f"%{search}%", f"%{search}%", f"%{search}%"]) if is_active is not None: where_clauses.append("c.is_active = %s") params.append(is_active) where_sql = "WHERE " + " AND ".join(where_clauses) if where_clauses else "" # Count total (needs alias c for consistency) count_query = f"SELECT COUNT(*) as count FROM contacts c {where_sql}" count_result = execute_query(count_query, tuple(params)) total = count_result[0]['count'] if count_result else 0 # Get contacts with company info query = f""" SELECT c.id, c.first_name, c.last_name, c.email, c.phone, c.mobile, c.title, c.department, c.is_active, c.created_at, c.updated_at, COUNT(DISTINCT cc.customer_id) as company_count, ARRAY_AGG(DISTINCT cu.name ORDER BY cu.name) FILTER (WHERE cu.name IS NOT NULL) as company_names FROM contacts c LEFT JOIN contact_companies cc ON c.id = cc.contact_id LEFT JOIN customers cu ON cc.customer_id = cu.id {where_sql} GROUP BY c.id, c.first_name, c.last_name, c.email, c.phone, c.mobile, c.title, c.department, c.is_active, c.created_at, c.updated_at ORDER BY company_count DESC, c.last_name, c.first_name LIMIT %s OFFSET %s """ params.extend([limit, offset]) contacts = execute_query(query, tuple(params)) return { "total": total, "contacts": contacts, "limit": limit, "offset": offset } except Exception as e: logger.error(f"Failed to get contacts: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/contacts", status_code=status.HTTP_201_CREATED) async def create_contact(contact: ContactCreate): """ Create a new basic contact """ try: # Check if email exists if contact.email: existing = execute_query( "SELECT id FROM contacts WHERE email = %s", (contact.email,) ) if existing: # Return existing contact if found? Or error? # For now, let's error to be safe, or just return it? # User prompted "Smart Create", implies if it exists, use it? # But safer to say "Email already exists" pass insert_query = """ INSERT INTO contacts (first_name, last_name, email, phone, title, is_active) VALUES (%s, %s, %s, %s, %s, true) RETURNING id """ contact_id = execute_insert( insert_query, (contact.first_name, contact.last_name, contact.email, contact.phone, contact.title) ) # Link to company if provided if contact.company_id: try: link_query = """ INSERT INTO contact_companies (contact_id, customer_id, is_primary, role) VALUES (%s, %s, true, 'primary') ON CONFLICT (contact_id, customer_id) DO UPDATE SET is_primary = EXCLUDED.is_primary, role = EXCLUDED.role RETURNING id """ execute_insert(link_query, (contact_id, contact.company_id)) except Exception as e: logger.error(f"Failed to link new contact {contact_id} to company {contact.company_id}: {e}") # Don't fail the whole request, just log it return await get_contact(contact_id) except Exception as e: logger.error(f"Failed to create contact: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/contacts/{contact_id}") async def get_contact(contact_id: int): """Get a single contact by ID with linked companies""" try: # Get contact info query = """ SELECT id, first_name, last_name, email, phone, mobile, title, department, is_active, user_company, vtiger_id, created_at, updated_at FROM contacts WHERE id = %s """ contacts = execute_query(query, (contact_id,)) if not contacts: raise HTTPException(status_code=404, detail="Contact not found") contact = contacts[0] # Get linked companies companies_query = """ SELECT cu.id, cu.name, cu.cvr_number, cc.is_primary, cc.role, cc.notes FROM contact_companies cc JOIN customers cu ON cc.customer_id = cu.id WHERE cc.contact_id = %s ORDER BY cc.is_primary DESC, cu.name """ companies = execute_query(companies_query, (contact_id,)) contact['companies'] = companies or [] return contact except HTTPException: raise except Exception as e: logger.error(f"Failed to get contact {contact_id}: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.put("/contacts/{contact_id}") async def update_contact(contact_id: int, contact_data: ContactUpdate): """Update a contact""" try: # Ensure contact exists contact = execute_query("SELECT id FROM contacts WHERE id = %s", (contact_id,)) if not contact: raise HTTPException(status_code=404, detail="Contact not found") # Build update query dynamically update_fields = [] params = [] for field, value in contact_data.model_dump(exclude_unset=True).items(): update_fields.append(f"{field} = %s") params.append(value) if not update_fields: # No fields to update return await get_contact(contact_id) params.append(contact_id) update_query = f""" UPDATE contacts SET {', '.join(update_fields)}, updated_at = NOW() WHERE id = %s RETURNING id """ execute_query(update_query, tuple(params)) return await get_contact(contact_id) except HTTPException: raise except Exception as e: logger.error(f"Failed to update contact {contact_id}: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/contacts/{contact_id}/companies") async def link_contact_to_company(contact_id: int, link: ContactCompanyLink): """Link a contact to a company""" try: # Ensure contact exists contact = execute_query("SELECT id FROM contacts WHERE id = %s", (contact_id,)) if not contact: raise HTTPException(status_code=404, detail="Contact not found") # Ensure customer exists customer = execute_query("SELECT id FROM customers WHERE id = %s", (link.customer_id,)) if not customer: raise HTTPException(status_code=404, detail="Customer not found") query = """ INSERT INTO contact_companies (contact_id, customer_id, is_primary, role) VALUES (%s, %s, %s, %s) ON CONFLICT (contact_id, customer_id) DO UPDATE SET is_primary = EXCLUDED.is_primary, role = EXCLUDED.role RETURNING id """ execute_insert(query, (contact_id, link.customer_id, link.is_primary, link.role)) return {"message": "Contact linked to company successfully"} except HTTPException: raise except Exception as e: logger.error(f"Failed to link contact to company: {e}") raise HTTPException(status_code=500, detail=str(e))