feat: Duplikate im Import als neue Version behandeln #6
No files matched your search
@@ -166,8 +166,11 @@ Geprüft wird immer gegen Paperless, und zwar über **beide** Merkmale, die aus
|
|||||||
der internen Belegnummer folgen:
|
der internen Belegnummer folgen:
|
||||||
|
|
||||||
- CF7 = interne Belegnummer (z. B. `2026-000123`)
|
- CF7 = interne Belegnummer (z. B. `2026-000123`)
|
||||||
- ASN = interne Belegnummer ohne Bindestrich (z. B. `2026000123`) – auch im
|
- ASN = die ASN, die das Dokument beim Upload **tatsächlich bekäme**: im
|
||||||
Posteingang, unabhängig von einem im Template separat hinterlegten ASN-Feld.
|
Posteingang aus dem ASN-Feld der Aktion, falls gesetzt, sonst aus der
|
||||||
|
Belegnummer ohne Bindestrich (z. B. `2026000123`); im E-Mail-Import immer aus
|
||||||
|
der Belegnummer. Prüfung, Versions-Upload und normaler Upload verwenden
|
||||||
|
dieselbe ASN.
|
||||||
|
|
||||||
„Als neue Version hinzufügen“ wird nur angeboten, wenn alle Treffer auf **genau
|
„Als neue Version hinzufügen“ wird nur angeboten, wenn alle Treffer auf **genau
|
||||||
ein** Dokument zeigen. Zeigen CF7- und ASN-Treffer auf verschiedene Dokumente
|
ein** Dokument zeigen. Zeigen CF7- und ASN-Treffer auf verschiedene Dokumente
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { InboxPostprocessorService } from './inbox-postprocessor.service';
|
import { InboxPostprocessorService } from './inbox-postprocessor.service';
|
||||||
import { originalSeitenDesAbschnitts } from './paperless-entscheidung.util';
|
import {
|
||||||
|
ermittleUploadAsn,
|
||||||
|
originalSeitenDesAbschnitts,
|
||||||
|
} from './paperless-entscheidung.util';
|
||||||
|
|
||||||
describe('InboxPostprocessorService.runForDocument', () => {
|
describe('InboxPostprocessorService.runForDocument', () => {
|
||||||
it('nimmt eine Duplikat-Entscheidung nur für genau einen Abschnitt an', async () => {
|
it('nimmt eine Duplikat-Entscheidung nur für genau einen Abschnitt an', async () => {
|
||||||
@@ -25,3 +28,18 @@ describe('originalSeitenDesAbschnitts', () => {
|
|||||||
expect(originalSeitenDesAbschnitts([1, 3, 4, 5], [1, 2])).toEqual([3, 4]);
|
expect(originalSeitenDesAbschnitts([1, 3, 4, 5], [1, 2])).toEqual([3, 4]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('ermittleUploadAsn', () => {
|
||||||
|
it('nimmt die ASN aus dem Template-Feld, wenn gesetzt', () => {
|
||||||
|
expect(ermittleUploadAsn('ASN 555', '2026-000123')).toBe(555);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bildet sie sonst aus der Belegnummer', () => {
|
||||||
|
expect(ermittleUploadAsn('', '2026-000123')).toBe(2026000123);
|
||||||
|
expect(ermittleUploadAsn(null, '2026-000123')).toBe(2026000123);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('liefert undefined, wenn keine Ziffern vorhanden sind', () => {
|
||||||
|
expect(ermittleUploadAsn(null, 'abc')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,6 +26,7 @@ import { applyTemplate, buildVariables } from './variable-resolver';
|
|||||||
import { DokumentVersionService } from '../paperless/dokument-version.service';
|
import { DokumentVersionService } from '../paperless/dokument-version.service';
|
||||||
import {
|
import {
|
||||||
aktionenAbEntscheidung,
|
aktionenAbEntscheidung,
|
||||||
|
ermittleUploadAsn,
|
||||||
originalSeitenDesAbschnitts,
|
originalSeitenDesAbschnitts,
|
||||||
type PaperlessEntscheidung,
|
type PaperlessEntscheidung,
|
||||||
} from './paperless-entscheidung.util';
|
} from './paperless-entscheidung.util';
|
||||||
@@ -471,12 +472,18 @@ export class InboxPostprocessorService {
|
|||||||
if (!interneBelegnummer)
|
if (!interneBelegnummer)
|
||||||
throw new Error('Interne Belegnummer konnte nicht aufgelöst werden');
|
throw new Error('Interne Belegnummer konnte nicht aufgelöst werden');
|
||||||
|
|
||||||
// 2. Vom Benutzer bestätigt: als neue Version an das Stammdokument hängen
|
// 2. ASN, die das Dokument beim Upload bekäme – gegen genau diese wird geprüft
|
||||||
|
const asnTpl = String(content.asn ?? '').trim();
|
||||||
|
const asn = asnTpl ? applyTemplate(asnTpl, variables).trim() : null;
|
||||||
|
const archiveSerialNumber = ermittleUploadAsn(asn, interneBelegnummer);
|
||||||
|
|
||||||
|
// 3. Vom Benutzer bestätigt: als neue Version an das Stammdokument hängen
|
||||||
if (entscheidung?.aktion === 'version') {
|
if (entscheidung?.aktion === 'version') {
|
||||||
await this.dokumentVersion.haengeVersionAn(
|
await this.dokumentVersion.haengeVersionAn(
|
||||||
entscheidung.documentId,
|
entscheidung.documentId,
|
||||||
pdfPath,
|
pdfPath,
|
||||||
interneBelegnummer,
|
interneBelegnummer,
|
||||||
|
archiveSerialNumber ?? null,
|
||||||
);
|
);
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Belegnummer ${interneBelegnummer} als neue Version zu Dokument ${entscheidung.documentId} hinzugefügt`,
|
`Belegnummer ${interneBelegnummer} als neue Version zu Dokument ${entscheidung.documentId} hinzugefügt`,
|
||||||
@@ -486,22 +493,11 @@ export class InboxPostprocessorService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. ASN bestimmen (für den Upload)
|
// 4. Duplikat-Check Paperless: CF7 + Upload-ASN
|
||||||
const asnTpl = String(content.asn ?? '').trim();
|
const pruefung = await this.paperlessService.pruefeBelegnummer(
|
||||||
const asn = asnTpl ? applyTemplate(asnTpl, variables).trim() : null;
|
interneBelegnummer,
|
||||||
let archiveSerialNumber: number | undefined;
|
archiveSerialNumber ?? null,
|
||||||
if (asn) {
|
);
|
||||||
const n = parseInt(asn.replace(/[^0-9]/g, ''), 10);
|
|
||||||
if (!Number.isNaN(n)) archiveSerialNumber = n;
|
|
||||||
}
|
|
||||||
if (archiveSerialNumber === undefined) {
|
|
||||||
const n = parseInt(interneBelegnummer.replace(/[^0-9]/g, ''), 10);
|
|
||||||
if (!Number.isNaN(n)) archiveSerialNumber = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Duplikat-Check Paperless: CF7 + aus der Belegnummer abgeleitete ASN
|
|
||||||
const pruefung =
|
|
||||||
await this.paperlessService.pruefeBelegnummer(interneBelegnummer);
|
|
||||||
if (pruefung.status !== 'frei') {
|
if (pruefung.status !== 'frei') {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`Duplikat (Paperless): Belegnummer ${interneBelegnummer} bereits vorhanden (Dokument(e) ${pruefung.kandidaten.join(', ')})`,
|
`Duplikat (Paperless): Belegnummer ${interneBelegnummer} bereits vorhanden (Dokument(e) ${pruefung.kandidaten.join(', ')})`,
|
||||||
|
|||||||
@@ -27,3 +27,19 @@ export function originalSeitenDesAbschnitts(
|
|||||||
): number[] {
|
): number[] {
|
||||||
return pageIndices.map((idx) => survivingOriginalPages[idx]);
|
return pageIndices.map((idx) => survivingOriginalPages[idx]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ASN, die das Dokument beim Upload bekommt: aus dem ASN-Feld der Aktion
|
||||||
|
* (bereits aufgelöst), sonst aus der internen Belegnummer – jeweils nur die Ziffern.
|
||||||
|
*/
|
||||||
|
export function ermittleUploadAsn(
|
||||||
|
asnAusTemplate: string | null,
|
||||||
|
interneBelegnummer: string,
|
||||||
|
): number | undefined {
|
||||||
|
for (const quelle of [asnAusTemplate, interneBelegnummer]) {
|
||||||
|
if (!quelle) continue;
|
||||||
|
const n = parseInt(quelle.replace(/[^0-9]/g, ''), 10);
|
||||||
|
if (!Number.isNaN(n)) return n;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
@@ -56,6 +56,17 @@ describe('DokumentVersionService.haengeVersionAn', () => {
|
|||||||
expect(paperless.uploadDocumentVersion).not.toHaveBeenCalled();
|
expect(paperless.uploadDocumentVersion).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prüft die Upload-ASN, wenn sie übergeben wird', async () => {
|
||||||
|
paperless.getDocument.mockResolvedValue({ id: 42, tags: [] });
|
||||||
|
|
||||||
|
await service.haengeVersionAn(42, '/tmp/x.pdf', '2026-000123', 555);
|
||||||
|
|
||||||
|
expect(paperless.pruefeBelegnummer).toHaveBeenCalledWith(
|
||||||
|
'2026-000123',
|
||||||
|
555,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('wartet auf die Paperless-Verarbeitung der Version', async () => {
|
it('wartet auf die Paperless-Verarbeitung der Version', async () => {
|
||||||
paperless.getDocument.mockResolvedValue({ id: 42, tags: [] });
|
paperless.getDocument.mockResolvedValue({ id: 42, tags: [] });
|
||||||
paperless.getTask
|
paperless.getTask
|
||||||
|
|||||||
@@ -32,8 +32,12 @@ export class DokumentVersionService {
|
|||||||
rootDocumentId: number,
|
rootDocumentId: number,
|
||||||
pdfPath: string,
|
pdfPath: string,
|
||||||
belegnummer: string,
|
belegnummer: string,
|
||||||
|
uploadAsn?: number | null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const pruefung = await this.paperlessService.pruefeBelegnummer(belegnummer);
|
const pruefung = await this.paperlessService.pruefeBelegnummer(
|
||||||
|
belegnummer,
|
||||||
|
uploadAsn,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
pruefung.status !== 'eindeutig' ||
|
pruefung.status !== 'eindeutig' ||
|
||||||
pruefung.documentId !== rootDocumentId
|
pruefung.documentId !== rootDocumentId
|
||||||
|
|||||||
@@ -85,4 +85,17 @@ describe('PaperlessService – Belegnummer-Suche', () => {
|
|||||||
kandidaten: [42],
|
kandidaten: [42],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prüft die übergebene Upload-ASN statt der aus der Belegnummer gebildeten', async () => {
|
||||||
|
get.mockResolvedValue({ data: { results: [] } });
|
||||||
|
|
||||||
|
await service.pruefeBelegnummer('2026-000123', 555);
|
||||||
|
|
||||||
|
const asnWerte = (
|
||||||
|
get.mock.calls as [string, { params: Record<string, unknown> }][]
|
||||||
|
)
|
||||||
|
.map(([, cfg]) => cfg.params.archive_serial_number)
|
||||||
|
.filter((wert) => wert !== undefined);
|
||||||
|
expect(asnWerte).toEqual([555]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -500,12 +500,16 @@ export class PaperlessService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft, ob die interne Belegnummer in Paperless schon vergeben ist – über
|
* Prüft, ob die interne Belegnummer in Paperless schon vergeben ist – über
|
||||||
* CF7 und die daraus abgeleitete ASN.
|
* CF7 und die ASN, die das Dokument beim Upload bekäme (`uploadAsn`; ohne
|
||||||
|
* Angabe die aus der Belegnummer gebildete).
|
||||||
*/
|
*/
|
||||||
async pruefeBelegnummer(belegnummer: string): Promise<BelegnummerPruefung> {
|
async pruefeBelegnummer(
|
||||||
|
belegnummer: string,
|
||||||
|
uploadAsn?: number | null,
|
||||||
|
): Promise<BelegnummerPruefung> {
|
||||||
const nummer = (belegnummer ?? '').trim();
|
const nummer = (belegnummer ?? '').trim();
|
||||||
if (!nummer) return klassifiziereTreffer([], []);
|
if (!nummer) return klassifiziereTreffer([], []);
|
||||||
const asn = belegnummerZuAsn(nummer);
|
const asn = uploadAsn ?? belegnummerZuAsn(nummer);
|
||||||
const [cf7, asnTreffer] = await Promise.all([
|
const [cf7, asnTreffer] = await Promise.all([
|
||||||
this.findRootDocumentIdsByCustomField(7, nummer),
|
this.findRootDocumentIdsByCustomField(7, nummer),
|
||||||
asn !== null
|
asn !== null
|
||||||
|
|||||||
Reference in new issue
Block a user