from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from starlette.requests import Request from app.core.rbac import require_any_permission, require_permission from app.db.database import get_db from app.models.repair import Repair from app.models.repair_estimate import RepairEstimate from app.models.user import User from app.repositories.repair_estimate_repository import RepairEstimateRepository from app.repositories.repair_repository import RepairRepository from app.schemas.repair_estimate import ( PublicEstimateDecisionRequest, PublicEstimateResponse, RepairEstimateCreate, RepairEstimateEventResponse, RepairEstimateResponse, RepairEstimateUpdate, ) from app.services.repair_estimate_service import RepairEstimateService from app.services.repair_public_link_service import RepairPublicLinkService router = APIRouter(tags=["Repair Estimates"]) def get_repair_or_404(db: Session, repair_id: int) -> Repair: repair = RepairRepository.get_by_id(db, repair_id) if repair is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Reparatur nicht gefunden") return repair def get_estimate_or_404(db: Session, repair_id: int, estimate_id: int) -> RepairEstimate: estimate = RepairEstimateRepository.get(db, repair_id=repair_id, estimate_id=estimate_id) if estimate is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kostenvoranschlag nicht gefunden") return estimate @router.get("/repairs/{repair_id}/estimates", response_model=list[RepairEstimateResponse]) def list_estimates( repair_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.read")), ): get_repair_or_404(db, repair_id) return RepairEstimateRepository.list_by_repair(db, repair_id) @router.post("/repairs/{repair_id}/estimates", response_model=RepairEstimateResponse, status_code=status.HTTP_201_CREATED) def create_estimate( repair_id: int, payload: RepairEstimateCreate, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.create")), ): repair = get_repair_or_404(db, repair_id) return RepairEstimateService.create(db, repair, payload, actor=current_user, request=request) @router.get("/repairs/{repair_id}/estimates/{estimate_id}", response_model=RepairEstimateResponse) def get_estimate( repair_id: int, estimate_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.read")), ): get_repair_or_404(db, repair_id) return get_estimate_or_404(db, repair_id, estimate_id) @router.put("/repairs/{repair_id}/estimates/{estimate_id}", response_model=RepairEstimateResponse) def update_estimate( repair_id: int, estimate_id: int, payload: RepairEstimateUpdate, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.update")), ): repair = get_repair_or_404(db, repair_id) estimate = get_estimate_or_404(db, repair_id, estimate_id) return RepairEstimateService.update(db, repair, estimate, payload, actor=current_user, request=request) @router.delete("/repairs/{repair_id}/estimates/{estimate_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_estimate( repair_id: int, estimate_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.delete")), ): repair = get_repair_or_404(db, repair_id) estimate = get_estimate_or_404(db, repair_id, estimate_id) RepairEstimateService.delete(db, repair, estimate, actor=current_user, request=request) return None @router.post("/repairs/{repair_id}/estimates/{estimate_id}/send", response_model=RepairEstimateResponse) def send_estimate( repair_id: int, estimate_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.send")), ): repair = get_repair_or_404(db, repair_id) estimate = get_estimate_or_404(db, repair_id, estimate_id) return RepairEstimateService.send(db, repair, estimate, actor=current_user, request=request) @router.post("/repairs/{repair_id}/estimates/{estimate_id}/cancel", response_model=RepairEstimateResponse) def cancel_estimate( repair_id: int, estimate_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_any_permission(["repair_estimates.update", "repair_estimates.send"])), ): repair = get_repair_or_404(db, repair_id) estimate = get_estimate_or_404(db, repair_id, estimate_id) return RepairEstimateService.cancel(db, repair, estimate, actor=current_user, request=request) @router.post("/repairs/{repair_id}/estimates/{estimate_id}/revoke", response_model=RepairEstimateResponse) def revoke_estimate( repair_id: int, estimate_id: int, request: Request, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.revoke")), ): repair = get_repair_or_404(db, repair_id) estimate = get_estimate_or_404(db, repair_id, estimate_id) return RepairEstimateService.revoke(db, repair, estimate, actor=current_user, request=request) @router.get("/repairs/{repair_id}/estimates/{estimate_id}/events", response_model=list[RepairEstimateEventResponse]) def list_estimate_events( repair_id: int, estimate_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_permission("repair_estimates.read")), ): estimate = get_estimate_or_404(db, repair_id, estimate_id) return estimate.events def get_public_repair_and_estimate(db: Session, token: str) -> tuple[Repair, RepairEstimate]: public_link = RepairPublicLinkService.get_public_link_or_404(db, token) repair = public_link.repair estimate = RepairEstimateRepository.get_latest_public(db, repair.id) if estimate is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Kostenvoranschlag nicht gefunden") RepairRepository.mark_public_link_used(db, public_link) return repair, estimate @router.post("/public/repairs/status/{token}/estimate/approve", response_model=PublicEstimateResponse) def approve_public_estimate( token: str, payload: PublicEstimateDecisionRequest, request: Request, db: Session = Depends(get_db), ): repair, estimate = get_public_repair_and_estimate(db, token) updated = RepairEstimateService.customer_decision(db, repair, estimate, "approve", payload, request=request) return RepairEstimateService.public_response(updated) @router.post("/public/repairs/status/{token}/estimate/decline", response_model=PublicEstimateResponse) def decline_public_estimate( token: str, payload: PublicEstimateDecisionRequest, request: Request, db: Session = Depends(get_db), ): repair, estimate = get_public_repair_and_estimate(db, token) updated = RepairEstimateService.customer_decision(db, repair, estimate, "decline", payload, request=request) return RepairEstimateService.public_response(updated) @router.post("/public/repairs/status/{token}/estimate/question", response_model=PublicEstimateResponse) def question_public_estimate( token: str, payload: PublicEstimateDecisionRequest, request: Request, db: Session = Depends(get_db), ): repair, estimate = get_public_repair_and_estimate(db, token) updated = RepairEstimateService.customer_decision(db, repair, estimate, "question", payload, request=request) return RepairEstimateService.public_response(updated)