24 KiB
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/, multipartdocument+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 wie1782700000000-CreateWebhookQueue.ts. - Backend nach jeder Änderung:
npx eslint <Dateien>undnpx prettier --check <Dateien>grün für die geänderten Dateien; Frontend:npm run lintundnpm run buildgrün.
Review Focus
- Dokument hat Tag 21 schon (zweite Korrektur) – Tag nicht doppelt setzen und bei fehlgeschlagenem Upload nicht entfernen. → Test in Task 4.
- Dokument hat schon Versionen: CF7-/ASN-Suche liefert Stamm und Versionen → darf nicht als „mehrdeutig“ gelten. → Test in Task 2.
- 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.
- Posteingang „Verwerfen“: Aktionen nach PAPERLESS laufen, Aktionen davor nicht doppelt. → Test in Task 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>undGET /api/documents/<id>/abrufen; notieren, obroot_documentundversionsbefü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 vonGET /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 habenid,root_document,page_count,contentam 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:
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(nebenfindDocumentIdByCustomField/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;nullbei 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[]>– wiefindDocumentIdByCustomField(gleiche case-insensitive Verifikation), aber alle Treffer, je Trefferroot_document ?? id.PaperlessService.findRootDocumentIdsByAsn(asn: number): Promise<number[]>– analog zufindDocumentIdByAsn.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:
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 perPDFDocument.load(..., { ignoreEncryption: true }).getPageCount(), Text perPDFParse(Muster ausemail/pdf-fingerprint.util.ts,destroy()imfinally); Lesefehler → jeweilsnull.PaperlessService.getDocumentKennzahlen(documentId: number): Promise<Kennzahlen>–page_countundzaehleWoerter(content)ausgetDocument(laut Task 1 neueste Fassung).POST /api/inbox/:id/vergleichBody{ paperlessDocumentId: number; pages: number[] }→VergleichKennzahlen; neue PDF überinboxService.getSegmentPdfBuffer(id, user, pages).POST /api/email-import/attachments/:attachmentId/vergleichBody{ paperlessDocumentId: number; pages?: { start: number; end: number } }→VergleichKennzahlen;@RequirePermissions(Permission.VIEW_MAIL); PDF ausContent.Content1, beipagesnur dieser Bereich (pdf-libcopyPageswie inrunImport).- 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 istprintPreview– nur verwenden, wenn ohne Stempel möglich; sonstGET /api/email-import/attachments/:attachmentId/pdf?start=&end=ergänzen).
-
Step 1: Failing tests:
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 wieuploadDocument, 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:- Dokument lesen; hat es Tag 21 noch nicht, per
updateDocument(rootDocumentId, { tags })bisherige Tags plus 21 setzen. - Danach
uploadDocumentVersion(rootDocumentId, pdfPath, versionLabel(new Date())). - Schlägt der Upload fehl und wurde Tag 21 in Schritt 1 neu gesetzt: Dokument erneut lesen, Tag 21 entfernen (
updateDocumentmit 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 ausPaperlessModule.
- Dokument lesen; hat es Tag 21 noch nicht, per
-
Step 1: Failing tests (
PaperlessServicegemockt):versionLabel(new Date(2026, 10, 2))→'Korrektur vom 02.11.2026'.- Tags
[3, 17]:updateDocument(42, { tags: [3, 17, 21] })wird voruploadDocumentVersion(42, '/tmp/x.pdf', <Label>)aufgerufen (Reihenfolge übermock.invocationCallOrderprüfen). - Tags
[21, 5]: keinupdateDocument, 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] });haengeVersionAnwirft 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 buildim 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(Typenpostprocess)
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 erstenPAPERLESS-Aktion (die davor liefen schon); gibt es keinePAPERLESS-Aktion, alle.ActionResultergänzt umversionMoeglich?: booleanundduplikatKandidaten?: number[];duplicateOfDocumentId= eindeutige Stamm-ID bzw. erster Kandidat.runForDocument(documentId, preferredUsername, sectionOffset = 0, processOnlyOne = false, entscheidung?: PaperlessEntscheidung)– ersetzt den ParameterreplaceDuplicate.- Controller-Body:
{ sectionOffset?: number; processOnlyOne?: boolean; entscheidung?: PaperlessEntscheidung }(replaceDuplicateentfä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). BeiskippedAbbruch wie heute.
-
Step 1: Failing tests für
aktionenAbEntscheidungmit[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 buildundnpm testim Backend (bekannte, schon vorher rote Specspdf-fingerprint.util.spec.ts,attachment-fingerprint.service.spec.tsausgenommen) 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 vonexecuteImport/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
executeImportakzeptiertversionZuDokumentId?: number | null. Ist er gesetzt:- kein
validateAsnNotExists, keinuploadDocument, kein Task, kein Task-Polling, keinsetBelegnummer; - je Upload-Datei
haengeVersionAn(versionZuDokumentId, uploadItem.path);paperlessIds[rangeKey] = versionZuDokumentId; ImportStatus = 1,InterneBelegnummerwie bisher am Attachment gesetzt;- Ergebnis enthält
{ attachmentId, paperlessIds, alsVersion: true }.
- kein
-
Step 1: Failing test in
email-import.service.spec.ts, neuerdescribe('Import als Version')mit dem bestehenden Testmodul (+ ProviderDokumentVersionServicegemockt), Anhang mitversionZuDokumentId: 42,belegnummer: '2026-000123',barcode.nummer: '000123':dokumentVersionService.haengeVersionAneinmal mit42und einem.pdf-Pfad aufgerufen;paperlessService.uploadDocument,paperlessService.validateAsnNotExistsundtaskRepo.savenicht aufgerufen;- gespeichertes Attachment hat
PaperlessDocumentIds{ full: 42 }undImportStatus: 1.
-
Step 2:
npx jest src/email/email-import.service.spec.ts→ neuer Test FAIL. -
Step 3: Zweig in
runImportimplementieren (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(CompareModalentfernen, Wizard umstellen) - Modify:
paperless-frontend/src/components/MailImportWizard.tsx
Interfaces:
- Consumes: Frontend-APIs aus Task 2, 3, 5, 6.
- Produces:
DuplikatVergleichModalmit PropsTitel{ 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; }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 orangeTag,nullals „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:
Verwerfenund primärAls neue Version hinzufügen(nur beieindeutig). Layout der PDFs wie bisher (flex, mobil untereinander,75vh), Object-URLs beim Schließen freigeben.
- je Kennzahl eine Zeile
Posteingang (PostprocessWizardModal):
processSection(idx, entscheidung?: PaperlessEntscheidung); Ergebnis mitversionMoeglichoderduplikatKandidatenöffnet den Dialog automatisch (setCompareForSection(idx)).- Zeilen-Knöpfe bei Duplikat:
Vergleichen(öffnet Dialog, nur wenn eine Dokument-ID vorliegt) undVerwerfen; „Neue Version erstellen“ (alterreplaceDuplicate-Weg) entfällt. onVersion→processSection(idx, { aktion: 'version', documentId });onVerwerfenund Zeilen-„Verwerfen“ →processSection(idx, { aktion: 'verwerfen' }); danach Abschnitt-Statusskippedmit 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
nextStepbeicurrentStep === 1, nachrecheckDuplicatesund vorpreFetchBelegnummern: für jedes nicht ignorierte Item im Modusmanuelldie Belegnummer${jahr}-${nummer.padStart(6,'0')}bilden undpaperlessApi.pruefeBelegnummeraufrufen; eine vorhandene Entscheidung gilt nur, wenn ihrebelegnummergleich 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 auftype: 'IGNORE'; sind danach alle ItemsIGNORE→ignoreEmail()statt Schrittwechsel. -
Rechte Seite: PDF des Anhangs bzw. Bereichs (Task 3), Kennzahlen:
emailImportApi.vergleich(...). -
executeImport:versionZuDokumentIdaus gültiger Entscheidung infinalDataübernehmen. -
In Schritt 3 („Abschluss“) bei Version-Items einen Hinweis „wird als neue Version zu Dokument X hinzugefügt“ anzeigen.
-
Step 1: Komponente bauen,
CompareModalinInboxDetailPage.tsxersetzen und Posteingang umstellen. -
Step 2:
MailImportWizardumstellen. -
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 andocs/todos.mdanhängen. -
Step 2: Backend
npm run build,npm test(bekannte rote Specs ausgenommen), Frontendnpm run build– alles grün. -
Step 3: Commit
docs: offene Punkte zu Versionen und Task-Bearbeitung