"""Contacts router: CRUD, search, filter, and contact person management endpoints.""" import uuid from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from app.database import get_db from app.dependencies import get_current_user, get_pagination, require_role from app.models.user import User from app.schemas.contact import ( ContactCreate, ContactListResponse, ContactPersonCreate, ContactPersonResponse, ContactResponse, ContactUpdate, ) from app.services import contact_service router = APIRouter(prefix="/contacts", tags=["contacts"]) @router.get("/", response_model=ContactListResponse, status_code=status.HTTP_200_OK) async def list_contacts( db: AsyncSession = Depends(get_db), pagination: dict = Depends(get_pagination), search: str | None = Query( None, description="Search in company_name, city, email, vat_id" ), role: str | None = Query( None, description="Filter by role (kaeufer, verkaeufer, beide)" ), is_eu: bool | None = Query(None, description="Filter EU (true) or Inland (false)"), is_private: bool | None = Query(None, description="Filter private contacts"), sort: str | None = Query(None, description="Sort field (prefix - for descending)"), current_user: User = Depends(get_current_user), ): """List contacts with pagination, filtering, and sorting. All authenticated roles can read. """ contacts, total = await contact_service.list_contacts( db, page=pagination["page"], page_size=pagination["page_size"], search=search, role=role, is_eu=is_eu, is_private=is_private, sort=sort, ) return ContactListResponse( items=[ContactResponse.model_validate(c) for c in contacts], total=total, page=pagination["page"], page_size=pagination["page_size"], ) @router.post("/", response_model=ContactResponse, status_code=status.HTTP_201_CREATED) async def create_contact( body: ContactCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role(["admin", "verkaeufer"])), ): """Create a new contact. Requires admin or verkaeufer role.""" data = body.model_dump(exclude_unset=False) contact = await contact_service.create_contact(db, data) return ContactResponse.model_validate(contact) @router.get( "/{contact_id}", response_model=ContactResponse, status_code=status.HTTP_200_OK ) async def get_contact( contact_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): """Get a single contact by ID with contact persons.""" contact = await contact_service.get_contact_by_id(db, contact_id) if contact is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "CONTACT_NOT_FOUND", "message": "Contact not found"} }, ) return ContactResponse.model_validate(contact) @router.put( "/{contact_id}", response_model=ContactResponse, status_code=status.HTTP_200_OK ) async def update_contact( contact_id: uuid.UUID, body: ContactUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role(["admin", "verkaeufer"])), ): """Update a contact's fields. Requires admin or verkaeufer role.""" updates = body.model_dump(exclude_unset=True) if not updates: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail={"error": {"code": "NO_FIELDS", "message": "No fields to update"}}, ) contact = await contact_service.update_contact(db, contact_id, updates) if contact is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "CONTACT_NOT_FOUND", "message": "Contact not found"} }, ) return ContactResponse.model_validate(contact) @router.delete( "/{contact_id}", response_model=ContactResponse, status_code=status.HTTP_200_OK ) async def delete_contact( contact_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role(["admin", "verkaeufer"])), ): """Soft-delete a contact (sets deleted_at). Requires admin or verkaeufer role.""" contact = await contact_service.soft_delete_contact(db, contact_id) if contact is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "CONTACT_NOT_FOUND", "message": "Contact not found"} }, ) return ContactResponse.model_validate(contact) @router.post( "/{contact_id}/persons", response_model=ContactPersonResponse, status_code=status.HTTP_201_CREATED, ) async def add_contact_person( contact_id: uuid.UUID, body: ContactPersonCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role(["admin", "verkaeufer"])), ): """Add a contact person to an existing contact. Requires admin or verkaeufer role.""" person = await contact_service.add_contact_person( db, contact_id, body.model_dump(exclude_unset=False) ) if person is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": {"code": "CONTACT_NOT_FOUND", "message": "Contact not found"} }, ) return ContactPersonResponse.model_validate(person) @router.delete( "/{contact_id}/persons/{person_id}", status_code=status.HTTP_204_NO_CONTENT, ) async def remove_contact_person( contact_id: uuid.UUID, person_id: uuid.UUID, db: AsyncSession = Depends(get_db), current_user: User = Depends(require_role(["admin", "verkaeufer"])), ): """Remove a contact person from a contact. Requires admin or verkaeufer role.""" deleted = await contact_service.remove_contact_person(db, contact_id, person_id) if not deleted: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail={ "error": { "code": "PERSON_NOT_FOUND", "message": "Contact person not found", } }, ) return None