feat: implement ProcessVerarbeiteteDocuments (Upload-Check)
Build and Push Multi-Platform Images / build-and-push (push) Successful in 37s

Ported ProcessVerarbeiteteDocuments() from C# ProcessUploads.cs:
- Checks docs tagged "hochgeladen" → eingangsrechnungVorhanden()
- On match: livesearch, update title/type/created/correspondent/tags,
  set custom fields (externeBelegnummer, AgrarmonitorLink), addNote
- Tag "hochgeladen" → "fertig" swap; owner via Client.AgrarmonitorBetriebId
- 401/403 guard: clearClient() + break (same pattern as runPolling)
- Cron: AGRARMONITOR_UPLOAD_CHECK_CRON (default: 0 * * * * *)
- New settings: agrarmonitor_tag_hochgeladen, agrarmonitor_link_field
- Endpoint: POST /api/agrarmonitor/process-uploads
- Frontend: polling-config extended with tagHochgeladen + linkField select,
  new card "Dokumenten-Verarbeitung" with run button + result display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 12:11:44 +02:00
parent a726f863f0
commit 8c5a81ed27
6 changed files with 312 additions and 11 deletions
+4
View File
@@ -199,6 +199,8 @@ export interface AgrarmonitorStatusData {
export interface AgrarmonitorPollingConfig {
tagFertig: string;
tagVerbucht: string;
tagHochgeladen: string;
linkField: string;
}
export interface AgrarmonitorPollingResult {
@@ -221,4 +223,6 @@ export const agrarmonitorApi = {
api.put<AgrarmonitorPollingConfig>('/api/agrarmonitor/polling-config', config).then((r) => r.data),
runPolling: () =>
api.post<AgrarmonitorPollingResult>('/api/agrarmonitor/run-polling').then((r) => r.data),
processUploads: () =>
api.post<AgrarmonitorPollingResult>('/api/agrarmonitor/process-uploads').then((r) => r.data),
};
+59 -1
View File
@@ -2291,9 +2291,12 @@ function AgrarmonitorTab() {
const [pollingConfigLoading, setPollingConfigLoading] = useState(false);
const [pollingSaving, setPollingSaving] = useState(false);
const [pollingRunning, setPollingRunning] = useState(false);
const [uploadCheckRunning, setUploadCheckRunning] = useState(false);
const [status, setStatus] = useState<AgrarmonitorStatusData | null>(null);
const [registerResult, setRegisterResult] = useState<{ success: boolean; message: string } | null>(null);
const [pollingResult, setPollingResult] = useState<AgrarmonitorPollingResult | null>(null);
const [uploadCheckResult, setUploadCheckResult] = useState<AgrarmonitorPollingResult | null>(null);
const [customFields, setCustomFields] = useState<PaperlessCustomField[]>([]);
const handleLoadStatus = async () => {
setLoading(true);
@@ -2341,7 +2344,10 @@ function AgrarmonitorTab() {
}
}, [pollingForm]);
useEffect(() => { handleLoadPollingConfig(); }, [handleLoadPollingConfig]);
useEffect(() => {
handleLoadPollingConfig();
paperlessApi.getCustomFields().then(setCustomFields).catch(() => {});
}, [handleLoadPollingConfig]);
const handleSavePollingConfig = async () => {
const values = await pollingForm.validateFields() as AgrarmonitorPollingConfig;
@@ -2369,6 +2375,19 @@ function AgrarmonitorTab() {
}
};
const handleProcessUploads = async () => {
setUploadCheckRunning(true);
setUploadCheckResult(null);
try {
const result = await agrarmonitorApi.processUploads();
setUploadCheckResult(result);
} catch {
message.error('Upload-Check fehlgeschlagen');
} finally {
setUploadCheckRunning(false);
}
};
const renderStatusTag = (value: boolean | null, labelTrue: string, labelFalse: string) => {
if (value === null) return <Tag></Tag>;
return value
@@ -2462,6 +2481,16 @@ function AgrarmonitorTab() {
>
<Input placeholder="9" style={{ width: 120 }} />
</Form.Item>
<Form.Item name="tagHochgeladen" label="Tag-ID: Hochgeladen in Agrarmonitor">
<Input placeholder="3" style={{ width: 120 }} />
</Form.Item>
<Form.Item name="linkField" label="Custom Field: Agrarmonitor-Link">
<Select allowClear placeholder="Kein Feld ausgewählt" style={{ width: 280 }}>
{customFields.map(f => (
<Select.Option key={f.id} value={String(f.id)}>{f.id}: {f.name}</Select.Option>
))}
</Select>
</Form.Item>
<Button type="primary" loading={pollingSaving} onClick={handleSavePollingConfig}>
Speichern
</Button>
@@ -2492,6 +2521,35 @@ function AgrarmonitorTab() {
)}
</Space>
</Card>
<Card size="small" title="Dokumenten-Verarbeitung">
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 12 }}>
Prüft Belege mit Tag "Hochgeladen in Agrarmonitor" und setzt den Tag auf "AMfertig",
sobald sie im Agrarmonitor-Buchungssystem erscheinen.
</Typography.Text>
<Space direction="vertical" style={{ width: '100%' }}>
<Button loading={uploadCheckRunning} onClick={handleProcessUploads}>
Jetzt prüfen
</Button>
{uploadCheckResult && (
<div>
<Tag color="blue">{uploadCheckResult.processed} geprüft</Tag>
<Tag color="success">{uploadCheckResult.updated} aktualisiert</Tag>
<Tag>{uploadCheckResult.skipped} übersprungen</Tag>
{uploadCheckResult.errors.length > 0 && (
<Tag color="error">{uploadCheckResult.errors.length} Fehler</Tag>
)}
{uploadCheckResult.errors.length > 0 && (
<ul style={{ marginTop: 8, paddingLeft: 20 }}>
{uploadCheckResult.errors.map((e, i) => (
<li key={i} style={{ color: '#ff4d4f' }}>{e}</li>
))}
</ul>
)}
</div>
)}
</Space>
</Card>
</Space>
</div>
);