feat(buchhaltung): add edit support for pending transactions
This commit is contained in:
@@ -501,12 +501,14 @@ function TransaktionDialog({
|
|||||||
haushaltsjahre,
|
haushaltsjahre,
|
||||||
selectedJahrId,
|
selectedJahrId,
|
||||||
onSave,
|
onSave,
|
||||||
|
existing,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
haushaltsjahre: Haushaltsjahr[];
|
haushaltsjahre: Haushaltsjahr[];
|
||||||
selectedJahrId: number | null;
|
selectedJahrId: number | null;
|
||||||
onSave: (data: TransaktionFormData) => void;
|
onSave: (data: TransaktionFormData) => void;
|
||||||
|
existing?: Transaktion;
|
||||||
}) {
|
}) {
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
const [form, setForm] = useState<TransaktionFormData>({
|
const [form, setForm] = useState<TransaktionFormData>({
|
||||||
@@ -537,13 +539,32 @@ function TransaktionDialog({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) setForm(f => ({ ...f, haushaltsjahr_id: selectedJahrId || 0, datum: today }));
|
if (open) {
|
||||||
|
if (existing) {
|
||||||
|
setForm({
|
||||||
|
haushaltsjahr_id: existing.haushaltsjahr_id,
|
||||||
|
typ: existing.typ as 'einnahme' | 'ausgabe',
|
||||||
|
betrag: Number(existing.betrag),
|
||||||
|
datum: existing.datum.slice(0, 10),
|
||||||
|
konto_id: existing.konto_id,
|
||||||
|
bankkonto_id: existing.bankkonto_id,
|
||||||
|
beschreibung: existing.beschreibung || '',
|
||||||
|
empfaenger_auftraggeber: existing.empfaenger_auftraggeber || '',
|
||||||
|
verwendungszweck: existing.verwendungszweck || '',
|
||||||
|
beleg_nr: existing.beleg_nr || '',
|
||||||
|
bestellung_id: existing.bestellung_id,
|
||||||
|
ausgaben_typ: existing.ausgaben_typ,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setForm(f => ({ ...f, haushaltsjahr_id: selectedJahrId || 0, datum: today }));
|
||||||
|
}
|
||||||
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<DialogTitle>Neue Transaktion</DialogTitle>
|
<DialogTitle>{existing ? 'Transaktion bearbeiten' : 'Neue Transaktion'}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||||
<FormControl fullWidth required>
|
<FormControl fullWidth required>
|
||||||
@@ -609,7 +630,7 @@ function TransaktionDialog({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>Abbrechen</Button>
|
<Button onClick={onClose}>Abbrechen</Button>
|
||||||
<Button variant="contained" onClick={() => onSave(form)} disabled={!form.haushaltsjahr_id || !form.betrag || !form.datum}>Erstellen</Button>
|
<Button variant="contained" onClick={() => onSave(form)} disabled={!form.haushaltsjahr_id || !form.betrag || !form.datum}>{existing ? 'Speichern' : 'Erstellen'}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
@@ -1119,6 +1140,7 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
const { hasPermission } = usePermissionContext();
|
const { hasPermission } = usePermissionContext();
|
||||||
const [filters, setFilters] = useState<TransaktionFilters>({ haushaltsjahr_id: selectedJahrId || undefined });
|
const [filters, setFilters] = useState<TransaktionFilters>({ haushaltsjahr_id: selectedJahrId || undefined });
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
|
const [editingTx, setEditingTx] = useState<Transaktion | null>(null);
|
||||||
const [filterAusgabenTyp, setFilterAusgabenTyp] = useState('');
|
const [filterAusgabenTyp, setFilterAusgabenTyp] = useState('');
|
||||||
const [txSubTab, setTxSubTab] = useState(0);
|
const [txSubTab, setTxSubTab] = useState(0);
|
||||||
|
|
||||||
@@ -1150,6 +1172,12 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
onError: () => showError('Transaktion konnte nicht erstellt werden'),
|
onError: () => showError('Transaktion konnte nicht erstellt werden'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updateMut = useMutation({
|
||||||
|
mutationFn: ({ id, data }: { id: number; data: Partial<TransaktionFormData> }) => buchhaltungApi.updateTransaktion(id, data),
|
||||||
|
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); setEditingTx(null); showSuccess('Transaktion aktualisiert'); },
|
||||||
|
onError: () => showError('Aktualisierung fehlgeschlagen'),
|
||||||
|
});
|
||||||
|
|
||||||
const buchenMut = useMutation({
|
const buchenMut = useMutation({
|
||||||
mutationFn: (id: number) => buchhaltungApi.buchenTransaktion(id),
|
mutationFn: (id: number) => buchhaltungApi.buchenTransaktion(id),
|
||||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); qc.invalidateQueries({ queryKey: ['buchhaltung-stats'] }); showSuccess('Transaktion gebucht'); },
|
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); qc.invalidateQueries({ queryKey: ['buchhaltung-stats'] }); showSuccess('Transaktion gebucht'); },
|
||||||
@@ -1383,6 +1411,13 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
Stornieren
|
Stornieren
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{t.status === 'entwurf' && hasPermission('buchhaltung:edit') && (
|
||||||
|
<Tooltip title="Bearbeiten">
|
||||||
|
<IconButton size="small" onClick={() => setEditingTx(t)}>
|
||||||
|
<Edit fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
{t.status === 'entwurf' && hasPermission('buchhaltung:delete') && (
|
{t.status === 'entwurf' && hasPermission('buchhaltung:delete') && (
|
||||||
<Tooltip title="Löschen">
|
<Tooltip title="Löschen">
|
||||||
<IconButton size="small" color="error" onClick={() => deleteMut.mutate(t.id)}>
|
<IconButton size="small" color="error" onClick={() => deleteMut.mutate(t.id)}>
|
||||||
@@ -1413,6 +1448,15 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
onSave={data => createMut.mutate(data)}
|
onSave={data => createMut.mutate(data)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<TransaktionDialog
|
||||||
|
open={!!editingTx}
|
||||||
|
onClose={() => setEditingTx(null)}
|
||||||
|
haushaltsjahre={haushaltsjahre}
|
||||||
|
selectedJahrId={selectedJahrId}
|
||||||
|
existing={editingTx ?? undefined}
|
||||||
|
onSave={data => updateMut.mutate({ id: editingTx!.id, data })}
|
||||||
|
/>
|
||||||
|
|
||||||
<ErstattungDialog
|
<ErstattungDialog
|
||||||
open={erstattungOpen}
|
open={erstattungOpen}
|
||||||
onClose={() => setErstattungOpen(false)}
|
onClose={() => setErstattungOpen(false)}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query';
|
|||||||
import {
|
import {
|
||||||
Box, Typography, Button, Grid, Card, CardContent,
|
Box, Typography, Button, Grid, Card, CardContent,
|
||||||
Table, TableHead, TableBody, TableRow, TableCell,
|
Table, TableHead, TableBody, TableRow, TableCell,
|
||||||
LinearProgress, Chip, Alert, Skeleton, TableContainer, Paper,
|
Chip, Alert, Skeleton, TableContainer, Paper,
|
||||||
IconButton, CircularProgress,
|
IconButton, CircularProgress,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { ArrowBack, KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
|
import { ArrowBack, KeyboardArrowDown, KeyboardArrowUp } from '@mui/icons-material';
|
||||||
@@ -16,23 +16,13 @@ import type { AusgabenTyp, BuchhaltungAudit } from '../types/buchhaltung.types';
|
|||||||
const fmtEur = (n: number) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n);
|
const fmtEur = (n: number) => new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(n);
|
||||||
|
|
||||||
function BudgetCard({ label, budget, spent }: { label: string; budget: number; spent: number }) {
|
function BudgetCard({ label, budget, spent }: { label: string; budget: number; spent: number }) {
|
||||||
const utilization = budget > 0 ? Math.min((spent / budget) * 100, 100) : 0;
|
|
||||||
const over = spent > budget && budget > 0;
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="subtitle2" color="text.secondary">{label}</Typography>
|
<Typography variant="subtitle2" color="text.secondary">{label}</Typography>
|
||||||
<Typography variant="h6">{fmtEur(Number(spent))}</Typography>
|
<Typography variant="h6">{fmtEur(Number(spent))}</Typography>
|
||||||
{budget > 0 && (
|
{budget > 0 && (
|
||||||
<>
|
<Typography variant="body2" color="text.secondary">Budget: {fmtEur(Number(budget))}</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">Budget: {fmtEur(Number(budget))}</Typography>
|
|
||||||
<LinearProgress
|
|
||||||
variant="determinate"
|
|
||||||
value={utilization}
|
|
||||||
color={over ? 'error' : utilization > 80 ? 'warning' : 'primary'}
|
|
||||||
sx={{ mt: 1, height: 6, borderRadius: 3 }}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -204,7 +194,7 @@ export default function BuchhaltungKontoDetail() {
|
|||||||
const childIsEinfach = (child.budget_typ || 'detailliert') === 'einfach';
|
const childIsEinfach = (child.budget_typ || 'detailliert') === 'einfach';
|
||||||
const childTotal = childIsEinfach
|
const childTotal = childIsEinfach
|
||||||
? Number(child.budget_gesamt || 0)
|
? Number(child.budget_gesamt || 0)
|
||||||
: Number(child.budget_gwg) + Number(child.budget_anlagen) + Number(child.budget_instandhaltung);
|
: Number(child.budget_gwg || 0) + Number(child.budget_anlagen || 0) + Number(child.budget_instandhaltung || 0);
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
key={child.id}
|
key={child.id}
|
||||||
|
|||||||
Reference in New Issue
Block a user