feat(repairs): add repair estimates
This commit is contained in:
parent
6e7e75f864
commit
4436fe5f73
25 changed files with 1802 additions and 9 deletions
|
|
@ -11,6 +11,7 @@ from app.models.audit import AuditLog
|
|||
from app.models.user import User
|
||||
from app.repositories.customer_repository import CustomerRepository
|
||||
from app.repositories.repair_repository import RepairRepository
|
||||
from app.repositories.repair_estimate_repository import RepairEstimateRepository
|
||||
from app.repositories.user_repository import UserRepository
|
||||
from app.schemas.dashboard import DashboardSummary, EmptyWidget, MetricCard, SystemStatusItem
|
||||
from app.services.system_settings_service import SystemSettingsService
|
||||
|
|
@ -73,6 +74,14 @@ def get_dashboard_summary(
|
|||
MetricCard(label="Reparaturdokumente", value=RepairRepository.count_documents(db)),
|
||||
]
|
||||
|
||||
if "repair_estimates.read" in permissions:
|
||||
repairs.extend([
|
||||
MetricCard(label="Offene KVs", value=RepairEstimateRepository.count_open(db)),
|
||||
MetricCard(label="Warten auf Freigabe", value=RepairEstimateRepository.count_waiting(db)),
|
||||
MetricCard(label="KVs freigegeben heute", value=RepairEstimateRepository.count_approved_today(db)),
|
||||
MetricCard(label="KVs abgelehnt", value=RepairEstimateRepository.count_declined(db)),
|
||||
])
|
||||
|
||||
if "system_settings.manage" in permissions:
|
||||
smtp_config = SystemSettingsService.get_smtp_runtime_config(db)
|
||||
public_links = SystemSettingsService.get_public_links_settings(db)
|
||||
|
|
|
|||
181
backend/hermes/app/api/repair_estimates.py
Normal file
181
backend/hermes/app/api/repair_estimates.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.core.rbac import 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_permission("repair_estimates.update")),
|
||||
):
|
||||
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.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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue