feat: add Steuertags concept to separate workflow from content tags
Build and Push Multi-Platform Images / build-and-push (push) Successful in 38s

- New steuertag_ids setting to mark tags as workflow-only (not editable)
- DocumentEditModal shows only content tags (non-Steuertags) as editable chips
- Backend preserves Steuertags when saving document tag changes
- ManuellBearbeitenPage renders content tag chips under document title
- New Steuertags settings tab with multi-select and color preview

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 11:46:39 +02:00
parent dad0136365
commit d96e06e86d
8 changed files with 245 additions and 5 deletions
@@ -1,5 +1,5 @@
import { useEffect, useState, useCallback } from 'react';
import { Modal, Form, Select, DatePicker, Input, Spin, message, Row, Col, Button, Space, Divider } from 'antd';
import { Modal, Form, Select, DatePicker, Input, Spin, message, Row, Col, Button, Space, Divider, Tag } from 'antd';
import { PlusOutlined, EyeOutlined, SearchOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import { posteingangApi } from '../api/posteingang';
@@ -7,7 +7,7 @@ import type { DocumentRequirement, PosteingangDocument, Kontonummer } from '../a
import { clientsApi } from '../api/inbox';
import type { Client } from '../api/inbox';
import { paperlessApi } from '../api/paperless';
import type { PaperlessDocType, PaperlessCorrespondent } from '../api/paperless';
import type { PaperlessDocType, PaperlessCorrespondent, PaperlessTag } from '../api/paperless';
import { getEnv } from '../utils/env';
import { AuthIframe, openAuthUrl } from '../utils/auth-resource';
import DocumentSearchModal from './DocumentSearchModal';
@@ -35,6 +35,11 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
const [correspondents, setCorrespondents] = useState<PaperlessCorrespondent[]>([]);
const [requirements, setRequirements] = useState<DocumentRequirement[]>([]);
// Tags: alle Tags + als Steuertags markierte IDs; bearbeitbar sind nur Inhaltstags
const [allTags, setAllTags] = useState<PaperlessTag[]>([]);
const [steuertagIds, setSteuertagIds] = useState<number[]>([]);
const contentTags = allTags.filter(t => !steuertagIds.includes(t.id));
const [kontonummerMissing, setKontonummerMissing] = useState<{ correspondentId: number, nummer: string } | null>(null);
const [docTitles, setDocTitles] = useState<Record<number, string>>({});
const [searchModalOpen, setSearchModalOpen] = useState<{ field: string, reqId: number } | null>(null);
@@ -140,6 +145,14 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
}
}, [document, form, open]);
// Tags getrennt setzen: nur Inhaltstags (Steuertags werden nicht angezeigt/bearbeitet)
useEffect(() => {
if (open && document) {
const contentTagIds = (document.tags || []).filter(id => !steuertagIds.includes(id));
form.setFieldValue('tags', contentTagIds);
}
}, [document, open, steuertagIds, form]);
const ensureCorrespondentInList = async (correspondentId: number | null | undefined) => {
if (!correspondentId) return;
@@ -178,14 +191,18 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
const loadInitialData = async () => {
setLoading(true);
try {
const [clientsData, docTypesData, correspondentsData] = await Promise.all([
const [clientsData, docTypesData, correspondentsData, tagsData, steuertagData] = await Promise.all([
clientsApi.getMyClients(),
paperlessApi.getDocumentTypes(),
paperlessApi.getCorrespondents()
paperlessApi.getCorrespondents(),
paperlessApi.getTags(),
paperlessApi.getSteuertagIds(),
]);
setClients(clientsData);
setDocumentTypes(docTypesData);
setCorrespondents(correspondentsData);
setAllTags(tagsData);
setSteuertagIds(steuertagData);
// If document is already there, ensure its correspondent is in the list
if (document?.correspondent) {
@@ -258,6 +275,7 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
correspondent: values.correspondent,
title: values.title,
date: values.belegdatum ? values.belegdatum.format('YYYY-MM-DD') : null,
tags: values.tags || [],
customFields: customFieldsObj,
};
@@ -373,6 +391,36 @@ export default function DocumentEditModal({ documentId, document, open, onClose,
<DatePicker style={{ width: '100%' }} format="DD.MM.YYYY" />
</Form.Item>
<Form.Item name="tags" label="Tags">
<Select
mode="multiple"
allowClear
showSearch
optionFilterProp="label"
placeholder="Tags auswählen"
options={contentTags.map(t => ({ value: t.id, label: t.name }))}
tagRender={({ value, closable, onClose }) => {
const tag = allTags.find(t => t.id === value);
return (
<Tag
color={tag?.color}
style={{ color: tag?.text_color, marginInlineEnd: 4 }}
closable={closable}
onClose={onClose}
>
{tag?.name ?? value}
</Tag>
);
}}
optionRender={(opt) => {
const tag = contentTags.find(t => t.id === opt.value);
return tag ? (
<Tag color={tag.color} style={{ color: tag.text_color }}>{tag.name}</Tag>
) : opt.label;
}}
/>
</Form.Item>
{/* Rendering dynamic requirements based on DocumentType */}
{requirements.map(req => {
if (req.feldId === '2' || (req.isCustomField && req.customFieldIndex === 9)) {