fix(estimates): add cancel confirmation and permissions
This commit is contained in:
parent
663bdc41b1
commit
31bdc1047d
2 changed files with 21 additions and 6 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ export default function RepairEstimatesSection({
|
|||
const [saving, setSaving] = useState(false);
|
||||
const [pendingId, setPendingId] = useState<number | null>(null);
|
||||
const [deleteTarget, setDeleteTarget] = useState<RepairEstimate | null>(null);
|
||||
const [cancelTarget, setCancelTarget] = useState<RepairEstimate | null>(null);
|
||||
const [inventoryDialogOpen, setInventoryDialogOpen] = useState(false);
|
||||
const [inventoryItems, setInventoryItems] = useState<InventoryItem[]>([]);
|
||||
const [inventoryCategories, setInventoryCategories] = useState<InventoryCategory[]>([]);
|
||||
|
|
@ -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<RepairEstimate>(`/repairs/${repairId}/estimates/${estimate.id}/cancel`);
|
||||
await api.post<RepairEstimate>(`/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({
|
|||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{canUpdate && ["draft", "sent"].includes(estimate.status) && <Button type="button" variant="outline" size="sm" onClick={() => openEditDialog(estimate)}>Bearbeiten</Button>}
|
||||
{canSend && ["draft", "sent"].includes(estimate.status) && <Button type="button" size="sm" onClick={() => void sendEstimate(estimate)} disabled={pendingId === estimate.id || !customerEmail}><Send />Senden</Button>}
|
||||
{canUpdate && ["draft", "sent"].includes(estimate.status) && <Button type="button" variant="outline" size="sm" onClick={() => void cancelEstimate(estimate)} disabled={pendingId === estimate.id}><XCircle />Stornieren</Button>}
|
||||
{(canUpdate || canSend) && ["draft", "sent"].includes(estimate.status) && <Button type="button" variant="outline" size="sm" onClick={() => setCancelTarget(estimate)} disabled={pendingId === estimate.id}><XCircle />Stornieren</Button>}
|
||||
{canDelete && ["draft", "cancelled"].includes(estimate.status) && <Button type="button" variant="destructive" size="sm" onClick={() => setDeleteTarget(estimate)} disabled={pendingId === estimate.id}><Trash2 />Löschen</Button>}
|
||||
</div>
|
||||
</article>
|
||||
|
|
@ -606,6 +609,18 @@ export default function RepairEstimatesSection({
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(cancelTarget)}
|
||||
title="Kostenvoranschlag stornieren?"
|
||||
description="Der Kostenvoranschlag wird storniert. Reservierte Lagerartikel werden freigegeben."
|
||||
confirmLabel="Stornieren"
|
||||
pending={pendingId === cancelTarget?.id}
|
||||
onOpenChange={(open) => !open && setCancelTarget(null)}
|
||||
onConfirm={() => void cancelEstimate()}
|
||||
>
|
||||
{cancelTarget && <p className="text-sm text-slate-600">{cancelTarget.estimate_number} · {cancelTarget.title}</p>}
|
||||
</ConfirmDialog>
|
||||
|
||||
<ConfirmDialog
|
||||
open={Boolean(deleteTarget)}
|
||||
title="Kostenvoranschlag löschen?"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue