feat(repairs): add repair documents

This commit is contained in:
Schubert Ferenc 2026-07-05 00:34:57 +02:00
parent 884a20e043
commit 6e7e75f864
21 changed files with 1222 additions and 6 deletions

View file

@ -70,6 +70,7 @@ def get_dashboard_summary(
MetricCard(label="Statusmails heute", value=RepairRepository.count_status_mails_sent_today(db)),
MetricCard(label="Fehlgeschlagene Mails", value=RepairRepository.count_failed_status_mails(db)),
MetricCard(label="Offen ohne Kundenmail", value=RepairRepository.count_open_repairs_without_customer_email(db)),
MetricCard(label="Reparaturdokumente", value=RepairRepository.count_documents(db)),
]
if "system_settings.manage" in permissions:

View file

@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends, Header, HTTPException, Query, status
from fastapi import APIRouter, Depends, File, Form, Header, HTTPException, Query, UploadFile, status
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from starlette.requests import Request
from typing import cast
@ -11,6 +12,10 @@ from app.models.user import User
from app.repositories.repair_repository import RepairRepository
from app.schemas.repair import (
RepairCreate,
RepairDocumentResponse,
RepairDocumentType,
RepairDocumentUpdate,
RepairDocumentVisibility,
RepairIntakePayload,
RepairIntakeResponse,
RepairNotificationEventResponse,
@ -26,6 +31,7 @@ from app.schemas.repair import (
RepairUpdate,
)
from app.services.repair_notification_service import RepairNotificationService
from app.services.repair_document_service import RepairDocumentService
from app.services.repair_public_link_service import RepairPublicLinkService
from app.services.repair_service import RepairService
@ -130,6 +136,117 @@ def get_repair_history(
return RepairRepository.get_history(db, repair_id)
@router.get("/repairs/{repair_id}/documents", response_model=list[RepairDocumentResponse])
def list_repair_documents(
repair_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("repairs.read")),
):
get_repair_or_404(db, repair_id)
return RepairRepository.list_documents(db, repair_id)
@router.post("/repairs/{repair_id}/documents/upload", response_model=RepairDocumentResponse, status_code=status.HTTP_201_CREATED)
async def upload_repair_document(
repair_id: int,
request: Request,
file: UploadFile = File(...),
title: str = Form(..., min_length=1, max_length=255),
document_type: RepairDocumentType = Form("other"),
visibility: RepairDocumentVisibility = Form("internal"),
note: str | None = Form(None),
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("repairs.update")),
):
db_repair = get_repair_or_404(db, repair_id)
normalized_title = title.strip()
normalized_note = note.strip() if note else None
if not normalized_title:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Titel ist erforderlich")
return await RepairDocumentService.upload_document(
db,
db_repair,
file=file,
title=normalized_title,
document_type=document_type,
visibility=visibility,
note=normalized_note,
actor=current_user,
request=request,
)
@router.get("/repairs/{repair_id}/documents/{document_id}", response_model=RepairDocumentResponse)
def get_repair_document(
repair_id: int,
document_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("repairs.read")),
):
get_repair_or_404(db, repair_id)
document = RepairRepository.get_document(db, repair_id=repair_id, document_id=document_id)
if document is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dokument nicht gefunden")
return document
@router.put("/repairs/{repair_id}/documents/{document_id}", response_model=RepairDocumentResponse)
def update_repair_document(
repair_id: int,
document_id: int,
payload: RepairDocumentUpdate,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("repairs.update")),
):
db_repair = get_repair_or_404(db, repair_id)
document = RepairRepository.get_document(db, repair_id=repair_id, document_id=document_id)
if document is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dokument nicht gefunden")
return RepairDocumentService.update_document(db, db_repair, document, payload, actor=current_user, request=request)
@router.get("/repairs/{repair_id}/documents/{document_id}/download")
def download_repair_document(
repair_id: int,
document_id: int,
disposition: str = Query(default="inline", pattern="^(inline|attachment)$"),
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("repairs.read")),
):
get_repair_or_404(db, repair_id)
document = RepairRepository.get_document(db, repair_id=repair_id, document_id=document_id)
if document is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dokument nicht gefunden")
file_path = RepairDocumentService.open_document_file(document)
return FileResponse(
file_path,
media_type=document.mime_type or "application/octet-stream",
headers={
"Content-Disposition": RepairDocumentService.content_disposition(
document,
mode="attachment" if disposition == "attachment" else "inline",
),
},
)
@router.delete("/repairs/{repair_id}/documents/{document_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_repair_document(
repair_id: int,
document_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("repairs.update")),
):
db_repair = get_repair_or_404(db, repair_id)
document = RepairRepository.get_document(db, repair_id=repair_id, document_id=document_id)
if document is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Dokument nicht gefunden")
RepairDocumentService.delete_document(db, db_repair, document, actor=current_user, request=request)
return None
@router.get("/repairs/{repair_id}/public-link", response_model=RepairPublicLinkResponse)
def get_public_link(
repair_id: int,