diff --git a/src/admin/pages/InvoiceDetail.tsx b/src/admin/pages/InvoiceDetail.tsx
index 0c7adfa..6bad19f 100644
--- a/src/admin/pages/InvoiceDetail.tsx
+++ b/src/admin/pages/InvoiceDetail.tsx
@@ -1211,15 +1211,19 @@ export default function InvoiceDetail() {
Zobrazit fakturu
)}
- {hasPermission("invoices.delete") && (
-
- )}
+ {/* A paid invoice is a settled financial record — never deletable
+ from the UI. (Backend rejection of deleting a paid invoice is a
+ separate hardening, out of scope here.) */}
+ {hasPermission("invoices.delete") &&
+ invoice.status !== "paid" && (
+
+ )}
@@ -1778,15 +1782,20 @@ export default function InvoiceDetail() {
)}
))}
- {hasPermission("invoices.delete") && (
-
- )}
+ {/* A paid invoice is a settled financial record — never
+ deletable from the UI. A paid invoice never actually reaches
+ this branch (it renders the read-only view above), but the
+ guard documents the invariant explicitly. */}
+ {hasPermission("invoices.delete") &&
+ invoice.status !== "paid" && (
+
+ )}
>
)}
diff --git a/src/admin/pages/IssuedOrderDetail.tsx b/src/admin/pages/IssuedOrderDetail.tsx
index b2c86ed..4f13b68 100644
--- a/src/admin/pages/IssuedOrderDetail.tsx
+++ b/src/admin/pages/IssuedOrderDetail.tsx
@@ -540,6 +540,8 @@ export default function IssuedOrderDetail() {
status: string | null;
}>({ show: false, status: null });
const [pdfLoading, setPdfLoading] = useState(false);
+ const [deleteConfirm, setDeleteConfirm] = useState(false);
+ const [deleting, setDeleting] = useState(false);
// ─── Queries ───
const customersQuery = useQuery(offerCustomersOptions());
@@ -654,6 +656,12 @@ export default function IssuedOrderDetail() {
invalidate: ["issued-orders"],
});
+ const deleteMutation = useApiMutation({
+ url: () => `${API_BASE}/issued-orders/${id}`,
+ method: () => "DELETE",
+ invalidate: ["issued-orders"],
+ });
+
// ─── Item handlers ───
const updateItem = (
index: number,
@@ -768,6 +776,21 @@ export default function IssuedOrderDetail() {
}
};
+ // ─── Delete ───
+ const handleDelete = async () => {
+ setDeleting(true);
+ try {
+ await deleteMutation.mutateAsync(undefined);
+ alert.success("Objednávka byla smazána");
+ navigate("/orders?tab=vydane");
+ } catch (err) {
+ alert.error(err instanceof Error ? err.message : "Chyba připojení");
+ } finally {
+ setDeleting(false);
+ setDeleteConfirm(false);
+ }
+ };
+
// ─── PDF export ───
const handleViewPdf = async () => {
const newWindow = window.open("", "_blank");
@@ -901,6 +924,21 @@ export default function IssuedOrderDetail() {
)}
))}
+ {/* A completed order is terminal/finalized — never deletable from the
+ UI. draft/sent/confirmed/cancelled may be deleted. (Backend
+ rejection of deleting an order is a separate hardening, out of
+ scope here.) */}
+ {isEdit &&
+ hasPermission("orders.delete") &&
+ form.status !== "completed" && (
+
+ )}
@@ -1280,6 +1318,21 @@ export default function IssuedOrderDetail() {
}
/>
)}
+
+ {/* Delete confirm */}
+ {isEdit && (
+ setDeleteConfirm(false)}
+ onConfirm={handleDelete}
+ title="Smazat objednávku?"
+ message={`Opravdu chcete smazat objednávku "${poNumber}"? Tato akce je nevratná.`}
+ confirmText="Smazat"
+ cancelText="Zrušit"
+ confirmVariant="danger"
+ loading={deleting}
+ />
+ )}
);
}
diff --git a/src/admin/pages/OfferDetail.tsx b/src/admin/pages/OfferDetail.tsx
index 0e393ae..3dc36c7 100644
--- a/src/admin/pages/OfferDetail.tsx
+++ b/src/admin/pages/OfferDetail.tsx
@@ -663,6 +663,11 @@ export default function OfferDetail() {
const isLockedByOther = !!lockedBy;
const readOnly = isInvalidated || isLockedByOther || isCompleted;
const canInvalidate = isEdit && !isInvalidated && !isCompleted && !orderInfo;
+ // An offer is deletable only when it has NOT spawned an order (orderInfo —
+ // deleting would orphan the linked order), is NOT invalidated, and is NOT
+ // locked by another user. (Backend rejection of deleting an ordered offer is
+ // a separate hardening, out of scope here.)
+ const canDelete = isEdit && !orderInfo && !isInvalidated && !isLockedByOther;
// Sync offer detail data to form state on first load (edit mode)
const formInitializedRef = useRef(false);
@@ -1190,7 +1195,7 @@ export default function OfferDetail() {
{saving ? "Ukládání..." : "Uložit"}
)}
- {isEdit && hasPermission("offers.delete") && (
+ {canDelete && hasPermission("offers.delete") && (