fix(odin): gate Uložit on invoices.create + correct invoice invalidation key
Final review caught two minor issues: - saveInvoice invalidated ["received-invoices"], which matches no query (the received list/stats are keyed ["invoices","received",…]); use ["invoices"] so the list/stats actually refresh after an import. - The Save button hit /received-invoices (requires invoices.create) while Odin is gated only on ai.use; disable Uložit (with a hint) when the user lacks invoices.create so the UI never offers a 403 action. Dormant today (ai.use is admin-only and admin bypasses checks) but correct going forward. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import type { ReviewInvoice, EditableField } from "./types";
|
||||
interface InvoiceReviewCardProps {
|
||||
inv: ReviewInvoice;
|
||||
busy: boolean;
|
||||
canSave: boolean;
|
||||
onChange: (uid: string, field: EditableField, value: string) => void;
|
||||
onSave: (inv: ReviewInvoice) => void;
|
||||
onDiscard: (uid: string) => void;
|
||||
@@ -17,6 +18,7 @@ const fieldCols = { xs: "1fr", sm: "1fr 1fr" };
|
||||
export default function InvoiceReviewCard({
|
||||
inv,
|
||||
busy,
|
||||
canSave,
|
||||
onChange,
|
||||
onSave,
|
||||
onDiscard,
|
||||
@@ -96,7 +98,13 @@ export default function InvoiceReviewCard({
|
||||
|
||||
{/* Actions */}
|
||||
<Box sx={{ display: "flex", gap: 1, mt: 1.5 }}>
|
||||
<Button onClick={() => onSave(inv)} disabled={busy}>
|
||||
<Button
|
||||
onClick={() => onSave(inv)}
|
||||
disabled={busy || !canSave}
|
||||
title={
|
||||
!canSave ? "Nemáte oprávnění k uložení přijatých faktur" : undefined
|
||||
}
|
||||
>
|
||||
Uložit
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -4,6 +4,7 @@ import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import apiFetch from "../../utils/api";
|
||||
import { useAlert } from "../../context/AlertContext";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import {
|
||||
aiUsageOptions,
|
||||
aiConversationsOptions,
|
||||
@@ -61,6 +62,10 @@ function summaryNote(reviews: ReviewInvoice[]): string {
|
||||
export default function OdinChat() {
|
||||
const alert = useAlert();
|
||||
const qc = useQueryClient();
|
||||
const { hasPermission } = useAuth();
|
||||
// Saving an extracted invoice hits /received-invoices (needs invoices.create);
|
||||
// Odin itself only needs ai.use, so the Save affordance is gated separately.
|
||||
const canSave = hasPermission("invoices.create");
|
||||
const { data: usage } = useQuery(aiUsageOptions());
|
||||
const { data: convData, isPending: convPending } = useQuery(
|
||||
aiConversationsOptions(),
|
||||
@@ -304,7 +309,7 @@ export default function OdinChat() {
|
||||
if (!res.ok) throw new Error(body?.error || "Uložení selhalo");
|
||||
alert.success("Faktura uložena");
|
||||
setReview((r) => r.filter((x) => x.uid !== inv.uid));
|
||||
qc.invalidateQueries({ queryKey: ["received-invoices"] });
|
||||
qc.invalidateQueries({ queryKey: ["invoices"] });
|
||||
} catch (e) {
|
||||
alert.error(e instanceof Error ? e.message : "Uložení selhalo");
|
||||
} finally {
|
||||
@@ -445,6 +450,7 @@ export default function OdinChat() {
|
||||
key={inv.uid}
|
||||
inv={inv}
|
||||
busy={busy}
|
||||
canSave={canSave}
|
||||
onChange={patch}
|
||||
onSave={saveInvoice}
|
||||
onDiscard={(uid) =>
|
||||
|
||||
Reference in New Issue
Block a user