fix(paperless): Anpassung an Paperless-NGX 3.4 (API v10)
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 31s
- Task-Status: getTask() normalisiert die paginierte v10-Antwort (status klein, result_data, related_document_ids). Bisher wurde jede Task als „nicht gefunden“ gelöscht, ohne Nachbearbeitung. - Checksummen: Paperless ≥ 3.0 nutzt SHA-256 statt MD5. Duplikatprüfung umgestellt; Migration verbreitert Attachments.Checksum und berechnet Bestandswerte per SHA2() neu. - Custom-Field-Suche über custom_field_query, der alte Filter wurde von Paperless ignoriert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
33457c19c2
commit
4f34855c12
10 files changed
+218
-40
No files matched your search
@@ -28,7 +28,8 @@ export class Attachment {
|
|||||||
@Column({ type: 'tinyint', width: 1 })
|
@Column({ type: 'tinyint', width: 1 })
|
||||||
Erechnung!: boolean;
|
Erechnung!: boolean;
|
||||||
|
|
||||||
@Column({ type: 'varchar', length: 32, default: '' })
|
/** SHA-256 der Datei — entspricht der Checksumme in Paperless ≥ 3.0 */
|
||||||
|
@Column({ type: 'varchar', length: 64, default: '' })
|
||||||
Checksum!: string;
|
Checksum!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paperless-NGX ≥ 3.0 speichert Checksummen als SHA-256 statt MD5. Damit der
|
||||||
|
* Duplikat-Abgleich (`checksum__iexact`) wieder trifft, wird
|
||||||
|
* `Attachments.Checksum` auf 64 Zeichen verbreitert und für alle Anhänge mit
|
||||||
|
* gespeichertem Inhalt direkt in der Datenbank neu berechnet.
|
||||||
|
*/
|
||||||
|
export class AttachmentChecksumSha2561793500000000 implements MigrationInterface {
|
||||||
|
name = 'AttachmentChecksumSha2561793500000000';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE \`Attachments\`
|
||||||
|
MODIFY \`Checksum\` varchar(64) NOT NULL DEFAULT ''
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE \`Attachments\` a
|
||||||
|
INNER JOIN \`Contents\` c ON c.\`AttachmentEntityId\` = a.\`Id\`
|
||||||
|
SET a.\`Checksum\` = SHA2(c.\`Content\`, 256)
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE \`Attachments\` a
|
||||||
|
INNER JOIN \`Contents\` c ON c.\`AttachmentEntityId\` = a.\`Id\`
|
||||||
|
SET a.\`Checksum\` = MD5(c.\`Content\`)
|
||||||
|
`);
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE \`Attachments\`
|
||||||
|
MODIFY \`Checksum\` varchar(32) NOT NULL DEFAULT ''
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -304,7 +304,10 @@ export class EmailDownloadService {
|
|||||||
attachment.ContentType = contentType.slice(0, 100);
|
attachment.ContentType = contentType.slice(0, 100);
|
||||||
attachment.IsEmbedded = isEmbedded;
|
attachment.IsEmbedded = isEmbedded;
|
||||||
attachment.ContentId = att.cid ? att.cid.slice(0, 255) : null;
|
attachment.ContentId = att.cid ? att.cid.slice(0, 255) : null;
|
||||||
attachment.Checksum = crypto.createHash('md5').update(buffer).digest('hex');
|
attachment.Checksum = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(buffer)
|
||||||
|
.digest('hex');
|
||||||
attachment.ContentFingerprint =
|
attachment.ContentFingerprint =
|
||||||
contentType.toLowerCase() === 'application/pdf'
|
contentType.toLowerCase() === 'application/pdf'
|
||||||
? await computeContentFingerprint(buffer)
|
? await computeContentFingerprint(buffer)
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ describe('EmailImportService – Duplikatprüfung', () => {
|
|||||||
|
|
||||||
const [checksum] = paperlessService.checksumExists.mock
|
const [checksum] = paperlessService.checksumExists.mock
|
||||||
.calls[0] as string[];
|
.calls[0] as string[];
|
||||||
expect(checksum).toMatch(/^[0-9a-f]{32}$/);
|
expect(checksum).toMatch(/^[0-9a-f]{64}$/);
|
||||||
expect(checksum).not.toBe('abc123');
|
expect(checksum).not.toBe('abc123');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -307,8 +307,9 @@ export class EmailImportService {
|
|||||||
const copied = await sliced.copyPages(pdfDoc, indices);
|
const copied = await sliced.copyPages(pdfDoc, indices);
|
||||||
copied.forEach((p) => sliced.addPage(p));
|
copied.forEach((p) => sliced.addPage(p));
|
||||||
|
|
||||||
|
// Paperless ≥ 3.0 vergleicht SHA-256-Checksummen
|
||||||
const checksum = crypto
|
const checksum = crypto
|
||||||
.createHash('md5')
|
.createHash('sha256')
|
||||||
.update(Buffer.from(await sliced.save()))
|
.update(Buffer.from(await sliced.save()))
|
||||||
.digest('hex');
|
.digest('hex');
|
||||||
return this.paperlessService.checksumExists(checksum);
|
return this.paperlessService.checksumExists(checksum);
|
||||||
@@ -787,14 +788,17 @@ export class EmailImportService {
|
|||||||
try {
|
try {
|
||||||
const taskStatus =
|
const taskStatus =
|
||||||
await this.paperlessService.getTask(paperlessTaskId);
|
await this.paperlessService.getTask(paperlessTaskId);
|
||||||
// Paperless returns { results: [ ... ] } for filtered tasks
|
if (taskStatus?.documentId) {
|
||||||
const statusObj = taskStatus.results
|
docId = taskStatus.documentId;
|
||||||
? taskStatus.results[0]
|
break;
|
||||||
: Array.isArray(taskStatus)
|
}
|
||||||
? taskStatus[0]
|
if (
|
||||||
: taskStatus;
|
taskStatus?.status === 'FAILURE' ||
|
||||||
if (statusObj && statusObj.related_document) {
|
taskStatus?.status === 'REVOKED'
|
||||||
docId = statusObj.related_document;
|
) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Paperless-Task ${paperlessTaskId} fehlgeschlagen: ${taskStatus.message ?? 'keine Meldung'}`,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|||||||
@@ -483,7 +483,8 @@ export class InboxPostprocessorService {
|
|||||||
|
|
||||||
// 6. Checksum berechnen und prüfen
|
// 6. Checksum berechnen und prüfen
|
||||||
const buffer = await fs.readFile(pdfPath);
|
const buffer = await fs.readFile(pdfPath);
|
||||||
const checksum = crypto.createHash('md5').update(buffer).digest('hex');
|
// Paperless ≥ 3.0 vergleicht SHA-256-Checksummen
|
||||||
|
const checksum = crypto.createHash('sha256').update(buffer).digest('hex');
|
||||||
const checksumExists =
|
const checksumExists =
|
||||||
await this.paperlessService.checksumExists(checksum);
|
await this.paperlessService.checksumExists(checksum);
|
||||||
if (checksumExists) {
|
if (checksumExists) {
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ describe('PaperlessTaskProcessorService.processSuccessfulTask', () => {
|
|||||||
/** Zugriff auf die private Methode, typsicher für den Test gekapselt. */
|
/** Zugriff auf die private Methode, typsicher für den Test gekapselt. */
|
||||||
function runProcessSuccessfulTask(
|
function runProcessSuccessfulTask(
|
||||||
task: Task,
|
task: Task,
|
||||||
apiTask: { related_document: number },
|
apiTask: { documentId: number },
|
||||||
parentTask: Task | null,
|
parentTask: Task | null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return (
|
return (
|
||||||
service as unknown as {
|
service as unknown as {
|
||||||
processSuccessfulTask: (
|
processSuccessfulTask: (
|
||||||
t: Task,
|
t: Task,
|
||||||
a: { related_document: number },
|
a: { documentId: number },
|
||||||
p: Task | null,
|
p: Task | null,
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
@@ -132,7 +132,7 @@ describe('PaperlessTaskProcessorService.processSuccessfulTask', () => {
|
|||||||
CustomFieldsJson: JSON.stringify({ '4': 'x' }),
|
CustomFieldsJson: JSON.stringify({ '4': 'x' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
await runProcessSuccessfulTask(task, { related_document: 101 }, null);
|
await runProcessSuccessfulTask(task, { documentId: 101 }, null);
|
||||||
|
|
||||||
expect(paperlessService.updateDocument).toHaveBeenCalledTimes(1);
|
expect(paperlessService.updateDocument).toHaveBeenCalledTimes(1);
|
||||||
expect(paperlessService.updateDocument).toHaveBeenCalledWith(
|
expect(paperlessService.updateDocument).toHaveBeenCalledWith(
|
||||||
@@ -164,7 +164,7 @@ describe('PaperlessTaskProcessorService.processSuccessfulTask', () => {
|
|||||||
});
|
});
|
||||||
const task = createTask({ InterneBelegnummer: '2026-000124' });
|
const task = createTask({ InterneBelegnummer: '2026-000124' });
|
||||||
|
|
||||||
await runProcessSuccessfulTask(task, { related_document: 102 }, null);
|
await runProcessSuccessfulTask(task, { documentId: 102 }, null);
|
||||||
|
|
||||||
expect(lastPatchPayload().owner).toBeNull();
|
expect(lastPatchPayload().owner).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -178,7 +178,7 @@ describe('PaperlessTaskProcessorService.processSuccessfulTask', () => {
|
|||||||
tags: [],
|
tags: [],
|
||||||
})
|
})
|
||||||
.mockResolvedValueOnce({ id: 55, title: 'Eltern' });
|
.mockResolvedValueOnce({ id: 55, title: 'Eltern' });
|
||||||
paperlessService.getTask.mockResolvedValue([{ related_document: 55 }]);
|
paperlessService.getTask.mockResolvedValue({ documentId: 55 });
|
||||||
const task = createTask({
|
const task = createTask({
|
||||||
DocumentType: 2,
|
DocumentType: 2,
|
||||||
TaskReferenceID: 'parent-uuid',
|
TaskReferenceID: 'parent-uuid',
|
||||||
@@ -189,7 +189,7 @@ describe('PaperlessTaskProcessorService.processSuccessfulTask', () => {
|
|||||||
Fertig: 1,
|
Fertig: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
await runProcessSuccessfulTask(task, { related_document: 103 }, parentTask);
|
await runProcessSuccessfulTask(task, { documentId: 103 }, parentTask);
|
||||||
|
|
||||||
const payload = lastPatchPayload();
|
const payload = lastPatchPayload();
|
||||||
expect(payload.document_type).toBe(5);
|
expect(payload.document_type).toBe(5);
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { Repository, IsNull } from 'typeorm';
|
|||||||
import { Task } from '../database/entities/task.entity';
|
import { Task } from '../database/entities/task.entity';
|
||||||
import { Document } from '../database/entities/document.entity';
|
import { Document } from '../database/entities/document.entity';
|
||||||
import { Attachment } from '../database/entities/attachment.entity';
|
import { Attachment } from '../database/entities/attachment.entity';
|
||||||
import { PaperlessService } from './paperless.service';
|
import {
|
||||||
|
PaperlessService,
|
||||||
|
type PaperlessTaskStatus,
|
||||||
|
} from './paperless.service';
|
||||||
import { deriveTaskMetadata } from './task-metadata.util';
|
import { deriveTaskMetadata } from './task-metadata.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -58,15 +61,12 @@ export class PaperlessTaskProcessorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch task status from Paperless
|
// Fetch task status from Paperless
|
||||||
const paperlessTasks = await this.paperlessService.getTask(t.TaskId);
|
const apiResponseTask = await this.paperlessService.getTask(t.TaskId);
|
||||||
const apiResponseTask = Array.isArray(paperlessTasks)
|
|
||||||
? paperlessTasks[0]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (apiResponseTask) {
|
if (apiResponseTask) {
|
||||||
if (apiResponseTask.status === 'SUCCESS') {
|
if (apiResponseTask.status === 'SUCCESS') {
|
||||||
const dateDone = apiResponseTask.date_done
|
const dateDone = apiResponseTask.dateDone
|
||||||
? new Date(apiResponseTask.date_done)
|
? new Date(apiResponseTask.dateDone)
|
||||||
: new Date();
|
: new Date();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
@@ -74,12 +74,15 @@ export class PaperlessTaskProcessorService {
|
|||||||
if (dateDone.getTime() + 10000 < now.getTime()) {
|
if (dateDone.getTime() + 10000 < now.getTime()) {
|
||||||
await this.processSuccessfulTask(t, apiResponseTask, parentTask);
|
await this.processSuccessfulTask(t, apiResponseTask, parentTask);
|
||||||
}
|
}
|
||||||
} else if (apiResponseTask.status === 'FAILURE') {
|
} else if (
|
||||||
|
apiResponseTask.status === 'FAILURE' ||
|
||||||
|
apiResponseTask.status === 'REVOKED'
|
||||||
|
) {
|
||||||
// Seit Metadaten (inkl. ASN) direkt beim Upload gesetzt werden, kann
|
// Seit Metadaten (inkl. ASN) direkt beim Upload gesetzt werden, kann
|
||||||
// z.B. eine ASN-Kollision bereits den Consume scheitern lassen –
|
// z.B. eine ASN-Kollision bereits den Consume scheitern lassen –
|
||||||
// vor dem Löschen die Diagnose-Infos festhalten
|
// vor dem Löschen die Diagnose-Infos festhalten
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Task ${t.TaskId} in Paperless fehlgeschlagen (Beleg: ${t.InterneBelegnummer || '-'}, Attachment: ${t.SourceAttachmentID ?? '-'}). Paperless-Meldung: ${apiResponseTask.result ?? 'keine'}`,
|
`Task ${t.TaskId} in Paperless fehlgeschlagen (Beleg: ${t.InterneBelegnummer || '-'}, Attachment: ${t.SourceAttachmentID ?? '-'}). Paperless-Meldung: ${apiResponseTask.message ?? 'keine'}`,
|
||||||
);
|
);
|
||||||
toDelete.push(t);
|
toDelete.push(t);
|
||||||
}
|
}
|
||||||
@@ -103,10 +106,10 @@ export class PaperlessTaskProcessorService {
|
|||||||
|
|
||||||
private async processSuccessfulTask(
|
private async processSuccessfulTask(
|
||||||
t: Task,
|
t: Task,
|
||||||
apiTask: any,
|
apiTask: PaperlessTaskStatus,
|
||||||
parentTask: Task | null,
|
parentTask: Task | null,
|
||||||
) {
|
) {
|
||||||
const documentId = apiTask.related_document;
|
const documentId = apiTask.documentId;
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[Postprocessing] Task ${t.TaskId} gestartet. DocumentID: ${documentId ?? 'nicht vorhanden'}`,
|
`[Postprocessing] Task ${t.TaskId} gestartet. DocumentID: ${documentId ?? 'nicht vorhanden'}`,
|
||||||
);
|
);
|
||||||
@@ -241,16 +244,13 @@ export class PaperlessTaskProcessorService {
|
|||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[Postprocessing] Task ${t.TaskId} - Verarbeite als Anlage zu ParentTask ${parentTask.TaskId}`,
|
`[Postprocessing] Task ${t.TaskId} - Verarbeite als Anlage zu ParentTask ${parentTask.TaskId}`,
|
||||||
);
|
);
|
||||||
const parentPaperlessTasks = await this.paperlessService.getTask(
|
const apiParentTask = await this.paperlessService.getTask(
|
||||||
parentTask.TaskId,
|
parentTask.TaskId,
|
||||||
);
|
);
|
||||||
const apiParentTask = Array.isArray(parentPaperlessTasks)
|
|
||||||
? parentPaperlessTasks[0]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (apiParentTask && apiParentTask.related_document) {
|
if (apiParentTask?.documentId) {
|
||||||
const parentDoc = await this.paperlessService.getDocument(
|
const parentDoc = await this.paperlessService.getDocument(
|
||||||
apiParentTask.related_document,
|
apiParentTask.documentId,
|
||||||
);
|
);
|
||||||
if (parentDoc) {
|
if (parentDoc) {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { normalizePaperlessTask } from './paperless.service';
|
||||||
|
|
||||||
|
describe('normalizePaperlessTask', () => {
|
||||||
|
it('API v10: erfolgreicher Consume liefert documentId aus result_data', () => {
|
||||||
|
const t = normalizePaperlessTask({
|
||||||
|
task_id: 'abc',
|
||||||
|
task_type: 'consume_file',
|
||||||
|
status: 'success',
|
||||||
|
date_done: '2026-10-30T16:16:00.091045+01:00',
|
||||||
|
result_data: { document_id: 4711 },
|
||||||
|
related_document_ids: [4711],
|
||||||
|
});
|
||||||
|
expect(t).toEqual({
|
||||||
|
taskId: 'abc',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
documentId: 4711,
|
||||||
|
duplicateOf: null,
|
||||||
|
message: null,
|
||||||
|
dateDone: '2026-10-30T16:16:00.091045+01:00',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('API v10: Duplikat ist FAILURE ohne documentId, mit duplicateOf', () => {
|
||||||
|
const t = normalizePaperlessTask({
|
||||||
|
task_id: 'abc',
|
||||||
|
status: 'failure',
|
||||||
|
result_data: { duplicate_of: 12 },
|
||||||
|
related_document_ids: [12],
|
||||||
|
});
|
||||||
|
expect(t.status).toBe('FAILURE');
|
||||||
|
expect(t.documentId).toBeNull();
|
||||||
|
expect(t.duplicateOf).toBe(12);
|
||||||
|
expect(t.message).toContain('12');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('API v10: Fehler übernimmt error_message', () => {
|
||||||
|
const t = normalizePaperlessTask({
|
||||||
|
task_id: 'abc',
|
||||||
|
status: 'failure',
|
||||||
|
result_data: {
|
||||||
|
error_type: 'ConsumerError',
|
||||||
|
error_message: 'ASN existiert',
|
||||||
|
},
|
||||||
|
related_document_ids: [],
|
||||||
|
});
|
||||||
|
expect(t.status).toBe('FAILURE');
|
||||||
|
expect(t.documentId).toBeNull();
|
||||||
|
expect(t.message).toBe('ASN existiert');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('API v10: laufende Task ohne Ergebnis', () => {
|
||||||
|
const t = normalizePaperlessTask({
|
||||||
|
task_id: 'abc',
|
||||||
|
status: 'started',
|
||||||
|
result_data: null,
|
||||||
|
related_document_ids: [],
|
||||||
|
date_done: null,
|
||||||
|
});
|
||||||
|
expect(t.status).toBe('STARTED');
|
||||||
|
expect(t.documentId).toBeNull();
|
||||||
|
expect(t.message).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Legacy-API v9: related_document und result', () => {
|
||||||
|
const t = normalizePaperlessTask({
|
||||||
|
task_id: 'abc',
|
||||||
|
status: 'SUCCESS',
|
||||||
|
result: 'Success. New document id 55 created',
|
||||||
|
related_document: '55',
|
||||||
|
date_done: '2026-10-30T16:16:00+01:00',
|
||||||
|
});
|
||||||
|
expect(t.status).toBe('SUCCESS');
|
||||||
|
expect(t.documentId).toBe(55);
|
||||||
|
expect(t.message).toBe('Success. New document id 55 created');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,6 +5,55 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import FormData = require('form-data');
|
import FormData = require('form-data');
|
||||||
|
|
||||||
|
export interface PaperlessTaskStatus {
|
||||||
|
taskId: string;
|
||||||
|
status: 'PENDING' | 'STARTED' | 'SUCCESS' | 'FAILURE' | 'REVOKED';
|
||||||
|
/** Neu angelegtes Dokument (nur bei Erfolg) */
|
||||||
|
documentId: number | null;
|
||||||
|
/** Bereits vorhandenes Dokument, falls Paperless ein Duplikat abgelehnt hat */
|
||||||
|
duplicateOf: number | null;
|
||||||
|
/** Fehlermeldung bzw. Ergebnistext */
|
||||||
|
message: string | null;
|
||||||
|
dateDone: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bildet eine Task aus API v10 (`result_data`, `related_document_ids`) oder
|
||||||
|
* der Legacy-API v9 (`result`, `related_document`) auf PaperlessTaskStatus ab.
|
||||||
|
*/
|
||||||
|
export function normalizePaperlessTask(raw: any): PaperlessTaskStatus {
|
||||||
|
const resultData =
|
||||||
|
raw.result_data && typeof raw.result_data === 'object'
|
||||||
|
? raw.result_data
|
||||||
|
: null;
|
||||||
|
const toId = (v: unknown): number | null =>
|
||||||
|
v === null || v === undefined || v === '' ? null : Number(v);
|
||||||
|
|
||||||
|
const duplicateOf = toId(resultData?.duplicate_of);
|
||||||
|
const documentId =
|
||||||
|
toId(resultData?.document_id) ??
|
||||||
|
toId(raw.related_document) ??
|
||||||
|
(duplicateOf === null ? toId(raw.related_document_ids?.[0]) : null);
|
||||||
|
|
||||||
|
let message: string | null = null;
|
||||||
|
if (resultData?.error_message) message = String(resultData.error_message);
|
||||||
|
else if (duplicateOf !== null)
|
||||||
|
message = `Duplikat von Dokument ${duplicateOf}`;
|
||||||
|
else if (typeof raw.result_data === 'string') message = raw.result_data;
|
||||||
|
else if (raw.result != null) message = String(raw.result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
taskId: raw.task_id,
|
||||||
|
status: String(
|
||||||
|
raw.status ?? '',
|
||||||
|
).toUpperCase() as PaperlessTaskStatus['status'],
|
||||||
|
documentId,
|
||||||
|
duplicateOf,
|
||||||
|
message,
|
||||||
|
dateDone: raw.date_done ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PaperlessService {
|
export class PaperlessService {
|
||||||
private readonly logger = new Logger(PaperlessService.name);
|
private readonly logger = new Logger(PaperlessService.name);
|
||||||
@@ -175,11 +224,19 @@ export class PaperlessService {
|
|||||||
return response.data.results;
|
return response.data.results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTask(taskId: string): Promise<any> {
|
/**
|
||||||
|
* Liefert den Status einer Paperless-Task oder null, falls unbekannt.
|
||||||
|
* Paperless ≥ 3.0 (API v10) antwortet paginiert mit kleingeschriebenem
|
||||||
|
* Status, `result_data` und `related_document_ids` — hier auf ein festes
|
||||||
|
* Format normalisiert, damit die Aufrufer unabhängig von der API-Version sind.
|
||||||
|
*/
|
||||||
|
async getTask(taskId: string): Promise<PaperlessTaskStatus | null> {
|
||||||
const response = await this.client.get('/tasks/', {
|
const response = await this.client.get('/tasks/', {
|
||||||
params: { task_id: taskId },
|
params: { task_id: taskId },
|
||||||
});
|
});
|
||||||
return response.data;
|
const data = response.data;
|
||||||
|
const raw = Array.isArray(data) ? data[0] : data?.results?.[0];
|
||||||
|
return raw ? normalizePaperlessTask(raw) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCorrespondentByName(name: string): Promise<any> {
|
async getCorrespondentByName(name: string): Promise<any> {
|
||||||
@@ -311,7 +368,8 @@ export class PaperlessService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Liefert die Paperless-Doc-ID des passenden Dokuments oder null.
|
* Liefert die Paperless-Doc-ID des passenden Dokuments oder null.
|
||||||
* Paperless kann den custom_fields-Filter ignorieren — daher manuell verifizieren.
|
* Filtert serverseitig per `custom_field_query`; die Treffer werden zusätzlich
|
||||||
|
* case-insensitiv verifiziert (Paperless kennt für Textfelder kein `iexact`).
|
||||||
*/
|
*/
|
||||||
async findDocumentIdByCustomField(
|
async findDocumentIdByCustomField(
|
||||||
fieldId: number,
|
fieldId: number,
|
||||||
@@ -319,7 +377,7 @@ export class PaperlessService {
|
|||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const response = await this.client.get('/documents/', {
|
const response = await this.client.get('/documents/', {
|
||||||
params: {
|
params: {
|
||||||
[`custom_fields__${fieldId}__value__iexact`]: value,
|
custom_field_query: JSON.stringify([fieldId, 'icontains', value]),
|
||||||
page_size: 25,
|
page_size: 25,
|
||||||
truncate_content: true,
|
truncate_content: true,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user