From 31bdc1047d838c9a5bccbd9c8dd73c5a43b43049 Mon Sep 17 00:00:00 2001 From: Schubert Ferenc Date: Sun, 5 Jul 2026 12:10:09 +0200 Subject: [PATCH] fix(estimates): add cancel confirmation and permissions --- backend/hermes/app/api/repair_estimates.py | 4 ++-- .../repairs/RepairEstimatesSection.tsx | 23 +++++++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/backend/hermes/app/api/repair_estimates.py b/backend/hermes/app/api/repair_estimates.py index 56ab0fb..1ec60f7 100644 --- a/backend/hermes/app/api/repair_estimates.py +++ b/backend/hermes/app/api/repair_estimates.py @@ -2,7 +2,7 @@ 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.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 @@ -117,7 +117,7 @@ def cancel_estimate( estimate_id: int, request: Request, db: Session = Depends(get_db), - current_user: User = Depends(require_permission("repair_estimates.update")), + 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) diff --git a/frontend/athena/components/repairs/RepairEstimatesSection.tsx b/frontend/athena/components/repairs/RepairEstimatesSection.tsx index 94de38e..d59e88e 100644 --- a/frontend/athena/components/repairs/RepairEstimatesSection.tsx +++ b/frontend/athena/components/repairs/RepairEstimatesSection.tsx @@ -183,6 +183,7 @@ export default function RepairEstimatesSection({ const [saving, setSaving] = useState(false); const [pendingId, setPendingId] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); + const [cancelTarget, setCancelTarget] = useState(null); const [inventoryDialogOpen, setInventoryDialogOpen] = useState(false); const [inventoryItems, setInventoryItems] = useState([]); const [inventoryCategories, setInventoryCategories] = useState([]); @@ -372,11 +373,13 @@ export default function RepairEstimatesSection({ } } - async function cancelEstimate(estimate: RepairEstimate) { - setPendingId(estimate.id); + async function cancelEstimate() { + if (!cancelTarget) return; + setPendingId(cancelTarget.id); try { - await api.post(`/repairs/${repairId}/estimates/${estimate.id}/cancel`); + await api.post(`/repairs/${repairId}/estimates/${cancelTarget.id}/cancel`); await loadEstimates(); + setCancelTarget(null); showToast({ type: "success", title: "Kostenvoranschlag storniert" }); } catch (err) { showToast({ type: "error", title: "Stornierung fehlgeschlagen", description: getErrorMessage(err) }); @@ -472,7 +475,7 @@ export default function RepairEstimatesSection({
{canUpdate && ["draft", "sent"].includes(estimate.status) && } {canSend && ["draft", "sent"].includes(estimate.status) && } - {canUpdate && ["draft", "sent"].includes(estimate.status) && } + {(canUpdate || canSend) && ["draft", "sent"].includes(estimate.status) && } {canDelete && ["draft", "cancelled"].includes(estimate.status) && }
@@ -606,6 +609,18 @@ export default function RepairEstimatesSection({ + !open && setCancelTarget(null)} + onConfirm={() => void cancelEstimate()} + > + {cancelTarget &&

{cancelTarget.estimate_number} · {cancelTarget.title}

} +
+