Integration: Stubs von L2/L3/L4/L5 gegen echte Services getauscht

- Aufträge: createWorkOrder/transitionWorkOrder/getCompletionBlockers aus L2
- Dokumente: storeFile aus L1; neu services/documents/read.ts (readStoredBytes,
  readDocumentBytes mit Prüfsummenprüfung); L2-Upload nutzt zentrale Ablage
- Dubletten aus L1 (findDuplicateCustomers(ctx)); Objekt-Kandidaten als
  imports/site-candidates.ts; Dateityp-Erkennung mobil als field/mime.ts
- Objekt-Historie mobil als Adapter auf L1 getSiteHistory, PDF über /api/v1/reports/:id/pdf
- L4-Upload-Idempotenz: fester Upload-Lineage nach storeFile, Race → Soft-Delete + Replay
- PDF-Worker-Kontext: document:write zum Ablegen des Berichts-PDF
- Import: doppeltes Work-Order-Audit entfernt; bestätigte Aufträge starten planned
- Dateilinks in Auftragsdetail auf /files/[documentId]
- proxy: /api/v1 ohne Session → 401 JSON statt Login-Redirect
- ARCHITEKTUR §2: Objekt-Historie für Feldrollen (freigegebene Einsätze aller Teams)

Gate: tsc, lint, build, 42/42 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 12:49:19 +02:00
co-authored by Claude Opus 5
parent 2984cc18d3
commit 3883296300
37 changed files with 216 additions and 798 deletions
@@ -3,7 +3,7 @@ import type { JobPayload } from "@/server/jobs/queues";
import type { ServiceCtx } from "@/server/services/context";
import { getExtractionProvider } from "@/server/ai/extraction/anthropic";
import { processImport } from "@/server/services/imports/process";
import { readDocumentBytes } from "@/server/services/imports/document-store-stub";
import { readStoredBytes } from "@/server/services/documents/read";
/**
* BullMQ processor for "import-extraction" (ARCHITEKTUR §4.4). System context: tenant from the
@@ -20,6 +20,6 @@ export async function process(payload: JobPayload): Promise<void> {
};
await processImport(ctx, payload.entityId, {
provider: getExtractionProvider(),
loadBytes: (doc) => readDocumentBytes(doc.storageKey),
loadBytes: (doc) => readStoredBytes(doc.storageKey),
});
}
+3 -2
View File
@@ -5,14 +5,15 @@ import type { JobPayload } from "../queues";
/**
* Queue "report-pdf": renders the PDF of an approved report (lane L5).
* System context: tenant-bound db, read access to all reports of the tenant — nothing else.
* System context: tenant-bound db, read access to all reports of the tenant, and storing the PDF document.
*/
export async function process(payload: JobPayload): Promise<void> {
const ctx: ServiceCtx = {
db: dbForTenant(payload.tenantId),
tenantId: payload.tenantId,
userId: payload.actorId ?? "system",
permissions: new Set(["report:read", "work_order:read_all", "document:read_internal"]),
// document:write: the system stores the rendered PDF via the central document service.
permissions: new Set(["report:read", "work_order:read_all", "document:read_internal", "document:write"]),
};
const res = await generateReportPdf(ctx, payload.entityId);
console.info(`[report-pdf] ${payload.entityId}: ${res.skipped ? "already rendered" : `stored ${res.documentId}`}`);