fix: auto-select open fiscal year on load, derive sub-account number from parent, replace flat konten table with collapsible tree
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
InputLabel,
|
||||
LinearProgress,
|
||||
MenuItem,
|
||||
@@ -199,6 +200,25 @@ function KontoDialog({
|
||||
const empty: KontoFormData = { haushaltsjahr_id: haushaltsjahrId, kontonummer: '', bezeichnung: '', budget_gwg: 0, budget_anlagen: 0, budget_instandhaltung: 0, parent_id: null, notizen: '' };
|
||||
const [form, setForm] = useState<KontoFormData>(empty);
|
||||
|
||||
const selectedParent = konten.find(k => k.id === form.parent_id);
|
||||
const parentPrefix = selectedParent ? selectedParent.kontonummer : '';
|
||||
// suffix = part of kontonummer after parent prefix
|
||||
const kontonummerSuffix = form.kontonummer.startsWith(parentPrefix) ? form.kontonummer.slice(parentPrefix.length) : form.kontonummer;
|
||||
|
||||
const handleParentChange = (parentId: number | null) => {
|
||||
const parent = konten.find(k => k.id === parentId);
|
||||
const prefix = parent ? parent.kontonummer : '';
|
||||
// strip old prefix, re-apply new one
|
||||
const oldPrefix = selectedParent ? selectedParent.kontonummer : '';
|
||||
const suffix = form.kontonummer.startsWith(oldPrefix) ? form.kontonummer.slice(oldPrefix.length) : form.kontonummer;
|
||||
setForm(f => ({ ...f, parent_id: parentId, kontonummer: prefix + suffix }));
|
||||
};
|
||||
|
||||
const handleSuffixChange = (suffix: string) => {
|
||||
const digits = suffix.replace(/\D/g, '');
|
||||
setForm(f => ({ ...f, kontonummer: parentPrefix + digits }));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (existing) {
|
||||
setForm({ haushaltsjahr_id: haushaltsjahrId, konto_typ_id: existing.konto_typ_id ?? undefined, kontonummer: existing.kontonummer, bezeichnung: existing.bezeichnung, budget_gwg: existing.budget_gwg, budget_anlagen: existing.budget_anlagen, budget_instandhaltung: existing.budget_instandhaltung, parent_id: existing.parent_id, notizen: existing.notizen || '' });
|
||||
@@ -213,7 +233,17 @@ function KontoDialog({
|
||||
<DialogTitle>{existing ? 'Konto bearbeiten' : 'Neues Konto'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack spacing={2} sx={{ mt: 1 }}>
|
||||
<TextField label="Kontonummer" value={form.kontonummer} onChange={e => setForm(f => ({ ...f, kontonummer: e.target.value }))} required />
|
||||
<TextField
|
||||
label="Kontonummer"
|
||||
value={kontonummerSuffix}
|
||||
onChange={e => handleSuffixChange(e.target.value)}
|
||||
required
|
||||
inputProps={{ inputMode: 'numeric', pattern: '[0-9]*' }}
|
||||
InputProps={parentPrefix ? {
|
||||
startAdornment: <InputAdornment position="start">{parentPrefix}</InputAdornment>,
|
||||
} : undefined}
|
||||
helperText={parentPrefix ? `Vollständige Nummer: ${form.kontonummer || parentPrefix + '…'}` : undefined}
|
||||
/>
|
||||
<TextField label="Bezeichnung" value={form.bezeichnung} onChange={e => setForm(f => ({ ...f, bezeichnung: e.target.value }))} required />
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Kontotyp</InputLabel>
|
||||
@@ -226,7 +256,7 @@ function KontoDialog({
|
||||
<InputLabel>Elternkonto (optional)</InputLabel>
|
||||
<Select
|
||||
value={form.parent_id ?? ''}
|
||||
onChange={e => setForm(f => ({ ...f, parent_id: e.target.value ? Number(e.target.value) : null }))}
|
||||
onChange={e => handleParentChange(e.target.value ? Number(e.target.value) : null)}
|
||||
label="Elternkonto (optional)"
|
||||
>
|
||||
<MenuItem value=""><em>Kein Elternkonto</em></MenuItem>
|
||||
@@ -431,6 +461,50 @@ function KontoRow({ konto, depth = 0, onNavigate }: { konto: KontoTreeNode; dept
|
||||
);
|
||||
}
|
||||
|
||||
function KontoManageRow({ konto, depth = 0, canManage, onEdit, onDelete }: {
|
||||
konto: KontoTreeNode;
|
||||
depth?: number;
|
||||
canManage: boolean;
|
||||
onEdit: (k: Konto) => void;
|
||||
onDelete: (id: number) => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const totalBudget = Number(konto.budget_gwg) + Number(konto.budget_anlagen) + Number(konto.budget_instandhaltung);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableRow hover>
|
||||
<TableCell sx={{ pl: 2 + depth * 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
{konto.children.length > 0 ? (
|
||||
<IconButton size="small" onClick={() => setOpen(!open)}>
|
||||
{open ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
</IconButton>
|
||||
) : (
|
||||
<Box sx={{ width: 28 }} />
|
||||
)}
|
||||
<Typography variant="body2">{konto.kontonummer} — {konto.bezeichnung}</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>{konto.konto_typ_bezeichnung || '–'}</TableCell>
|
||||
<TableCell align="right">{fmtEur(konto.budget_gwg)}</TableCell>
|
||||
<TableCell align="right">{fmtEur(konto.budget_anlagen)}</TableCell>
|
||||
<TableCell align="right">{fmtEur(konto.budget_instandhaltung)}</TableCell>
|
||||
<TableCell align="right"><strong>{fmtEur(totalBudget)}</strong></TableCell>
|
||||
{canManage && (
|
||||
<TableCell>
|
||||
<IconButton size="small" onClick={() => onEdit(konto as unknown as Konto)}><Edit fontSize="small" /></IconButton>
|
||||
<IconButton size="small" color="error" onClick={() => onDelete(konto.id)}><Delete fontSize="small" /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
{open && konto.children.map(child => (
|
||||
<KontoManageRow key={child.id} konto={child} depth={depth + 1} canManage={canManage} onEdit={onEdit} onDelete={onDelete} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Tab 0: Übersicht ─────────────────────────────────────────────────────────
|
||||
|
||||
function UebersichtTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
@@ -907,11 +981,18 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
const [jahrDialog, setJahrDialog] = useState<{ open: boolean; existing?: Haushaltsjahr }>({ open: false });
|
||||
const [wiederkehrendDialog, setWiederkehrendDialog] = useState<{ open: boolean; existing?: WiederkehrendBuchung }>({ open: false });
|
||||
|
||||
const { data: konten = [] } = useQuery({
|
||||
const { data: kontenFlat = [] } = useQuery({
|
||||
queryKey: ['buchhaltung-konten', selectedJahrId],
|
||||
queryFn: () => buchhaltungApi.getKonten(selectedJahrId!),
|
||||
enabled: selectedJahrId != null,
|
||||
});
|
||||
const { data: kontenTreeData = [] } = useQuery({
|
||||
queryKey: ['kontenTree', selectedJahrId],
|
||||
queryFn: () => buchhaltungApi.getKontenTree(selectedJahrId!),
|
||||
enabled: selectedJahrId != null,
|
||||
});
|
||||
const kontenTree = buildTree(kontenTreeData);
|
||||
const konten = kontenFlat;
|
||||
const { data: bankkonten = [] } = useQuery({ queryKey: ['bankkonten'], queryFn: buchhaltungApi.getBankkonten });
|
||||
const { data: wiederkehrend = [] } = useQuery({ queryKey: ['buchhaltung-wiederkehrend'], queryFn: buchhaltungApi.getWiederkehrend });
|
||||
|
||||
@@ -1006,10 +1087,8 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Kontonummer</TableCell>
|
||||
<TableCell>Bezeichnung</TableCell>
|
||||
<TableCell>Konto</TableCell>
|
||||
<TableCell>Typ</TableCell>
|
||||
<TableCell>Elternkonto</TableCell>
|
||||
<TableCell align="right">GWG</TableCell>
|
||||
<TableCell align="right">Anlagen</TableCell>
|
||||
<TableCell align="right">Instandh.</TableCell>
|
||||
@@ -1018,24 +1097,15 @@ function KontenTab({ haushaltsjahre, selectedJahrId, onJahrChange }: {
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{konten.length === 0 && <TableRow><TableCell colSpan={canManage ? 9 : 8} align="center"><Typography color="text.secondary">Keine Konten</Typography></TableCell></TableRow>}
|
||||
{konten.map((k: Konto) => (
|
||||
<TableRow key={k.id} hover>
|
||||
<TableCell>{k.kontonummer}</TableCell>
|
||||
<TableCell>{k.bezeichnung}</TableCell>
|
||||
<TableCell>{k.konto_typ_bezeichnung || '–'}</TableCell>
|
||||
<TableCell>{k.parent_bezeichnung || '–'}</TableCell>
|
||||
<TableCell align="right">{fmtEur(k.budget_gwg)}</TableCell>
|
||||
<TableCell align="right">{fmtEur(k.budget_anlagen)}</TableCell>
|
||||
<TableCell align="right">{fmtEur(k.budget_instandhaltung)}</TableCell>
|
||||
<TableCell align="right">{fmtEur(k.budget_gwg + k.budget_anlagen + k.budget_instandhaltung)}</TableCell>
|
||||
{canManage && (
|
||||
<TableCell>
|
||||
<IconButton size="small" onClick={() => setKontoDialog({ open: true, existing: k })}><Edit fontSize="small" /></IconButton>
|
||||
<IconButton size="small" color="error" onClick={() => deleteKontoMut.mutate(k.id)}><Delete fontSize="small" /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
{kontenTree.length === 0 && <TableRow><TableCell colSpan={canManage ? 7 : 6} align="center"><Typography color="text.secondary">Keine Konten</Typography></TableCell></TableRow>}
|
||||
{kontenTree.map(k => (
|
||||
<KontoManageRow
|
||||
key={k.id}
|
||||
konto={k}
|
||||
canManage={canManage}
|
||||
onEdit={existing => setKontoDialog({ open: true, existing: existing as unknown as Konto })}
|
||||
onDelete={id => deleteKontoMut.mutate(id)}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@@ -1230,13 +1300,14 @@ export default function Buchhaltung() {
|
||||
const { data: haushaltsjahre = [] } = useQuery({
|
||||
queryKey: ['haushaltsjahre'],
|
||||
queryFn: buchhaltungApi.getHaushaltsjahre,
|
||||
onSuccess: (data: Haushaltsjahr[]) => {
|
||||
if (data.length > 0 && !selectedJahrId) {
|
||||
const openYear = data.find(hj => !hj.abgeschlossen) || data[0];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (haushaltsjahre.length > 0 && !selectedJahrId) {
|
||||
const openYear = haushaltsjahre.find((hj: Haushaltsjahr) => !hj.abgeschlossen) || haushaltsjahre[0];
|
||||
setSelectedJahrId(openYear.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
}, [haushaltsjahre]);
|
||||
|
||||
const { data: pendingCount } = useQuery({
|
||||
queryKey: ['buchhaltungPending', selectedJahrId],
|
||||
|
||||
Reference in New Issue
Block a user