docs: Design und Plan „Duplikat → neue Version“
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc2d1cd4b7
commit
62d81dc25f
2 files changed
+512
No files matched your search
@@ -0,0 +1,287 @@
|
|||||||
|
# Duplikat → neue Version – Umsetzungsplan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Ein Beleg, dessen interne Belegnummer in Paperless schon existiert, wird nach einem Vergleichsdialog entweder als neue Version an das vorhandene Dokument gehängt oder verworfen – im E-Mail-Import und im Posteingang.
|
||||||
|
|
||||||
|
**Architecture:** Backend bekommt drei Bausteine im `paperless`-Modul: Belegnummer-Prüfung gegen Paperless (CF7 + ASN, auf Stammdokument normalisiert), Kennzahlen (Seiten/Wörter) und `DokumentVersionService` (Upload per `update_version` + Tag 21 „neue Version hochgeladen“ am Dokument; die Postprocessing-Regeln ignoriert der Nutzer per Regel-Konfiguration). E-Mail-Import und Inbox-Postprocessor nutzen sie; das Frontend bekommt eine gemeinsame Komponente `DuplikatVergleichModal`.
|
||||||
|
|
||||||
|
**Tech Stack:** NestJS 11, TypeORM/MariaDB, Jest, pdf-lib, pdf-parse (`PDFParse`), React 19 + Ant Design, Axios.
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-11-02-duplikat-neue-version-design.md`
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Sprache: UI-Texte, Kommentare, Commit-Messages auf Deutsch; Bezeichner in bestehender Konvention (gemischt dt./engl. wie im Umfeld).
|
||||||
|
- Maßgeblich für „Belegnummer existiert“ ist **Paperless**: CF7 = interne Belegnummer **und** ASN = Belegnummer ohne Bindestriche (`2026-000123` → `2026000123`).
|
||||||
|
- Treffer immer auf das Stammdokument normalisieren: `root_document ?? id`.
|
||||||
|
- „Als neue Version hinzufügen“ nur bei **genau einem** Stammdokument; sonst nur „Verwerfen“.
|
||||||
|
- Versions-Upload: `POST /api/documents/{id}/update_version/`, multipart `document` + `version_label` = `Korrektur vom TT.MM.JJJJ` (max. 64 Zeichen).
|
||||||
|
- Nach Versions-Upload bekommt das Stammdokument Tag **21** („neue Version hochgeladen“), vorhandene Tags bleiben. Keine eigene Webhook-Unterdrückung im Code.
|
||||||
|
- Checksummen-Prüfungen (`checkDuplicate`, `recheckDuplicates`, `sperreImporte`, Inbox-Schritt 6) bleiben unverändert.
|
||||||
|
- Task-Bearbeitung (`PaperlessTaskProcessorService`) bleibt unverändert; für Versionen wird **kein** Task angelegt.
|
||||||
|
- Produktions-Schema nur per Migration (`src/database/migrations/`), handgeschrieben wie `1782700000000-CreateWebhookQueue.ts`.
|
||||||
|
- Backend nach jeder Änderung: `npx eslint <Dateien>` und `npx prettier --check <Dateien>` grün für die geänderten Dateien; Frontend: `npm run lint` und `npm run build` grün.
|
||||||
|
|
||||||
|
## Review Focus
|
||||||
|
|
||||||
|
1. **Dokument hat Tag 21 schon** (zweite Korrektur) – Tag nicht doppelt setzen und bei fehlgeschlagenem Upload **nicht** entfernen. → Test in Task 4.
|
||||||
|
2. **Dokument hat schon Versionen**: CF7-/ASN-Suche liefert Stamm *und* Versionen → darf nicht als „mehrdeutig“ gelten. → Test in Task 2.
|
||||||
|
3. **E-Mail-Import: Benutzer geht nach der Entscheidung zurück und ändert die Belegnummer** – die alte Versions-Entscheidung darf nicht am neuen Wert kleben. → Abnahme in Task 7.
|
||||||
|
4. **Posteingang „Verwerfen“**: Aktionen *nach* PAPERLESS laufen, Aktionen *davor* nicht doppelt. → Test in Task 5.
|
||||||
|
5. **PDF ohne Textebene** (Scan): Wortzahl „nicht ermittelbar“ statt 0 bzw. falscher Abweichung. → Test in Task 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Live-Prüfung der Paperless-Versionsfunktion
|
||||||
|
|
||||||
|
Keine Code-Änderung. Ergebnisse als Abschnitt „Ergebnisse Live-Prüfung“ unten in die Spec schreiben. **Vor Schritt 2 den Nutzer fragen**, welches Testdokument verwendet werden darf (der Upload verändert ein echtes Dokument in Produktion).
|
||||||
|
|
||||||
|
- [ ] **Step 1:** `GET /api/documents/?archive_serial_number=<ASN eines Dokuments>` und `GET /api/documents/<id>/` abrufen; notieren, ob `root_document` und `versions` befüllt sind.
|
||||||
|
- [ ] **Step 2:** Mit Freigabe des Nutzers eine Test-PDF per `curl -F document=@test.pdf -F version_label="Test" .../api/documents/<id>/update_version/` hochladen. Notieren: Antwortformat (Task-UUID?), Ergebnis von `GET /api/tasks/?task_id=…` nach Abschluss (`result_data.document_id`?).
|
||||||
|
- [ ] **Step 3:** Danach notieren: Liefert die CF7-Suche (`custom_field_query=[7,"icontains","…"]`) und die ASN-Suche die Version als eigenes Dokument? Welche Werte haben `id`, `root_document`, `page_count`, `content` am Stamm (neueste Fassung?) und was liefert `/download/`?
|
||||||
|
- [ ] **Step 4:** Im Backend-Log nachsehen, ob und mit welcher Dokument-ID ein Webhook kam (nur zur Info; die Regeln filtern über Tag 21).
|
||||||
|
- [ ] **Step 5:** Testversion wieder löschen (`DELETE /api/documents/<id>/versions/<version_id>/`), Ergebnisse in die Spec schreiben, committen:
|
||||||
|
```bash
|
||||||
|
git add docs/superpowers/specs/2026-11-02-duplikat-neue-version-design.md
|
||||||
|
git commit -m "docs: Ergebnisse der Live-Prüfung Paperless-Versionen"
|
||||||
|
```
|
||||||
|
Weichen die Ergebnisse von den Annahmen in Task 2/4 ab (z. B. keine Task-ID, kein `root_document`), vor Task 2 den Plan anpassen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Belegnummer-Prüfung gegen Paperless
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `paperless-backend/src/paperless/belegnummer-pruefung.util.ts`
|
||||||
|
- Create: `paperless-backend/src/paperless/belegnummer-pruefung.util.spec.ts`
|
||||||
|
- Modify: `paperless-backend/src/paperless/paperless.service.ts` (neben `findDocumentIdByCustomField`/`findDocumentIdByAsn`)
|
||||||
|
- Modify: `paperless-backend/src/paperless/paperless.controller.ts`
|
||||||
|
- Modify: `paperless-frontend/src/api/paperless.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces:
|
||||||
|
- `export type BelegnummerStatus = 'frei' | 'eindeutig' | 'mehrdeutig'`
|
||||||
|
- `export interface BelegnummerPruefung { status: BelegnummerStatus; documentId: number | null; kandidaten: number[] }`
|
||||||
|
- `export function belegnummerZuAsn(belegnummer: string): number | null` – entfernt `-`, `parseInt`; `null` bei NaN oder leer.
|
||||||
|
- `export function klassifiziereTreffer(cf7RootIds: number[], asnRootIds: number[]): BelegnummerPruefung` – Vereinigung, dedupliziert, aufsteigend sortiert; 0 → frei, 1 → eindeutig (`documentId` = diese ID), >1 → mehrdeutig (`documentId: null`).
|
||||||
|
- `PaperlessService.findRootDocumentIdsByCustomField(fieldId: number, value: string): Promise<number[]>` – wie `findDocumentIdByCustomField` (gleiche case-insensitive Verifikation), aber alle Treffer, je Treffer `root_document ?? id`.
|
||||||
|
- `PaperlessService.findRootDocumentIdsByAsn(asn: number): Promise<number[]>` – analog zu `findDocumentIdByAsn`.
|
||||||
|
- `PaperlessService.pruefeBelegnummer(belegnummer: string): Promise<BelegnummerPruefung>`
|
||||||
|
- `GET /api/paperless/belegnummer-pruefung?belegnummer=2026-000123` → `BelegnummerPruefung`; `@RequirePermissions(Permission.VIEW_MAIL)` (wie die E-Mail-Import-Endpunkte).
|
||||||
|
- Frontend: `paperlessApi.pruefeBelegnummer(belegnummer: string): Promise<BelegnummerPruefung>` + exportierte Typen.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Failing tests** in `belegnummer-pruefung.util.spec.ts`:
|
||||||
|
```ts
|
||||||
|
expect(belegnummerZuAsn('2026-000123')).toBe(2026000123);
|
||||||
|
expect(belegnummerZuAsn('')).toBeNull();
|
||||||
|
expect(belegnummerZuAsn('abc')).toBeNull();
|
||||||
|
expect(klassifiziereTreffer([], [])).toEqual({ status: 'frei', documentId: null, kandidaten: [] });
|
||||||
|
expect(klassifiziereTreffer([42], [42])).toEqual({ status: 'eindeutig', documentId: 42, kandidaten: [42] });
|
||||||
|
expect(klassifiziereTreffer([42, 42], [])).toEqual({ status: 'eindeutig', documentId: 42, kandidaten: [42] }); // Review Focus 2: Stamm + Version
|
||||||
|
expect(klassifiziereTreffer([42], [7])).toEqual({ status: 'mehrdeutig', documentId: null, kandidaten: [7, 42] });
|
||||||
|
expect(klassifiziereTreffer([], [7])).toEqual({ status: 'eindeutig', documentId: 7, kandidaten: [7] });
|
||||||
|
```
|
||||||
|
- [ ] **Step 2:** `cd paperless-backend && npx jest src/paperless/belegnummer-pruefung.util.spec.ts` → FAIL (Modul fehlt).
|
||||||
|
- [ ] **Step 3:** Util implementieren; Service-Methoden und Endpoint ergänzen; Frontend-API-Methode ergänzen.
|
||||||
|
- [ ] **Step 4:** Test erneut → PASS; ESLint/Prettier für die geänderten Dateien grün.
|
||||||
|
- [ ] **Step 5: Commit** `feat(paperless): Belegnummer gegen Paperless prüfen (CF7 + ASN)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Kennzahlen für den Vergleich (Seiten, Wörter)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `paperless-backend/src/paperless/vergleich-kennzahlen.util.ts`
|
||||||
|
- Create: `paperless-backend/src/paperless/vergleich-kennzahlen.util.spec.ts`
|
||||||
|
- Modify: `paperless-backend/src/paperless/paperless.service.ts`
|
||||||
|
- Modify: `paperless-backend/src/inbox/inbox.controller.ts` (+ `inbox.service.ts`, falls Hilfsmethode nötig)
|
||||||
|
- Modify: `paperless-backend/src/email/email-import.controller.ts`, `email-import.service.ts`
|
||||||
|
- Modify: `paperless-frontend/src/api/inbox.ts`, `paperless-frontend/src/api/email-import.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces:
|
||||||
|
- `export interface Kennzahlen { seiten: number | null; woerter: number | null }`
|
||||||
|
- `export interface VergleichKennzahlen { vorhanden: Kennzahlen; neu: Kennzahlen }`
|
||||||
|
- `export function zaehleWoerter(text: string | null | undefined): number | null` – an `/\s+/` trennen, nur Tokens mit mind. einem Buchstaben oder einer Ziffer (`/[\p{L}\p{N}]/u`) zählen; Seitenmarker `-- N of M --` vorher entfernen; ergibt sich 0 → `null` („nicht ermittelbar“).
|
||||||
|
- `export async function kennzahlenAusPdf(pdf: Buffer): Promise<Kennzahlen>` – Seiten per `PDFDocument.load(..., { ignoreEncryption: true }).getPageCount()`, Text per `PDFParse` (Muster aus `email/pdf-fingerprint.util.ts`, `destroy()` im `finally`); Lesefehler → jeweils `null`.
|
||||||
|
- `PaperlessService.getDocumentKennzahlen(documentId: number): Promise<Kennzahlen>` – `page_count` und `zaehleWoerter(content)` aus `getDocument` (laut Task 1 neueste Fassung).
|
||||||
|
- `POST /api/inbox/:id/vergleich` Body `{ paperlessDocumentId: number; pages: number[] }` → `VergleichKennzahlen`; neue PDF über `inboxService.getSegmentPdfBuffer(id, user, pages)`.
|
||||||
|
- `POST /api/email-import/attachments/:attachmentId/vergleich` Body `{ paperlessDocumentId: number; pages?: { start: number; end: number } }` → `VergleichKennzahlen`; `@RequirePermissions(Permission.VIEW_MAIL)`; PDF aus `Content.Content1`, bei `pages` nur dieser Bereich (pdf-lib `copyPages` wie in `runImport`).
|
||||||
|
- Frontend: `inboxApi.vergleich(id, paperlessDocumentId, pages)`, `emailImportApi.vergleich(attachmentId, paperlessDocumentId, pages?)`, `emailImportApi.attachmentPdfBlob(attachmentId, pages?)` falls es noch keinen PDF-Endpunkt für Anhänge/Bereiche gibt (vorhanden ist `printPreview` – nur verwenden, wenn ohne Stempel möglich; sonst `GET /api/email-import/attachments/:attachmentId/pdf?start=&end=` ergänzen).
|
||||||
|
|
||||||
|
- [ ] **Step 1: Failing tests:**
|
||||||
|
```ts
|
||||||
|
expect(zaehleWoerter('Rechnung Nr. 123 – Betrag 4,50 €')).toBe(5); // "–" und "€" zählen nicht
|
||||||
|
expect(zaehleWoerter('-- 1 of 2 --')).toBeNull();
|
||||||
|
expect(zaehleWoerter('')).toBeNull();
|
||||||
|
expect(zaehleWoerter(null)).toBeNull();
|
||||||
|
// PDF mit Text (pdf-lib, StandardFonts.Helvetica, 2 Seiten, Text "Hallo Welt" auf Seite 1)
|
||||||
|
expect(await kennzahlenAusPdf(pdfMitText)).toEqual({ seiten: 2, woerter: 2 });
|
||||||
|
// PDF ohne Text (1 leere Seite) – Review Focus 5
|
||||||
|
expect(await kennzahlenAusPdf(leeresPdf)).toEqual({ seiten: 1, woerter: null });
|
||||||
|
expect(await kennzahlenAusPdf(Buffer.from('kaputt'))).toEqual({ seiten: null, woerter: null });
|
||||||
|
```
|
||||||
|
- [ ] **Step 2:** `npx jest src/paperless/vergleich-kennzahlen.util.spec.ts` → FAIL.
|
||||||
|
- [ ] **Step 3:** Util, Service-Methode, beide Endpunkte und Frontend-API implementieren.
|
||||||
|
- [ ] **Step 4:** Test → PASS; Lint/Prettier grün.
|
||||||
|
- [ ] **Step 5: Commit** `feat: Seiten- und Wortzahl für den Duplikatvergleich`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Versions-Upload mit Tag 21
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `paperless-backend/src/paperless/dokument-version.service.ts`
|
||||||
|
- Create: `paperless-backend/src/paperless/dokument-version.service.spec.ts`
|
||||||
|
- Modify: `paperless-backend/src/paperless/paperless.service.ts`, `paperless.module.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces:
|
||||||
|
- `PaperlessService.uploadDocumentVersion(documentId: number, filePath: string, versionLabel: string): Promise<string>` – multipart wie `uploadDocument`, Timeout 120 s, Rückgabe ohne Anführungszeichen.
|
||||||
|
- `export const NEUE_VERSION_TAG_ID = 21;`
|
||||||
|
- `export function versionLabel(datum: Date): string` → `Korrektur vom 02.11.2026`.
|
||||||
|
- `DokumentVersionService.haengeVersionAn(rootDocumentId: number, pdfPath: string): Promise<void>` – Reihenfolge:
|
||||||
|
1. Dokument lesen; hat es Tag 21 noch nicht, per `updateDocument(rootDocumentId, { tags })` bisherige Tags plus 21 setzen.
|
||||||
|
2. Danach `uploadDocumentVersion(rootDocumentId, pdfPath, versionLabel(new Date()))`.
|
||||||
|
3. Schlägt der Upload fehl **und** wurde Tag 21 in Schritt 1 neu gesetzt: Dokument erneut lesen, Tag 21 entfernen (`updateDocument` mit den übrigen Tags), danach den ursprünglichen Fehler weiterwerfen. Schlägt das Entfernen selbst fehl, nur loggen (`Tag 21 konnte nach fehlgeschlagenem Versions-Upload nicht entfernt werden`) und trotzdem den ursprünglichen Fehler werfen.
|
||||||
|
Exportiert aus `PaperlessModule`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Failing tests** (`PaperlessService` gemockt):
|
||||||
|
- `versionLabel(new Date(2026, 10, 2))` → `'Korrektur vom 02.11.2026'`.
|
||||||
|
- Tags `[3, 17]`: `updateDocument(42, { tags: [3, 17, 21] })` wird **vor** `uploadDocumentVersion(42, '/tmp/x.pdf', <Label>)` aufgerufen (Reihenfolge über `mock.invocationCallOrder` prüfen).
|
||||||
|
- Tags `[21, 5]`: kein `updateDocument`, Upload läuft (Review Focus 1).
|
||||||
|
- Upload wirft, Tags vorher `[3]`: nach dem Setzen `[3, 21]` wird beim zweiten Lesen `[3, 21]` geliefert → `updateDocument(42, { tags: [3] })`; `haengeVersionAn` wirft den ursprünglichen Fehler.
|
||||||
|
- Upload wirft, Tag 21 war schon vorher da: Tag wird **nicht** entfernt, Fehler wird geworfen.
|
||||||
|
- Upload wirft und das Entfernen wirft ebenfalls: der **Upload**-Fehler wird geworfen.
|
||||||
|
- [ ] **Step 2:** `npx jest src/paperless/dokument-version.service.spec.ts` → FAIL.
|
||||||
|
- [ ] **Step 3:** Service und Upload-Methode implementieren.
|
||||||
|
- [ ] **Step 4:** Test → PASS; `npm run build` im Backend grün; Lint/Prettier grün.
|
||||||
|
- [ ] **Step 5: Commit** `feat(paperless): Belege als neue Version anhängen und mit Tag 21 markieren`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Posteingang – Entscheidung im Inbox-Postprocessor
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `paperless-backend/src/inbox-postprocessor/paperless-entscheidung.util.ts` (+ `.spec.ts`)
|
||||||
|
- Modify: `paperless-backend/src/inbox-postprocessor/inbox-postprocessor.service.ts` (`runForDocument`, `runPaperless`, Interfaces oben)
|
||||||
|
- Modify: `paperless-backend/src/inbox-postprocessor/inbox-postprocessor.module.ts` (falls Provider fehlt)
|
||||||
|
- Modify: `paperless-backend/src/inbox/inbox.controller.ts` (`postprocess`-Body)
|
||||||
|
- Modify: `paperless-frontend/src/api/inbox.ts` (Typen `postprocess`)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `PaperlessService.pruefeBelegnummer` (Task 2), `DokumentVersionService.haengeVersionAn` (Task 4).
|
||||||
|
- Produces:
|
||||||
|
- `export type PaperlessEntscheidung = { aktion: 'version'; documentId: number } | { aktion: 'verwerfen' }`
|
||||||
|
- `export function aktionenAbEntscheidung<T extends { ActionType: string }>(aktionen: T[], entscheidung?: PaperlessEntscheidung): T[]` – ohne Entscheidung alle; mit Entscheidung alle ab der ersten `PAPERLESS`-Aktion (die davor liefen schon); gibt es keine `PAPERLESS`-Aktion, alle.
|
||||||
|
- `ActionResult` ergänzt um `versionMoeglich?: boolean` und `duplikatKandidaten?: number[]`; `duplicateOfDocumentId` = eindeutige Stamm-ID bzw. erster Kandidat.
|
||||||
|
- `runForDocument(documentId, preferredUsername, sectionOffset = 0, processOnlyOne = false, entscheidung?: PaperlessEntscheidung)` – ersetzt den Parameter `replaceDuplicate`.
|
||||||
|
- Controller-Body: `{ sectionOffset?: number; processOnlyOne?: boolean; entscheidung?: PaperlessEntscheidung }` (`replaceDuplicate` entfällt).
|
||||||
|
- Verhalten der PAPERLESS-Aktion:
|
||||||
|
- `entscheidung.aktion === 'verwerfen'` → Aktion übersprungen, Ergebnis `{ ok: true, skipped: true, message: 'Verworfen' }`, **kein** Abbruch; folgende Aktionen laufen.
|
||||||
|
- `entscheidung.aktion === 'version'` → Belegnummer/ASN wie heute auflösen, keine Duplikatprüfung, `haengeVersionAn(entscheidung.documentId, sectionPdfPath)`, kein Task; Ergebnis `{ ok: true, message: 'Als neue Version zu Dokument X hinzugefügt' }`; folgende Aktionen laufen.
|
||||||
|
- ohne Entscheidung: Duplikatprüfung in dieser Reihenfolge: (1) `pruefeBelegnummer(interneBelegnummer)` – `eindeutig` → `skipped`, `versionMoeglich: true`; `mehrdeutig` → `skipped`, `versionMoeglich: false`, `duplikatKandidaten`; (2) lokaler Task (wie heute, `versionMoeglich: false`); (3) Checksumme (unverändert). Die bisherigen Einzelprüfungen CF7 und ASN (Schritte 4/5) entfallen zugunsten von (1). Bei `skipped` Abbruch wie heute.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Failing tests** für `aktionenAbEntscheidung` mit `[MAIL, PAPERLESS, EXPORT]`:
|
||||||
|
- ohne Entscheidung → alle drei;
|
||||||
|
- `{ aktion: 'verwerfen' }` → `[PAPERLESS, EXPORT]` (Review Focus 4: MAIL nicht doppelt, EXPORT läuft);
|
||||||
|
- `{ aktion: 'version', documentId: 1 }` → `[PAPERLESS, EXPORT]`;
|
||||||
|
- `[MAIL, EXPORT]` mit Entscheidung → beide.
|
||||||
|
- [ ] **Step 2:** `npx jest src/inbox-postprocessor/paperless-entscheidung.util.spec.ts` → FAIL.
|
||||||
|
- [ ] **Step 3:** Util implementieren, `runForDocument`/`runPaperless`/Controller/Frontend-Typen umstellen.
|
||||||
|
- [ ] **Step 4:** Test → PASS; `npm run build` und `npm test` im Backend (bekannte, schon vorher rote Specs `pdf-fingerprint.util.spec.ts`, `attachment-fingerprint.service.spec.ts` ausgenommen) grün.
|
||||||
|
- [ ] **Step 5: Commit** `feat(posteingang): Duplikat als neue Version oder verwerfen`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: E-Mail-Import – Version beim Import hochladen
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `paperless-backend/src/email/email-import.service.ts` (Typen von `executeImport`/`runImport`, Upload-Schleife)
|
||||||
|
- Modify: `paperless-backend/src/email/email-import.service.spec.ts`
|
||||||
|
- Modify: `paperless-backend/src/email/email.module.ts` (falls Provider fehlt)
|
||||||
|
- Modify: `paperless-frontend/src/api/email-import.ts` (`AttachmentImportData.versionZuDokumentId?: number | null`)
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `DokumentVersionService.haengeVersionAn` (Task 4).
|
||||||
|
- Produces: Anhang-Eintrag in `executeImport` akzeptiert `versionZuDokumentId?: number | null`. Ist er gesetzt:
|
||||||
|
- kein `validateAsnNotExists`, kein `uploadDocument`, kein Task, kein Task-Polling, kein `setBelegnummer`;
|
||||||
|
- je Upload-Datei `haengeVersionAn(versionZuDokumentId, uploadItem.path)`; `paperlessIds[rangeKey] = versionZuDokumentId`;
|
||||||
|
- `ImportStatus = 1`, `InterneBelegnummer` wie bisher am Attachment gesetzt;
|
||||||
|
- Ergebnis enthält `{ attachmentId, paperlessIds, alsVersion: true }`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Failing test** in `email-import.service.spec.ts`, neuer `describe('Import als Version')` mit dem bestehenden Testmodul (+ Provider `DokumentVersionService` gemockt), Anhang mit `versionZuDokumentId: 42`, `belegnummer: '2026-000123'`, `barcode.nummer: '000123'`:
|
||||||
|
- `dokumentVersionService.haengeVersionAn` einmal mit `42` und einem `.pdf`-Pfad aufgerufen;
|
||||||
|
- `paperlessService.uploadDocument`, `paperlessService.validateAsnNotExists` und `taskRepo.save` **nicht** aufgerufen;
|
||||||
|
- gespeichertes Attachment hat `PaperlessDocumentIds` `{ full: 42 }` und `ImportStatus: 1`.
|
||||||
|
- [ ] **Step 2:** `npx jest src/email/email-import.service.spec.ts` → neuer Test FAIL.
|
||||||
|
- [ ] **Step 3:** Zweig in `runImport` implementieren (eigene private Methode, damit die Upload-Schleife lesbar bleibt).
|
||||||
|
- [ ] **Step 4:** Test → PASS, übrige Tests der Datei weiter grün; Lint/Prettier grün.
|
||||||
|
- [ ] **Step 5: Commit** `feat(mail): Beleg beim Import als neue Version anhängen`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Frontend – gemeinsamer Vergleichsdialog, E-Mail-Import und Posteingang
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `paperless-frontend/src/components/DuplikatVergleichModal.tsx`
|
||||||
|
- Modify: `paperless-frontend/src/pages/InboxDetailPage.tsx` (`CompareModal` entfernen, Wizard umstellen)
|
||||||
|
- Modify: `paperless-frontend/src/components/MailImportWizard.tsx`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Frontend-APIs aus Task 2, 3, 5, 6.
|
||||||
|
- Produces: `DuplikatVergleichModal` mit Props
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
open: boolean;
|
||||||
|
belegnummer: string;
|
||||||
|
pruefung: BelegnummerPruefung | null; // eindeutig → beide Knöpfe; mehrdeutig → nur Verwerfen + Hinweis
|
||||||
|
paperlessDocumentId: number | null; // linke Seite
|
||||||
|
ladeNeuesPdf: () => Promise<Blob>; // rechte Seite
|
||||||
|
ladeKennzahlen: () => Promise<VergleichKennzahlen>;
|
||||||
|
onVersion: () => void;
|
||||||
|
onVerwerfen: () => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Titel `Belegnummer ${belegnummer} existiert bereits`. Auffälligkeiten-Leiste über den PDFs:
|
||||||
|
- je Kennzahl eine Zeile `Seiten: 2 (vorhanden) → 3 (neu)` bzw. `Wörter: 412 → 398`; Abweichung als orange `Tag`, `null` als „nicht ermittelbar“ (grau, keine Abweichung);
|
||||||
|
- keine Abweichung und nichts `null` → `Alert type="success"` „Seiten- und Wortzahl gleich“;
|
||||||
|
- mehrdeutig → `Alert type="warning"` „Mehrere Dokumente in Paperless mit dieser Belegnummer (IDs: …) – eine neue Version ist nicht möglich.“
|
||||||
|
- Knöpfe: `Verwerfen` und primär `Als neue Version hinzufügen` (nur bei `eindeutig`). Layout der PDFs wie bisher (`flex`, mobil untereinander, `75vh`), Object-URLs beim Schließen freigeben.
|
||||||
|
|
||||||
|
**Posteingang (`PostprocessWizardModal`):**
|
||||||
|
- `processSection(idx, entscheidung?: PaperlessEntscheidung)`; Ergebnis mit `versionMoeglich` oder `duplikatKandidaten` öffnet den Dialog automatisch (`setCompareForSection(idx)`).
|
||||||
|
- Zeilen-Knöpfe bei Duplikat: `Vergleichen` (öffnet Dialog, nur wenn eine Dokument-ID vorliegt) und `Verwerfen`; „Neue Version erstellen“ (alter `replaceDuplicate`-Weg) entfällt.
|
||||||
|
- `onVersion` → `processSection(idx, { aktion: 'version', documentId })`; `onVerwerfen` und Zeilen-„Verwerfen“ → `processSection(idx, { aktion: 'verwerfen' })`; danach Abschnitt-Status `skipped` mit Meldung „Verworfen“ bzw. `success`.
|
||||||
|
- Rechte Seite: `inboxApi.downloadSegmentBlob(documentId, documents[idx].pages)`; Kennzahlen: `inboxApi.vergleich(documentId, paperlessDocumentId, documents[idx].pages)`.
|
||||||
|
|
||||||
|
**E-Mail-Import (`MailImportWizard`):**
|
||||||
|
- State `versionEntscheidung: Record<virtualId, { belegnummer: string; documentId: number }>`.
|
||||||
|
- In `nextStep` bei `currentStep === 1`, nach `recheckDuplicates` und vor `preFetchBelegnummern`: für jedes nicht ignorierte Item im Modus `manuell` die Belegnummer `${jahr}-${nummer.padStart(6,'0')}` bilden und `paperlessApi.pruefeBelegnummer` aufrufen; eine vorhandene Entscheidung gilt nur, wenn ihre `belegnummer` gleich ist (sonst verwerfen – Review Focus 3).
|
||||||
|
- Treffer (`eindeutig`/`mehrdeutig`) ohne gültige Entscheidung nacheinander im Dialog zeigen; Schrittwechsel erst, wenn alle entschieden sind. Schließen ohne Entscheidung → Warteschlange abbrechen, in Schritt 2 bleiben.
|
||||||
|
- `onVersion` → Entscheidung speichern; `onVerwerfen` → Item auf `type: 'IGNORE'`; sind danach alle Items `IGNORE` → `ignoreEmail()` statt Schrittwechsel.
|
||||||
|
- Rechte Seite: PDF des Anhangs bzw. Bereichs (Task 3), Kennzahlen: `emailImportApi.vergleich(...)`.
|
||||||
|
- `executeImport`: `versionZuDokumentId` aus gültiger Entscheidung in `finalData` übernehmen.
|
||||||
|
- In Schritt 3 („Abschluss“) bei Version-Items einen Hinweis „wird als neue Version zu Dokument X hinzugefügt“ anzeigen.
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Komponente bauen, `CompareModal` in `InboxDetailPage.tsx` ersetzen und Posteingang umstellen.
|
||||||
|
- [ ] **Step 2:** `MailImportWizard` umstellen.
|
||||||
|
- [ ] **Step 3:** `cd paperless-frontend && npm run lint && npm run build` → beide ohne Fehler.
|
||||||
|
- [ ] **Step 4: Abnahme im Browser** (Dev-Server, Backend gegen Paperless), nur mit Freigabe des Nutzers für echte Uploads:
|
||||||
|
- Mail mit manueller, vorhandener Belegnummer → Dialog erscheint vor Schritt 3, Auffälligkeiten stimmen; „Verwerfen“ stellt auf Ignorieren; „Als neue Version“ → Import hängt Version an (in Paperless sichtbar, kein neues Dokument, Tag 21 gesetzt).
|
||||||
|
- Zurück, Belegnummer ändern, Weiter → alte Entscheidung greift nicht (Review Focus 3).
|
||||||
|
- Posteingang-Abschnitt mit vorhandener Belegnummer → Dialog öffnet automatisch; beide Entscheidungen; Aktionen nach PAPERLESS laufen.
|
||||||
|
- [ ] **Step 5: Commit** `feat(frontend): Vergleichsdialog für Duplikate mit neuer Version`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 8: Todos und Abschluss
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/todos.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1:** Die drei Einträge aus dem Spec-Abschnitt „Todos-Einträge“ wörtlich als `- [ ]`-Punkte an `docs/todos.md` anhängen.
|
||||||
|
- [ ] **Step 2:** Backend `npm run build`, `npm test` (bekannte rote Specs ausgenommen), Frontend `npm run build` – alles grün.
|
||||||
|
- [ ] **Step 3: Commit** `docs: offene Punkte zu Versionen und Task-Bearbeitung`
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
# Duplikat → neue Version (Design)
|
||||||
|
|
||||||
|
Stand: 02.11.2026
|
||||||
|
|
||||||
|
## Worum es geht
|
||||||
|
|
||||||
|
Wird ein Beleg ein zweites Mal mit derselben **internen Belegnummer** nach
|
||||||
|
Paperless gebracht, soll er nicht als zweites, eigenständiges Dokument landen.
|
||||||
|
Stattdessen sieht der Benutzer beide Belege nebeneinander und entscheidet:
|
||||||
|
|
||||||
|
- **Als neue Version hinzufügen** – die Datei wird über die Versionsfunktion
|
||||||
|
von Paperless-NGX an das vorhandene Dokument gehängt.
|
||||||
|
- **Verwerfen** – der Beleg wird nicht importiert.
|
||||||
|
|
||||||
|
Typische Fälle:
|
||||||
|
|
||||||
|
1. **Korrigierte Rechnung**: Lieferant schickt eine fehlerbereinigte Fassung mit
|
||||||
|
derselben Rechnungsnummer.
|
||||||
|
2. **Nachträglich geteilt**: Ein Dokument enthielt zwei Belege. Der Benutzer
|
||||||
|
teilt das PDF **von Hand** und sendet es neu; der Teil mit der bereits
|
||||||
|
vergebenen Belegnummer wird zur neuen Version.
|
||||||
|
|
||||||
|
## Ziele
|
||||||
|
|
||||||
|
- Gleicher Vergleichsdialog im **E-Mail-Import** und im **Posteingang (Scanner)**.
|
||||||
|
- Oben im Dialog eine Leiste mit **Auffälligkeiten**: abweichende
|
||||||
|
**Seitenzahl** und abweichende **Wortzahl**. Darunter beide PDFs nebeneinander.
|
||||||
|
- Maßgeblich für „Belegnummer existiert schon“ ist **Paperless** (Custom Field 7
|
||||||
|
= interne Belegnummer). Das ist die wichtige Prüfung.
|
||||||
|
|
||||||
|
## Nicht-Ziele
|
||||||
|
|
||||||
|
- Umbau der Task-Bearbeitung – läuft unverändert weiter.
|
||||||
|
- **Checksummen-Duplikate umgehen** (gleiche Datei erneut importieren) – bleibt
|
||||||
|
wie heute, wird in `docs/todos.md` notiert.
|
||||||
|
- Inhaltliche Analyse (Beträge, Datum, Positionen) für die Auffälligkeiten –
|
||||||
|
dafür gibt es noch keine Auswertung.
|
||||||
|
- Metadaten des vorhandenen Dokuments ändern (Titel, Tags, Custom Fields,
|
||||||
|
Korrespondent bleiben unverändert; nur die Datei kommt als Version dazu).
|
||||||
|
- Nachgelagerte Automatik für die Version (Postprocessing-Regeln, Mail,
|
||||||
|
WebDAV-Export, Agrarmonitor-Austausch/Storno) – wird vorerst **unterdrückt**,
|
||||||
|
die fachliche Logik kommt später (siehe Todos).
|
||||||
|
|
||||||
|
## Anti-Ziele (wäre ein Fehlschlag, auch wenn es „funktioniert“)
|
||||||
|
|
||||||
|
- Ein Beleg mit vorhandener Belegnummer landet doch als zweites Dokument in
|
||||||
|
Paperless, ohne dass der Benutzer es so entschieden hat.
|
||||||
|
- Eine Version wird an das **falsche** Dokument gehängt.
|
||||||
|
- „Verwerfen“ lässt eine reservierte Belegnummer oder einen halb angelegten
|
||||||
|
Task zurück.
|
||||||
|
|
||||||
|
## Paperless-API (am Live-System geprüft, API v10)
|
||||||
|
|
||||||
|
- `POST /api/documents/{id}/update_version/` – multipart, Feld `document`
|
||||||
|
(Datei, Pflicht), `version_label` (optional, max. 64 Zeichen). Antwort: String
|
||||||
|
(vermutlich die Consume-Task-ID; Verarbeitung asynchron).
|
||||||
|
- `PATCH|DELETE /api/documents/{id}/versions/{version_id}/` – Label ändern/löschen.
|
||||||
|
- `DocumentVersionInfo`: `id`, `added`, `version_label`, `checksum`, `is_root`.
|
||||||
|
|
||||||
|
## Ablauf E-Mail-Import (`MailImportWizard.tsx`)
|
||||||
|
|
||||||
|
Schritte: 0 Zuordnung → 1 Bearbeitung → 2 Abschluss.
|
||||||
|
|
||||||
|
1. In Schritt 1 („Bearbeitung“) hat ein Beleg den Modus **Manuell** und eine
|
||||||
|
Belegnummer (Jahr + 6-stellige Nummer → `JJJJ-NNNNNN`).
|
||||||
|
2. Beim Klick auf **Weiter** (Schritt 1 → 2) prüft das Backend für jeden
|
||||||
|
manuellen Beleg in Paperless, ob die Belegnummer schon vergeben ist (CF7 und
|
||||||
|
ASN, siehe „Eindeutigkeit des Treffers“).
|
||||||
|
3. Gibt es Treffer, öffnet sich **vor** dem Schrittwechsel der Vergleichsdialog
|
||||||
|
– nacheinander für jeden betroffenen Beleg.
|
||||||
|
- Links: vorhandenes Dokument aus Paperless. Rechts: der neue Beleg (bei
|
||||||
|
geteilten Anhängen nur die gewählten Seiten).
|
||||||
|
- **Als neue Version hinzufügen** → Beleg wird für den Import als
|
||||||
|
„Version zu Dokument X“ markiert.
|
||||||
|
- **Verwerfen** → Beleg wird auf `IGNORE` gestellt. Stehen danach alle Belege
|
||||||
|
der Mail auf `IGNORE`, greift die bestehende Logik (Mail als ignoriert
|
||||||
|
markieren).
|
||||||
|
- Dialog schließen (X) ohne Entscheidung → kein Schrittwechsel, der Benutzer
|
||||||
|
bleibt in Schritt 1.
|
||||||
|
4. Beim **Import ausführen** werden Version-Belege nicht per `post_document`
|
||||||
|
hochgeladen, sondern per `update_version` an Dokument X gehängt.
|
||||||
|
- Die ASN-Sperre (`validateAsnNotExists`) und der Belegnummer-Duplikatcheck
|
||||||
|
entfallen für diese Belege – sonst würde der Import abbrechen.
|
||||||
|
- Hochgeladen wird wie beim normalen Import das Original-PDF bzw. die
|
||||||
|
gewählten Seiten, ohne eingebrannten Stempel.
|
||||||
|
- Kein neuer Enrichment-Task in der `tasks`-Tabelle nötig (Metadaten bleiben).
|
||||||
|
- Im `Attachment`-Datensatz wird die Dokument-ID X unter dem jeweiligen
|
||||||
|
Bereich eingetragen, damit der Anhang als importiert gilt.
|
||||||
|
5. Die Checksummen-Prüfung (`recheckDuplicates`) bleibt unverändert.
|
||||||
|
|
||||||
|
## Ablauf Posteingang (`InboxDetailPage.tsx`, `inbox-postprocessor`)
|
||||||
|
|
||||||
|
Heute: `runPaperless` meldet ein Duplikat mit `duplicateOfDocumentId`; der
|
||||||
|
Wizard bietet „Neue Version erstellen“ (= Duplikatprüfung überspringen und als
|
||||||
|
zweites Dokument hochladen), „Vergleichen“ und „Überspringen“. Im
|
||||||
|
Vergleichsdialog ist „Neue Version anlegen“ noch ein TODO.
|
||||||
|
|
||||||
|
Neu:
|
||||||
|
|
||||||
|
- Liefert der Duplikat-Treffer eine Paperless-Dokument-ID, öffnet sich der
|
||||||
|
Vergleichsdialog **automatisch**.
|
||||||
|
- **Als neue Version hinzufügen** → Backend verarbeitet den Abschnitt erneut,
|
||||||
|
lädt die fertige PDF (nach allen Bearbeitungen/Stempeln) aber per
|
||||||
|
`update_version` an Dokument X hoch statt per `post_document`. Danach ist der
|
||||||
|
Abschnitt „erfolgreich“.
|
||||||
|
- **Verwerfen** → entspricht dem heutigen „Überspringen“ (Text wird umbenannt).
|
||||||
|
- Der bisherige Weg „Duplikatprüfung überspringen und als eigenes Dokument
|
||||||
|
hochladen“ entfällt in der Oberfläche.
|
||||||
|
- Duplikate **ohne** Dokument-ID (Checksumme, lokaler Task ohne
|
||||||
|
`PaperlessDocumentID`) behalten den heutigen Weg: nur Verwerfen/Überspringen.
|
||||||
|
- Rechts im Dialog wird der **betroffene Abschnitt** gezeigt, nicht das ganze
|
||||||
|
Inbox-Dokument (heute zeigt der Dialog das ganze Dokument).
|
||||||
|
|
||||||
|
## Nachgelagerte Automatik unterdrücken
|
||||||
|
|
||||||
|
Entscheidung des Nutzers: Nach dem Hinzufügen einer Version bekommt das
|
||||||
|
(Stamm-)Dokument den Tag **„neue Version hochgeladen“ (ID 21)**. Der Nutzer
|
||||||
|
konfiguriert die Postprocessing-Regeln so, dass Belege mit Tag 21 ignoriert
|
||||||
|
werden. Im Code gibt es **keine** eigene Unterdrückung (keine Markierung, keine
|
||||||
|
Änderung an der Webhook-Warteschlange).
|
||||||
|
|
||||||
|
- Reihenfolge: **erst** Tag 21 setzen (Tags lesen, 21 ergänzen, vorhandene
|
||||||
|
behalten), **dann** `update_version`. Schlägt der Versions-Upload fehl, wird
|
||||||
|
Tag 21 wieder entfernt – aber nur, wenn er vorher nicht schon gesetzt war.
|
||||||
|
- Entfernen von Tag 21 ist Sache des Nutzers.
|
||||||
|
- Die Anreicherung in `processDocumentById` (Tag 17, bereit/nicht bereit,
|
||||||
|
Titel-Template) läuft beim Webhook weiterhin – nur die Regeln greifen nicht.
|
||||||
|
|
||||||
|
Im **Posteingang** betrifft die Abfrage nur die Aktion „An Paperless senden“.
|
||||||
|
Die übrigen Aktionen des Barcode-Templates (MAIL, EXPORT) davor und danach
|
||||||
|
laufen normal: Die Aktionen davor sind beim Erkennen des Duplikats bereits
|
||||||
|
gelaufen und werden nicht wiederholt; die Aktionen danach werden nach der
|
||||||
|
Entscheidung (Version **oder** Verwerfen) ganz normal ausgeführt.
|
||||||
|
|
||||||
|
## Vergleichsdialog (gemeinsame Komponente)
|
||||||
|
|
||||||
|
- Eine Komponente für beide Stellen (aus `CompareModal` herauslösen).
|
||||||
|
- Titel z. B. „Belegnummer 2026-000123 existiert bereits“.
|
||||||
|
- **Auffälligkeiten-Leiste** oben (Ant Design `Alert`/`Tag`s):
|
||||||
|
- Seitenzahl: „Seiten: 2 (vorhanden) → 3 (neu)“ – hervorgehoben bei Abweichung.
|
||||||
|
- Wortzahl: „Wörter: 412 → 398“ – hervorgehoben bei Abweichung.
|
||||||
|
- Ist eine Zahl nicht ermittelbar (z. B. PDF ohne Textebene), steht dort
|
||||||
|
„nicht ermittelbar“ statt einer Abweichung.
|
||||||
|
- Keine Abweichung → kurzer neutraler Hinweis „Seiten- und Wortzahl gleich“.
|
||||||
|
- Darunter beide PDFs nebeneinander (mobil untereinander), wie heute.
|
||||||
|
- Knöpfe: **Verwerfen** / **Als neue Version hinzufügen** (primär).
|
||||||
|
|
||||||
|
### Kennzahlen ermitteln (Backend-Endpunkt)
|
||||||
|
|
||||||
|
- Vorhanden (Paperless): Seitenzahl aus `page_count` des Dokuments, Wortzahl aus
|
||||||
|
dessen `content` (OCR-Text von Paperless). Verglichen wird mit der aktuellen
|
||||||
|
(neuesten) Fassung des Dokuments.
|
||||||
|
- Neu: Seitenzahl per `pdf-lib`, Wortzahl aus der Textebene per `pdf-parse`
|
||||||
|
(beides bereits Abhängigkeiten im Backend). Keine Textebene → „nicht
|
||||||
|
ermittelbar“.
|
||||||
|
- Wortzählung: Text an Leerraum trennen, nur Tokens mit mindestens einem
|
||||||
|
Buchstaben oder einer Ziffer zählen.
|
||||||
|
|
||||||
|
## Versionsbezeichnung
|
||||||
|
|
||||||
|
`version_label` = „Korrektur vom TT.MM.JJJJ“ (Datum des Hinzufügens). Max. 64 Zeichen.
|
||||||
|
|
||||||
|
## Eindeutigkeit des Treffers
|
||||||
|
|
||||||
|
Geprüft wird immer gegen Paperless, und zwar über **beide** Merkmale, die aus
|
||||||
|
der internen Belegnummer folgen:
|
||||||
|
|
||||||
|
- CF7 = interne Belegnummer (z. B. `2026-000123`)
|
||||||
|
- ASN = interne Belegnummer ohne Bindestrich (z. B. `2026000123`) – auch im
|
||||||
|
Posteingang, unabhängig von einem im Template separat hinterlegten ASN-Feld.
|
||||||
|
|
||||||
|
„Als neue Version hinzufügen“ wird nur angeboten, wenn alle Treffer auf **genau
|
||||||
|
ein** Dokument zeigen. Zeigen CF7- und ASN-Treffer auf verschiedene Dokumente
|
||||||
|
oder gibt es mehrere CF7-Treffer, zeigt der Dialog einen Hinweis und bietet nur
|
||||||
|
**Verwerfen** an. Dafür muss die Suche alle Treffer liefern, nicht nur den ersten.
|
||||||
|
|
||||||
|
## Am Live-System zu prüfen (vor/bei der Umsetzung)
|
||||||
|
|
||||||
|
- Antwort von `update_version` (Task-ID?) und wie der Abschluss erkennbar ist.
|
||||||
|
- Liefern CF7-Suche, `GET /documents/{id}/` (`content`, `page_count`) und
|
||||||
|
`/download/` bei einem versionierten Dokument die Stamm-ID bzw. die neueste
|
||||||
|
Fassung? `update_version` muss immer auf die Stamm-ID zielen.
|
||||||
|
|
||||||
|
## Todos-Einträge
|
||||||
|
|
||||||
|
In `docs/todos.md` ergänzen:
|
||||||
|
|
||||||
|
1. „Duplikatprüfung per Checksumme gezielt umgehen können, wenn ein bereits
|
||||||
|
importiertes Dokument bewusst erneut importiert (z. B. geteilt) werden soll.“
|
||||||
|
2. „Neue Version eines Belegs in Agrarmonitor nachziehen (derzeit unterdrückt):
|
||||||
|
- Beleg noch im AM-Posteingang → dort löschen und neue Fassung senden.
|
||||||
|
- Beleg verarbeitet, aber nicht gebucht → im jeweiligen Einkauf das
|
||||||
|
vorhandene Dokument löschen und die neue Fassung hochladen.
|
||||||
|
- Beleg bereits gebucht → Storno-Beleg zum gebuchten Beleg erstellen und die
|
||||||
|
neue Fassung über die jeweilige E-Mail-Adresse neu in den AM-Posteingang
|
||||||
|
senden.
|
||||||
|
Außerdem klären, ob Postprocessing-Regeln (Mail an Buchhaltung, Export) für
|
||||||
|
Versionen laufen sollen.“
|
||||||
|
3. „Task-Bearbeitung (`PaperlessTaskProcessorService`, 30-s-Cron) auf den
|
||||||
|
Paperless-Webhook umstellen. Beim Upload werden die Metadaten schon gesetzt;
|
||||||
|
übrig bleiben Owner entfernen, Notizen, Anlage-Verknüpfung (Feld 8) und die
|
||||||
|
lokale Buchführung (Task/Attachment/documents) – die brauchen die
|
||||||
|
Dokument-ID nach dem Consume.“
|
||||||
|
|
||||||
|
## Dem Umsetzer überlassen
|
||||||
|
|
||||||
|
- Schnitt der Backend-Endpunkte (eigener Endpunkt für „Belegnummern prüfen“ und
|
||||||
|
„Kennzahlen“, oder zusammengefasst).
|
||||||
|
- Wie das Ergebnis von `update_version` verfolgt wird (Task-Polling wie beim
|
||||||
|
normalen Upload, sofern die Antwort eine Task-ID ist).
|
||||||
|
- Tests: mindestens Unit-Tests für Wortzählung/Auffälligkeiten und für die
|
||||||
|
Entscheidung Upload vs. Version im Backend.
|
||||||
|
|
||||||
|
## Eigene Entscheidungen (nicht ausdrücklich vom Nutzer)
|
||||||
|
|
||||||
|
- Versionsbezeichnung „Korrektur vom …“.
|
||||||
|
- Version nur bei eindeutigem Treffer (CF7 und ASN zeigen auf dasselbe
|
||||||
|
Dokument); sonst nur Verwerfen.
|
||||||
|
- E-Mail-Import: Version wird wie normale Importe ohne eingebrannten Stempel
|
||||||
|
hochgeladen; keine erneute Barcode-Notiz (die hat das Stammdokument schon).
|
||||||
|
|
||||||
|
- Automatisches Öffnen des Dialogs im Posteingang; Wegfall von „als eigenes
|
||||||
|
Dokument hochladen“.
|
||||||
|
- Dialog schließen ohne Entscheidung = im Schritt bleiben.
|
||||||
|
- Rechts nur der betroffene Abschnitt statt des ganzen Inbox-Dokuments.
|
||||||
Reference in new issue
Block a user