from fastapi import APIRouter, Depends, Header, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.api.deps import ( ensure_service_employee, get_current_telegram_user, get_or_create_telegram_user, log_audit, require_internal_api_token, ) from app.db.session import get_session from app.models.car import ( Car, CarServiceLink, ServiceCenter, ServiceCenterVerification, ServiceEmployee, ServiceInboxMessage, ServiceVisit, ) from app.models.user import User from app.schemas.service_center import ( CarServiceLinkCreate, CarServiceLinkRead, ServiceCenterCreate, ServiceCenterRead, ServiceCenterVerificationCreate, ServiceCenterVerificationRead, ServiceEmployeeInvite, ServiceEmployeeRead, ServiceInboxCreate, ServiceInboxRead, ServiceVisitCreate, ServiceVisitRead, VehicleSearchRequest, VehicleSearchResult, ) from app.services.vehicle_identity import mask_license_plate, mask_vin router = APIRouter(prefix="/service-centers", tags=["service-centers"]) @router.post("", response_model=ServiceCenterRead, status_code=status.HTTP_201_CREATED) async def create_service_center( payload: ServiceCenterCreate, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> ServiceCenter: center = ServiceCenter( name=payload.display_name, display_name=payload.display_name, legal_name=payload.legal_name, country=payload.country.upper() if payload.country else None, city=payload.city, address=payload.address, phone=payload.phone, contact_phone=payload.contact_phone or payload.phone, telegram_chat_id=payload.telegram_chat_id, business_registration_number=payload.business_registration_number, owner_user_id=current_user.id, verification_status="pending", ) session.add(center) await session.flush() employee = ServiceEmployee( service_center_id=center.id, user_id=current_user.id, role="owner", status="active", ) session.add(employee) await log_audit(session, actor=current_user, action="service_center.create", target_type="service_center", target_id=center.id) await session.commit() await session.refresh(center) return center @router.get("/my", response_model=list[ServiceCenterRead]) async def my_service_centers( session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> list[ServiceCenter]: result = await session.execute( select(ServiceCenter) .join(ServiceEmployee, ServiceEmployee.service_center_id == ServiceCenter.id) .where(ServiceEmployee.user_id == current_user.id, ServiceEmployee.status == "active") .order_by(ServiceCenter.created_at.desc()) ) return list(result.scalars()) @router.get("", response_model=list[ServiceCenterRead]) async def list_service_centers( session: AsyncSession = Depends(get_session), x_internal_api_token: str | None = Header(default=None, alias="X-Internal-API-Token"), ) -> list[ServiceCenter]: require_internal_api_token(x_internal_api_token) result = await session.execute(select(ServiceCenter).order_by(ServiceCenter.name)) return list(result.scalars()) @router.post("/{service_center_id}/verification", response_model=ServiceCenterVerificationRead) async def submit_verification( service_center_id: int, payload: ServiceCenterVerificationCreate, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> ServiceCenterVerification: await ensure_service_employee(session, service_center_id, current_user, {"owner", "manager"}) verification = ServiceCenterVerification( service_center_id=service_center_id, submitted_documents=payload.submitted_documents, comment=payload.comment, status="pending", ) session.add(verification) center = await session.get(ServiceCenter, service_center_id) if center: center.verification_status = "pending" await log_audit(session, actor=current_user, action="service_center.verification.submit", target_type="service_center", target_id=service_center_id) await session.commit() await session.refresh(verification) return verification @router.post("/{service_center_id}/employees/invite", response_model=ServiceEmployeeRead) async def invite_employee( service_center_id: int, payload: ServiceEmployeeInvite, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> ServiceEmployee: await ensure_service_employee(session, service_center_id, current_user, {"owner", "manager"}) user = await get_or_create_telegram_user(session, telegram_id=payload.telegram_id) result = await session.execute( select(ServiceEmployee).where( ServiceEmployee.service_center_id == service_center_id, ServiceEmployee.user_id == user.id, ) ) employee = result.scalar_one_or_none() if employee is None: employee = ServiceEmployee( service_center_id=service_center_id, user_id=user.id, role=payload.role, permissions=payload.permissions, status="invited", ) session.add(employee) else: employee.role = payload.role employee.permissions = payload.permissions employee.status = "invited" await log_audit(session, actor=current_user, action="service_employee.invite", target_type="service_center", target_id=service_center_id, metadata={"telegram_id": payload.telegram_id}) await session.commit() await session.refresh(employee) return employee @router.get("/{service_center_id}/visits", response_model=list[ServiceVisitRead]) async def service_center_visits( service_center_id: int, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> list[ServiceVisit]: await ensure_service_employee(session, service_center_id, current_user) result = await session.execute( select(ServiceVisit) .where(ServiceVisit.service_center_id == service_center_id) .order_by(ServiceVisit.visit_date.desc(), ServiceVisit.id.desc()) ) return list(result.scalars()) @router.post("/{service_center_id}/visits", response_model=ServiceVisitRead, status_code=status.HTTP_201_CREATED) async def create_visit( service_center_id: int, payload: ServiceVisitCreate, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> ServiceVisit: employee = await ensure_service_employee(session, service_center_id, current_user, {"owner", "manager", "receptionist"}) vehicle = await session.get(Car, payload.vehicle_id) if vehicle is None: raise HTTPException(status_code=404, detail="Vehicle not found") center = await session.get(ServiceCenter, service_center_id) if center and center.verification_status not in {"verified", "pending"}: raise HTTPException(status_code=403, detail="Service center is not allowed to create visits") visit = ServiceVisit( service_center_id=service_center_id, vehicle_id=payload.vehicle_id, created_by_employee_id=employee.id, visit_date=payload.visit_date, odometer=payload.odometer, notes=payload.notes, total_cost=payload.total_cost, currency=payload.currency, status="draft", ) session.add(visit) await log_audit(session, actor=current_user, action="service_visit.create", target_type="service_visit", metadata={"vehicle_id": payload.vehicle_id}) await session.commit() await session.refresh(visit) return visit @router.post("/{service_center_id}/vehicle-access/request") async def request_vehicle_access( service_center_id: int, payload: VehicleSearchRequest, session: AsyncSession = Depends(get_session), current_user: User = Depends(get_current_telegram_user), ) -> VehicleSearchResult: await ensure_service_employee(session, service_center_id, current_user, {"owner", "manager", "receptionist"}) stmt = select(Car) if payload.vin: stmt = stmt.where(Car.vin_normalized == payload.vin) elif payload.license_plate: stmt = stmt.where(Car.license_plate_normalized == payload.license_plate) if payload.country_code: stmt = stmt.where(Car.license_plate_country == payload.country_code.upper()) else: raise HTTPException(status_code=400, detail="license_plate or vin is required") vehicle = (await session.execute(stmt.limit(1))).scalar_one_or_none() await log_audit( session, actor=current_user, action="vehicle_access.request", target_type="vehicle", target_id=vehicle.id if vehicle else None, metadata={"service_center_id": service_center_id, "found": bool(vehicle)}, ) await session.commit() if vehicle is None: return VehicleSearchResult(access_status="not_found") return VehicleSearchResult( vehicle_id=vehicle.id, make=vehicle.make, model=vehicle.model, year=vehicle.year, masked_license_plate=mask_license_plate(vehicle.license_plate_display or vehicle.plate_number), masked_vin=mask_vin(vehicle.vin_normalized or vehicle.vin), access_status="request_logged", ) @router.post("/links", response_model=CarServiceLinkRead, status_code=status.HTTP_201_CREATED) async def link_car_to_service( payload: CarServiceLinkCreate, session: AsyncSession = Depends(get_session), x_internal_api_token: str | None = Header(default=None, alias="X-Internal-API-Token"), ) -> CarServiceLink: require_internal_api_token(x_internal_api_token) if await session.get(Car, payload.car_id) is None: raise HTTPException(status_code=404, detail="Car not found") if await session.get(ServiceCenter, payload.service_center_id) is None: raise HTTPException(status_code=404, detail="Service center not found") link = CarServiceLink(**payload.model_dump()) session.add(link) await session.commit() await session.refresh(link) return link @router.post("/inbox", response_model=ServiceInboxRead, status_code=status.HTTP_201_CREATED) async def receive_service_message( payload: ServiceInboxCreate, session: AsyncSession = Depends(get_session), x_internal_api_token: str | None = Header(default=None, alias="X-Internal-API-Token"), ) -> ServiceInboxMessage: require_internal_api_token(x_internal_api_token) service_center_id = payload.service_center_id if not service_center_id and payload.source_chat_id: result = await session.execute( select(ServiceCenter).where(ServiceCenter.telegram_chat_id == payload.source_chat_id) ) center = result.scalar_one_or_none() service_center_id = center.id if center else None message = ServiceInboxMessage( source_chat_id=payload.source_chat_id, raw_text=payload.raw_text, car_id=payload.car_id, service_center_id=service_center_id, parsed_status="pending", ) session.add(message) await session.commit() await session.refresh(message) return message