refactor(buchhaltung): simplify transaction workflow to two states, reorder tabs, guard booking, add overview divider
This commit is contained in:
@@ -48,13 +48,10 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
ExpandLess as ExpandLessIcon,
|
ExpandLess as ExpandLessIcon,
|
||||||
ExpandMore as ExpandMoreIcon,
|
ExpandMore as ExpandMoreIcon,
|
||||||
HowToReg,
|
|
||||||
Lock,
|
Lock,
|
||||||
Save,
|
Save,
|
||||||
SwapHoriz,
|
SwapHoriz,
|
||||||
PictureAsPdf as PdfIcon,
|
PictureAsPdf as PdfIcon,
|
||||||
ThumbDown,
|
|
||||||
ThumbUp,
|
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
@@ -96,6 +93,7 @@ function fmtEur(val: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dividerLeft = { borderLeft: '2px solid', borderColor: 'divider' } as const;
|
const dividerLeft = { borderLeft: '2px solid', borderColor: 'divider' } as const;
|
||||||
|
const dividerRight = { borderRight: '2px solid', borderColor: 'divider' } as const;
|
||||||
|
|
||||||
function fmtDate(val: string) {
|
function fmtDate(val: string) {
|
||||||
return new Date(val).toLocaleDateString('de-DE');
|
return new Date(val).toLocaleDateString('de-DE');
|
||||||
@@ -656,7 +654,7 @@ function KontoRow({ konto, depth = 0, onNavigate }: { konto: KontoTreeNode; dept
|
|||||||
<TableCell align="right">{isEinfach ? '' : fmtEur(konto.spent_anlagen)}</TableCell>
|
<TableCell align="right">{isEinfach ? '' : fmtEur(konto.spent_anlagen)}</TableCell>
|
||||||
<TableCell align="right">{isEinfach ? '' : fmtEur(konto.spent_instandhaltung)}</TableCell>
|
<TableCell align="right">{isEinfach ? '' : fmtEur(konto.spent_instandhaltung)}</TableCell>
|
||||||
<TableCell align="right"><strong>{fmtEur(totalSpent)}</strong></TableCell>
|
<TableCell align="right"><strong>{fmtEur(totalSpent)}</strong></TableCell>
|
||||||
<TableCell align="right" sx={dividerLeft}>{fmtEur(konto.einnahmen_betrag)}</TableCell>
|
<TableCell align="right" sx={{ ...dividerLeft, ...dividerRight }}>{fmtEur(konto.einnahmen_betrag)}</TableCell>
|
||||||
<TableCell sx={{ width: 40, px: 0.5 }}>
|
<TableCell sx={{ width: 40, px: 0.5 }}>
|
||||||
{konto.children.length > 0 && (
|
{konto.children.length > 0 && (
|
||||||
<IconButton size="small" onClick={e => { e.stopPropagation(); setOpen(!open); }}>
|
<IconButton size="small" onClick={e => { e.stopPropagation(); setOpen(!open); }}>
|
||||||
@@ -816,7 +814,7 @@ function UebersichtTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
<TableCell align="right">Ausgaben Anlagen</TableCell>
|
<TableCell align="right">Ausgaben Anlagen</TableCell>
|
||||||
<TableCell align="right">Ausgaben Instandh.</TableCell>
|
<TableCell align="right">Ausgaben Instandh.</TableCell>
|
||||||
<TableCell align="right">Ausgaben Gesamt</TableCell>
|
<TableCell align="right">Ausgaben Gesamt</TableCell>
|
||||||
<TableCell align="right" sx={dividerLeft}>Einnahmen</TableCell>
|
<TableCell align="right" sx={{ ...dividerLeft, ...dividerRight }}>Einnahmen</TableCell>
|
||||||
<TableCell sx={{ width: 40 }} />
|
<TableCell sx={{ width: 40 }} />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@@ -861,7 +859,7 @@ function UebersichtTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
<TableCell align="right">{fmtEur(catSpentAnl)}</TableCell>
|
<TableCell align="right">{fmtEur(catSpentAnl)}</TableCell>
|
||||||
<TableCell align="right">{fmtEur(catSpentInst)}</TableCell>
|
<TableCell align="right">{fmtEur(catSpentInst)}</TableCell>
|
||||||
<TableCell align="right">{fmtEur(catSpentGwg + catSpentAnl + catSpentInst)}</TableCell>
|
<TableCell align="right">{fmtEur(catSpentGwg + catSpentAnl + catSpentInst)}</TableCell>
|
||||||
<TableCell align="right" sx={dividerLeft}>{fmtEur(catEinnahmen)}</TableCell>
|
<TableCell align="right" sx={{ ...dividerLeft, ...dividerRight }}>{fmtEur(catEinnahmen)}</TableCell>
|
||||||
<TableCell />
|
<TableCell />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
@@ -883,7 +881,7 @@ function UebersichtTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
<TableCell align="right">{fmtEur(sumSpentAnlagen)}</TableCell>
|
<TableCell align="right">{fmtEur(sumSpentAnlagen)}</TableCell>
|
||||||
<TableCell align="right">{fmtEur(sumSpentInst)}</TableCell>
|
<TableCell align="right">{fmtEur(sumSpentInst)}</TableCell>
|
||||||
<TableCell align="right">{fmtEur(sumSpentGesamt)}</TableCell>
|
<TableCell align="right">{fmtEur(sumSpentGesamt)}</TableCell>
|
||||||
<TableCell align="right" sx={dividerLeft}>{fmtEur(sumEinnahmen)}</TableCell>
|
<TableCell align="right" sx={{ ...dividerLeft, ...dividerRight }}>{fmtEur(sumEinnahmen)}</TableCell>
|
||||||
<TableCell />
|
<TableCell />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
@@ -1171,24 +1169,6 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
onError: () => showError('Löschen fehlgeschlagen'),
|
onError: () => showError('Löschen fehlgeschlagen'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const freigabeMut = useMutation({
|
|
||||||
mutationFn: (id: number) => buchhaltungApi.requestFreigabe(id),
|
|
||||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); showSuccess('Freigabe angefragt'); },
|
|
||||||
onError: () => showError('Freigabe konnte nicht angefragt werden'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const approveMut = useMutation({
|
|
||||||
mutationFn: (id: number) => buchhaltungApi.approveFreigabe(id),
|
|
||||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); qc.invalidateQueries({ queryKey: ['buchhaltung-stats'] }); showSuccess('Freigabe genehmigt'); },
|
|
||||||
onError: () => showError('Genehmigung fehlgeschlagen'),
|
|
||||||
});
|
|
||||||
|
|
||||||
const rejectMut = useMutation({
|
|
||||||
mutationFn: (id: number) => buchhaltungApi.rejectFreigabe(id),
|
|
||||||
onSuccess: () => { qc.invalidateQueries({ queryKey: ['buchhaltung-transaktionen'] }); showSuccess('Freigabe abgelehnt'); },
|
|
||||||
onError: () => showError('Ablehnung fehlgeschlagen'),
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Wiederkehrend mutations ──
|
// ── Wiederkehrend mutations ──
|
||||||
const canManage = hasPermission('buchhaltung:manage_accounts');
|
const canManage = hasPermission('buchhaltung:manage_accounts');
|
||||||
|
|
||||||
@@ -1263,7 +1243,7 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
|
|
||||||
const subTabTransaktionen = sortedTransaktionen.filter(t => {
|
const subTabTransaktionen = sortedTransaktionen.filter(t => {
|
||||||
if (txSubTab === 0) return t.status === 'entwurf';
|
if (txSubTab === 0) return t.status === 'entwurf';
|
||||||
if (txSubTab === 1) return t.status === 'gebucht' || t.status === 'freigegeben';
|
if (txSubTab === 1) return t.status === 'gebucht';
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1391,30 +1371,15 @@ function TransaktionenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<Stack direction="row" spacing={0.5} flexWrap="wrap">
|
<Stack direction="row" spacing={0.5} flexWrap="wrap">
|
||||||
{t.status === 'entwurf' && hasPermission('buchhaltung:edit') && (
|
{t.status === 'entwurf' && hasPermission('buchhaltung:edit') && (
|
||||||
<Tooltip title={!t.konto_id ? 'Kein Konto ausgewählt' : ''}>
|
<Tooltip title={kontenFlat.length === 0 ? 'Keine Konten konfiguriert' : bankkonten.length === 0 ? 'Keine Bankkonten konfiguriert' : !t.konto_id ? 'Kein Konto ausgewählt' : ''}>
|
||||||
<span>
|
<span>
|
||||||
<Button size="small" variant="outlined" startIcon={<BookmarkAdd fontSize="small" />} disabled={!t.konto_id} onClick={() => buchenMut.mutate(t.id)}>
|
<Button size="small" variant="outlined" startIcon={<BookmarkAdd fontSize="small" />} disabled={kontenFlat.length === 0 || bankkonten.length === 0 || !t.konto_id} onClick={() => buchenMut.mutate(t.id)}>
|
||||||
Buchen
|
Buchen
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{t.status === 'gebucht' && hasPermission('buchhaltung:edit') && (
|
{t.status === 'gebucht' && hasPermission('buchhaltung:edit') && (
|
||||||
<Button size="small" variant="outlined" color="info" startIcon={<HowToReg fontSize="small" />} onClick={() => freigabeMut.mutate(t.id)}>
|
|
||||||
Freigabe
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{t.status === 'freigegeben' && hasPermission('buchhaltung:manage_accounts') && (
|
|
||||||
<>
|
|
||||||
<Button size="small" variant="outlined" color="success" startIcon={<ThumbUp fontSize="small" />} onClick={() => approveMut.mutate(t.id)}>
|
|
||||||
Genehmigen
|
|
||||||
</Button>
|
|
||||||
<Button size="small" variant="outlined" color="error" startIcon={<ThumbDown fontSize="small" />} onClick={() => rejectMut.mutate(t.id)}>
|
|
||||||
Ablehnen
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{(t.status === 'gebucht' || t.status === 'freigegeben') && hasPermission('buchhaltung:edit') && (
|
|
||||||
<Button size="small" variant="outlined" color="warning" startIcon={<Cancel fontSize="small" />} onClick={() => stornoMut.mutate(t.id)}>
|
<Button size="small" variant="outlined" color="warning" startIcon={<Cancel fontSize="small" />} onClick={() => stornoMut.mutate(t.id)}>
|
||||||
Stornieren
|
Stornieren
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2140,20 +2105,20 @@ export default function Buchhaltung() {
|
|||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
<Tabs value={tabValue} onChange={handleTabChange} variant="scrollable" scrollButtons="auto">
|
<Tabs value={tabValue} onChange={handleTabChange} variant="scrollable" scrollButtons="auto">
|
||||||
|
<Tab label={<Badge badgeContent={pendingCount || 0} color="warning" invisible={!pendingCount}><span>Transaktionen Übersicht</span></Badge>} />
|
||||||
<Tab label="Übersicht" />
|
<Tab label="Übersicht" />
|
||||||
<Tab label={<Badge badgeContent={pendingCount || 0} color="warning" invisible={!pendingCount}><span>Transaktionen</span></Badge>} />
|
|
||||||
<Tab label="Konten" />
|
<Tab label="Konten" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
<TabPanel value={tabValue} index={0}>
|
<TabPanel value={tabValue} index={0}>
|
||||||
<UebersichtTab
|
<TransaktionenTab
|
||||||
haushaltsjahre={haushaltsjahre}
|
haushaltsjahre={haushaltsjahre}
|
||||||
selectedJahrId={selectedJahrId}
|
selectedJahrId={selectedJahrId}
|
||||||
onJahrChange={setSelectedJahrId}
|
onJahrChange={setSelectedJahrId}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tabValue} index={1}>
|
<TabPanel value={tabValue} index={1}>
|
||||||
<TransaktionenTab
|
<UebersichtTab
|
||||||
haushaltsjahre={haushaltsjahre}
|
haushaltsjahre={haushaltsjahre}
|
||||||
selectedJahrId={selectedJahrId}
|
selectedJahrId={selectedJahrId}
|
||||||
onJahrChange={setSelectedJahrId}
|
onJahrChange={setSelectedJahrId}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Lookup types
|
// Lookup types
|
||||||
export type KontoArt = 'einnahme' | 'ausgabe' | 'vermoegen' | 'verbindlichkeit';
|
export type KontoArt = 'einnahme' | 'ausgabe' | 'vermoegen' | 'verbindlichkeit';
|
||||||
export type TransaktionTyp = 'einnahme' | 'ausgabe' | 'transfer';
|
export type TransaktionTyp = 'einnahme' | 'ausgabe' | 'transfer';
|
||||||
export type TransaktionStatus = 'entwurf' | 'gebucht' | 'freigegeben' | 'storniert';
|
export type TransaktionStatus = 'entwurf' | 'gebucht' | 'storniert';
|
||||||
export type FreigabeStatus = 'ausstehend' | 'genehmigt' | 'abgelehnt';
|
export type FreigabeStatus = 'ausstehend' | 'genehmigt' | 'abgelehnt';
|
||||||
export type WiederkehrendIntervall = 'monatlich' | 'quartalsweise' | 'halbjaehrlich' | 'jaehrlich';
|
export type WiederkehrendIntervall = 'monatlich' | 'quartalsweise' | 'halbjaehrlich' | 'jaehrlich';
|
||||||
export type PlanungStatus = 'entwurf' | 'aktiv' | 'abgeschlossen';
|
export type PlanungStatus = 'entwurf' | 'aktiv' | 'abgeschlossen';
|
||||||
@@ -18,14 +18,12 @@ export const AUSGABEN_TYP_LABELS: Record<AusgabenTyp, string> = {
|
|||||||
export const TRANSAKTION_STATUS_LABELS: Record<TransaktionStatus, string> = {
|
export const TRANSAKTION_STATUS_LABELS: Record<TransaktionStatus, string> = {
|
||||||
entwurf: 'Entwurf',
|
entwurf: 'Entwurf',
|
||||||
gebucht: 'Gebucht',
|
gebucht: 'Gebucht',
|
||||||
freigegeben: 'Freigegeben',
|
|
||||||
storniert: 'Storniert',
|
storniert: 'Storniert',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TRANSAKTION_STATUS_COLORS: Record<TransaktionStatus, 'default' | 'warning' | 'success' | 'error'> = {
|
export const TRANSAKTION_STATUS_COLORS: Record<TransaktionStatus, 'default' | 'warning' | 'success' | 'error'> = {
|
||||||
entwurf: 'default',
|
entwurf: 'default',
|
||||||
gebucht: 'warning',
|
gebucht: 'warning',
|
||||||
freigegeben: 'success',
|
|
||||||
storniert: 'error',
|
storniert: 'error',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user