""" Customer (Rolodex) API endpoints """ from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session, joinedload from sqlalchemy import or_, func from app.database.base import get_db from app.models.rolodex import Rolodex, Phone from app.models.user import User from app.auth.security import get_current_user router = APIRouter() # Pydantic schemas for request/response from pydantic import BaseModel, EmailStr from datetime import date class PhoneCreate(BaseModel): location: Optional[str] = None phone: str class PhoneResponse(BaseModel): id: int location: Optional[str] phone: str class Config: from_attributes = True class CustomerBase(BaseModel): id: str last: str first: Optional[str] = None middle: Optional[str] = None prefix: Optional[str] = None suffix: Optional[str] = None title: Optional[str] = None group: Optional[str] = None a1: Optional[str] = None a2: Optional[str] = None a3: Optional[str] = None city: Optional[str] = None abrev: Optional[str] = None zip: Optional[str] = None email: Optional[EmailStr] = None dob: Optional[date] = None ss_number: Optional[str] = None legal_status: Optional[str] = None memo: Optional[str] = None class CustomerCreate(CustomerBase): pass class CustomerUpdate(BaseModel): last: Optional[str] = None first: Optional[str] = None middle: Optional[str] = None prefix: Optional[str] = None suffix: Optional[str] = None title: Optional[str] = None group: Optional[str] = None a1: Optional[str] = None a2: Optional[str] = None a3: Optional[str] = None city: Optional[str] = None abrev: Optional[str] = None zip: Optional[str] = None email: Optional[EmailStr] = None dob: Optional[date] = None ss_number: Optional[str] = None legal_status: Optional[str] = None memo: Optional[str] = None class CustomerResponse(CustomerBase): phone_numbers: List[PhoneResponse] = [] class Config: from_attributes = True @router.get("/search/phone") async def search_by_phone( phone: str = Query(..., description="Phone number to search for"), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Search customers by phone number (legacy phone search feature)""" phones = db.query(Phone).join(Rolodex).filter( Phone.phone.contains(phone) ).options(joinedload(Phone.rolodex_entry)).all() results = [] for phone_record in phones: results.append({ "phone": phone_record.phone, "location": phone_record.location, "customer": { "id": phone_record.rolodex_entry.id, "name": f"{phone_record.rolodex_entry.first or ''} {phone_record.rolodex_entry.last}".strip(), "city": phone_record.rolodex_entry.city, "state": phone_record.rolodex_entry.abrev } }) return results @router.get("/groups") async def get_customer_groups( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get list of customer groups for filtering""" groups = db.query(Rolodex.group).filter( Rolodex.group.isnot(None), Rolodex.group != "" ).distinct().all() return [{"group": group[0]} for group in groups if group[0]] @router.get("/states") async def get_states( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get list of states used in the database""" states = db.query(Rolodex.abrev).filter( Rolodex.abrev.isnot(None), Rolodex.abrev != "" ).distinct().all() return [{"state": state[0]} for state in states if state[0]] @router.get("/stats") async def get_customer_stats( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get customer database statistics""" total_customers = db.query(Rolodex).count() total_phones = db.query(Phone).count() customers_with_email = db.query(Rolodex).filter( Rolodex.email.isnot(None), Rolodex.email != "" ).count() # Group breakdown group_stats = db.query(Rolodex.group, func.count(Rolodex.id)).filter( Rolodex.group.isnot(None), Rolodex.group != "" ).group_by(Rolodex.group).all() return { "total_customers": total_customers, "total_phone_numbers": total_phones, "customers_with_email": customers_with_email, "group_breakdown": [{"group": group, "count": count} for group, count in group_stats] } @router.get("/", response_model=List[CustomerResponse]) async def list_customers( skip: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), search: Optional[str] = Query(None), db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """List customers with pagination and search""" try: query = db.query(Rolodex).options(joinedload(Rolodex.phone_numbers)) if search: query = query.filter( or_( Rolodex.id.contains(search), Rolodex.last.contains(search), Rolodex.first.contains(search), Rolodex.city.contains(search), Rolodex.email.contains(search) ) ) customers = query.offset(skip).limit(limit).all() return customers except Exception as e: raise HTTPException(status_code=500, detail=f"Error loading customers: {str(e)}") @router.get("/{customer_id}", response_model=CustomerResponse) async def get_customer( customer_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get specific customer by ID""" customer = db.query(Rolodex).options(joinedload(Rolodex.phone_numbers)).filter( Rolodex.id == customer_id ).first() if not customer: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found" ) return customer @router.post("/", response_model=CustomerResponse) async def create_customer( customer_data: CustomerCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Create new customer""" # Check if ID already exists existing = db.query(Rolodex).filter(Rolodex.id == customer_data.id).first() if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Customer ID already exists" ) customer = Rolodex(**customer_data.model_dump()) db.add(customer) db.commit() db.refresh(customer) return customer @router.put("/{customer_id}", response_model=CustomerResponse) async def update_customer( customer_id: str, customer_data: CustomerUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Update customer""" customer = db.query(Rolodex).filter(Rolodex.id == customer_id).first() if not customer: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found" ) # Update fields for field, value in customer_data.model_dump(exclude_unset=True).items(): setattr(customer, field, value) db.commit() db.refresh(customer) return customer @router.delete("/{customer_id}") async def delete_customer( customer_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Delete customer""" customer = db.query(Rolodex).filter(Rolodex.id == customer_id).first() if not customer: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found" ) db.delete(customer) db.commit() return {"message": "Customer deleted successfully"} @router.get("/{customer_id}/phones", response_model=List[PhoneResponse]) async def get_customer_phones( customer_id: str, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Get customer phone numbers""" customer = db.query(Rolodex).filter(Rolodex.id == customer_id).first() if not customer: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found" ) phones = db.query(Phone).filter(Phone.rolodex_id == customer_id).all() return phones @router.post("/{customer_id}/phones", response_model=PhoneResponse) async def add_customer_phone( customer_id: str, phone_data: PhoneCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Add phone number to customer""" customer = db.query(Rolodex).filter(Rolodex.id == customer_id).first() if not customer: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Customer not found" ) phone = Phone( rolodex_id=customer_id, location=phone_data.location, phone=phone_data.phone ) db.add(phone) db.commit() db.refresh(phone) return phone @router.put("/{customer_id}/phones/{phone_id}", response_model=PhoneResponse) async def update_customer_phone( customer_id: str, phone_id: int, phone_data: PhoneCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Update customer phone number""" phone = db.query(Phone).filter( Phone.id == phone_id, Phone.rolodex_id == customer_id ).first() if not phone: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Phone number not found" ) phone.location = phone_data.location phone.phone = phone_data.phone db.commit() db.refresh(phone) return phone @router.delete("/{customer_id}/phones/{phone_id}") async def delete_customer_phone( customer_id: str, phone_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """Delete customer phone number""" phone = db.query(Phone).filter( Phone.id == phone_id, Phone.rolodex_id == customer_id ).first() if not phone: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Phone number not found" ) db.delete(phone) db.commit() return {"message": "Phone number deleted successfully"}