# Database Repository for Lead Management import psycopg2 from psycopg2.extras import RealDictCursor from uuid import UUID from typing import List, Optional, Dict, Any from datetime import datetime class LeadRepository: def __init__(self, get_db_connection): self.get_db_connection = get_db_connection # Institution Methods def get_institutions_with_counts(self) -> List[Dict[str, Any]]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ SELECT i.id, i.name, i.metadata, i.created_at, i.updated_at, i.created_by, COUNT(c.id) as contact_count FROM lead_institutions i LEFT JOIN lead_contacts c ON c.institution_id = i.id GROUP BY i.id ORDER BY i.name """ cur.execute(query) results = cur.fetchall() cur.close() return results def create_institution(self, name: str, created_by: str) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ INSERT INTO lead_institutions (name, created_by) VALUES (%s, %s) RETURNING * """ cur.execute(query, (name, created_by)) result = cur.fetchone() cur.close() return result def get_institution_by_id(self, institution_id: UUID) -> Optional[Dict[str, Any]]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ SELECT * FROM lead_institutions WHERE id = %s """ cur.execute(query, (str(institution_id),)) result = cur.fetchone() cur.close() return result def update_institution(self, institution_id: UUID, name: str) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ UPDATE lead_institutions SET name = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s RETURNING * """ cur.execute(query, (name, str(institution_id))) result = cur.fetchone() cur.close() return result # Contact Methods def get_contacts_by_institution(self, institution_id: UUID) -> List[Dict[str, Any]]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ SELECT c.*, i.name as institution_name FROM lead_contacts c JOIN lead_institutions i ON i.id = c.institution_id WHERE c.institution_id = %s ORDER BY c.last_name, c.first_name """ cur.execute(query, (str(institution_id),)) results = cur.fetchall() cur.close() return results def create_contact(self, data: Dict[str, Any]) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ INSERT INTO lead_contacts (institution_id, first_name, last_name, position, extra_fields) VALUES (%s, %s, %s, %s, %s) RETURNING * """ cur.execute(query, ( str(data['institution_id']), data['first_name'], data['last_name'], data.get('position'), psycopg2.extras.Json(data.get('extra_fields', {})) )) result = cur.fetchone() cur.close() return result def get_contact_with_details(self, contact_id: UUID) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) # Get contact base info query = """ SELECT c.*, i.name as institution_name FROM lead_contacts c JOIN lead_institutions i ON i.id = c.institution_id WHERE c.id = %s """ cur.execute(query, (str(contact_id),)) contact = cur.fetchone() if contact: # Get contact details (phones, emails) details_query = """ SELECT * FROM lead_contact_details WHERE contact_id = %s ORDER BY detail_type, is_primary DESC, created_at """ cur.execute(details_query, (str(contact_id),)) contact['details'] = cur.fetchall() # Get notes notes_query = """ SELECT * FROM lead_notes WHERE contact_id = %s AND is_current = true ORDER BY created_at DESC """ cur.execute(notes_query, (str(contact_id),)) contact['notes'] = cur.fetchall() cur.close() return contact def update_contact(self, contact_id: UUID, data: Dict[str, Any]) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ UPDATE lead_contacts SET first_name = %s, last_name = %s, position = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s RETURNING * """ cur.execute(query, ( data['first_name'], data['last_name'], data.get('position'), str(contact_id) )) result = cur.fetchone() cur.close() return result # Contact Details Methods def add_contact_detail(self, contact_id: UUID, detail_type: str, detail_value: str, detail_label: str = None) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ INSERT INTO lead_contact_details (contact_id, detail_type, detail_value, detail_label) VALUES (%s, %s, %s, %s) RETURNING * """ cur.execute(query, ( str(contact_id), detail_type, detail_value, detail_label )) result = cur.fetchone() cur.close() return result def get_contact_detail_by_id(self, detail_id: UUID) -> Optional[Dict[str, Any]]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = "SELECT * FROM lead_contact_details WHERE id = %s" cur.execute(query, (str(detail_id),)) result = cur.fetchone() cur.close() return result def update_contact_detail(self, detail_id: UUID, detail_value: str, detail_label: str = None) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ UPDATE lead_contact_details SET detail_value = %s, detail_label = %s, updated_at = CURRENT_TIMESTAMP WHERE id = %s RETURNING * """ cur.execute(query, (detail_value, detail_label, str(detail_id))) result = cur.fetchone() cur.close() return result def delete_contact_detail(self, detail_id: UUID) -> bool: with self.get_db_connection() as conn: cur = conn.cursor() query = "DELETE FROM lead_contact_details WHERE id = %s" cur.execute(query, (str(detail_id),)) deleted = cur.rowcount > 0 cur.close() return deleted # Notes Methods def create_note(self, contact_id: UUID, note_text: str, created_by: str) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ INSERT INTO lead_notes (contact_id, note_text, created_by) VALUES (%s, %s, %s) RETURNING * """ cur.execute(query, ( str(contact_id), note_text, created_by )) result = cur.fetchone() cur.close() return result def update_note(self, note_id: UUID, note_text: str, updated_by: str) -> Dict[str, Any]: with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) # First, mark current version as not current update_old = """ UPDATE lead_notes SET is_current = false WHERE id = %s """ cur.execute(update_old, (str(note_id),)) # Create new version create_new = """ INSERT INTO lead_notes (contact_id, note_text, created_by, parent_note_id, version) SELECT contact_id, %s, %s, %s, version + 1 FROM lead_notes WHERE id = %s RETURNING * """ cur.execute(create_new, ( note_text, updated_by, str(note_id), str(note_id) )) result = cur.fetchone() cur.close() return result def delete_note(self, note_id: UUID) -> bool: with self.get_db_connection() as conn: cur = conn.cursor() # Soft delete by marking as not current query = """ UPDATE lead_notes SET is_current = false WHERE id = %s """ cur.execute(query, (str(note_id),)) deleted = cur.rowcount > 0 cur.close() return deleted def get_all_contacts_with_institutions(self) -> List[Dict[str, Any]]: """Get all contacts with their institution information""" with self.get_db_connection() as conn: cur = conn.cursor(cursor_factory=RealDictCursor) query = """ SELECT c.id, c.first_name, c.last_name, c.position, c.created_at, c.updated_at, c.institution_id, i.name as institution_name, (SELECT COUNT(*) FROM lead_contact_details WHERE contact_id = c.id AND detail_type = 'phone') as phone_count, (SELECT COUNT(*) FROM lead_contact_details WHERE contact_id = c.id AND detail_type = 'email') as email_count, (SELECT COUNT(*) FROM lead_notes WHERE contact_id = c.id AND is_current = true) as note_count FROM lead_contacts c JOIN lead_institutions i ON i.id = c.institution_id ORDER BY c.last_name, c.first_name """ cur.execute(query) results = cur.fetchall() cur.close() return results