Files
schalli 316f8353b7
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 38s
Tessera CI/CD / Tests (push) Waiting to run
docs(07-04): complete DKV integration plane plan
SUMMARY: DkvService pipeline, DkvSchedulerService dynamic cron, DkvController 12 routes,
DkvModule registry seed, AppModule DkvModule registration, dkvFleet + settings i18n keys.
Deviation: CronJob via require() workaround (pnpm transitive dep isolation).
2026-06-27 00:32:19 +02:00

10 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
07-dkv-fleet-module 04 dkv-integration-plane
dkv
nestjs
scheduler
cron
pipeline
orchestration
rest
i18n
module-registry
requires provides affects
DkvParserService (07-01)
ImapProvider + ExchangeInboxProvider (07-02)
DkvExportService + DkvMailService + SettingsModule (07-03)
CalendarModule (CalendarCryptoService)
DkvService
DkvSchedulerService
DkvController
DkvModule
dkv.seed.ts
dkvFleet i18n namespace
settings.categorySmtp i18n keys
apps/api/src/dkv/dkv.service.ts
apps/api/src/dkv/dkv-scheduler.service.ts
apps/api/src/dkv/dkv.controller.ts
apps/api/src/dkv/dkv.seed.ts
apps/api/src/dkv/dkv.module.ts
apps/api/src/app.module.ts
apps/web/src/messages/de.json
apps/web/src/messages/en.json
added patterns
Single-flight guard: private processing=false in DkvService.processInbox (Pitfall 7)
D-10 parse retry: 3 attempts, on final failure record Fehler history row with errorMessage
D-16 SMTP retry: 3 attempts, exponential backoff 2s/4s, on final failure record Versand fehlgeschlagen
CONFIG_SAFE_SELECT const excludes encryptedInboxCreds from all API responses (T-07-12)
getExportFile whitelist regex ^DKV_[\w-]+.xlsx$ blocks path traversal (T-07-09)
CronJob via require() workaround: cron is transitive dep of @nestjs/schedule under pnpm strict isolation
DkvController coordinates scheduler after PUT /dkv/config (no circular DkvService <-> DkvScheduler dep)
DkvModule imports CalendarModule to get CalendarCryptoService without re-declaring it
Invoice number extracted from email subject via regex /\d{2}-\d{9}-\d{3}/; falls back to email-{uid}
created modified
apps/api/src/dkv/dkv.service.ts
apps/api/src/dkv/dkv-scheduler.service.ts
apps/api/src/dkv/dkv.controller.ts
apps/api/src/dkv/dkv.seed.ts
apps/api/src/dkv/dkv.module.ts
apps/api/src/app.module.ts
apps/web/src/messages/de.json
apps/web/src/messages/en.json
DkvScheduler v1: findFirst() loads first active config (single-tenant); full per-tenant scheduling deferred
Circular dep avoided: DkvController injects both DkvService + DkvSchedulerService; DkvService does NOT inject scheduler
rechnungsnummer: extracted from email subject via /\d{2}-\d{9}-\d{3}/ regex; fallback=email-{uid}
invoiceMonth: derived from first lieferdatum (DD.MM.YYYY→YYYY-MM); fallback=current month
Unknown plates export with empty Fahrer + resolveFahrzeug applied with empty marke/modell (consistent with format string)
cron package imported via require() not import: pnpm strict isolation prevents direct import of transitive deps
DkvModule imports CalendarModule (exported CalendarCryptoService) not re-declared in providers
duration completed tasks files_created files_modified
7min 2026-06-26T22:30:00Z 3 5 3

Phase 07 Plan 04: DKV Integration Plane — Pipeline + REST + Module Summary

DkvService orchestrates the full poll→parse→map→export→send→history pipeline with single-flight guard, 3-retry parse (D-10), and 3-retry SMTP with exponential backoff (D-16). DkvSchedulerService drives configurable polling via SchedulerRegistry dynamic cron. DkvController exposes all 12 ADMIN-only /dkv/* routes. DkvModule self-registers in the module registry. Complete dkvFleet i18n namespace and settings.smtp/categorySmtp/categoryGeneral keys added — Wave 3 frontend plans can run without touching message files.

Tasks Completed

Task Name Commit Key Files
1 DkvService — pipeline orchestration + vehicle/config/history c40a023 dkv.service.ts (305 LOC)
2 DkvSchedulerService + DkvController c22d367 dkv-scheduler.service.ts, dkv.controller.ts
3 DkvModule + seed + AppModule + i18n 2a3d1c1 dkv.module.ts, dkv.seed.ts, app.module.ts, de.json, en.json

Architecture Notes

Pipeline Orchestration (DkvService)

processInbox(tenantId)
  └── single-flight guard (this.processing flag)
  └── load raw config + decrypt inbox creds (T-05-13: never log)
  └── select provider by config.protocol (imap | exchange)
  └── provider.fetchPdfAttachments(inboxConfig)
  └── for each email.attachments:
        └── 3-retry parsePdf (D-10: Fehler row on final fail)
        └── batch load DkvVehicleMaster for tenant
        └── build ExportRow[] (resolveFahrzeug + driver lookup)
        └── DkvExportService.buildExcelBuffer + writeAndPrune
        └── 3-retry DkvMailService.sendExportEmail (D-16)
              └── exponential backoff: 2s, 4s
              └── on fail: Versand fehlgeschlagen (file stays available)
        └── prisma.dkvInvoiceHistory.create (D-20)

Scheduler Multi-Tenant Decision (v1)

For v1, DkvSchedulerService.onModuleInit() calls dkvService.loadConfig() without a tenantId — which uses findFirst() to load the first active DkvModuleConfig row. One cron job is registered for that tenant's interval.

Rationale: Single-tenant deployments are the v1 target. Multi-tenant scheduling (one SchedulerRegistry job per active tenant, keyed as dkv-inbox-poll-{tenantId}) is deferred to a future plan.

Impact: Administrators with multiple tenants must set the schedule per-tenant; only the first active config is polled automatically.

Circular Dependency Avoidance

DkvSchedulerService injects DkvService (to call processInbox). If DkvService also injected DkvSchedulerService (to re-apply the interval after saveConfig), this would create a circular dependency.

Decision: DkvController injects both services independently. After PUT /dkv/config, the controller calls dkvScheduler.setInterval(...) or dkvScheduler.stopJob(). Neither service injects the other.

cron Package Resolution (pnpm strict isolation)

CronJob is from the cron@4.4.0 package, which is a transitive dependency of @nestjs/schedule@6.1.3. Under pnpm strict isolation, transitive packages are not directly importable — import { CronJob } from 'cron' fails TypeScript's module resolution.

Fix: CronJob is resolved at runtime via require('cron').CronJob cast to a minimal interface { start(): void }. The schedulerRegistry.addCronJob() call uses as any cast. At runtime, the object IS a full CronJob — SchedulerRegistry only calls .stop() on it. No new package install was needed (cron is already on disk).

Invoice Number Extraction

DKV invoice numbers follow the pattern \d{2}-\d{9}-\d{3} (e.g., 26-651566449-001). DkvService extracts this from email.subject via regex. Fallback when not found: email-{uid} using the IMAP UID or EWS item ID. This fallback still produces a unique, meaningful history record and filename.

Verification Results

  • pnpm --filter @tessera/api type-check exits 0: PASS (3x verified during execution)
  • grep -q "processing" dkv.service.ts: PASS (single-flight guard present)
  • grep -q "encryptedInboxCreds" dkv.service.ts: PASS (CONFIG_SAFE_SELECT + encryption handling)
  • grep -Eq "parsePdf|buildExcelBuffer|sendExportEmail" dkv.service.ts: PASS
  • grep -q "schedulerRegistry.addCronJob" dkv-scheduler.service.ts: PASS
  • grep -q "@Controller('dkv')" dkv.controller.ts: PASS
  • grep -q "FileInterceptor" dkv.controller.ts: PASS
  • 12 handlers with @Roles(Role.ADMIN, Role.SUPER_ADMIN): PASS
  • grep -q "DkvModule" app.module.ts: PASS
  • grep -q "seedDkvModule" dkv.module.ts: PASS
  • grep -q "OnModuleInit" dkv.module.ts: PASS
  • i18n node validation (dkvFleet + settings.categorySmtp + settings.smtp): PASS

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] cron package not directly importable under pnpm strict isolation

  • Found during: Task 2 type-check
  • Issue: import { CronJob } from 'cron' failed with TS2307 because cron@4.4.0 is a transitive dependency of @nestjs/schedule, not declared in apps/api/package.json. pnpm strict isolation prevents phantom dependency imports.
  • Fix: Replaced import { CronJob } from 'cron' with require('cron').CronJob cast to a minimal { start(): void } interface. The schedulerRegistry.addCronJob() call uses as any to satisfy the CronJob<null,null> type expected by SchedulerRegistry. Functionally identical at runtime.
  • Files modified: apps/api/src/dkv/dkv-scheduler.service.ts
  • Commit: c22d367 (included in task commit)

Architectural Decision

DkvScheduler → saveConfig coordination: The plan's action text said "after save calls the scheduler to (re)apply the interval". This could have been implemented as DkvService injecting DkvSchedulerService, but that creates a circular dependency (scheduler injects DkvService for processInbox). Instead, DkvController coordinates both services after PUT /dkv/config. This is a cleaner design with no NestJS forwardRef() workaround needed.

Known Stubs

None. All pipeline orchestration, vehicle CRUD, CSV import, history, config and export download logic is fully implemented. The frontend Wave 3 plans (05, 06) can call all /dkv/* endpoints.

Threat Flags

None. All STRIDE threats in this plan's threat register were mitigated:

  • T-07-12: CONFIG_SAFE_SELECT excludes encryptedInboxCreds in all loadConfig/saveConfig responses
  • T-05-13: Decrypted inbox credentials used only within method scope; never logged (generic error messages)
  • T-07-09: getExportFile validates filename against /^DKV_[\w\-]+\.xlsx$/ before fs.readFileSync; rejects path separators and ..
  • V4: All 12 DkvController handlers carry @Roles(Role.ADMIN, Role.SUPER_ADMIN)

Self-Check: PASSED

Files verified present:

  • apps/api/src/dkv/dkv.service.ts ✓
  • apps/api/src/dkv/dkv-scheduler.service.ts ✓
  • apps/api/src/dkv/dkv.controller.ts ✓
  • apps/api/src/dkv/dkv.seed.ts ✓
  • apps/api/src/dkv/dkv.module.ts ✓
  • apps/api/src/app.module.ts (modified) ✓
  • apps/web/src/messages/de.json (modified) ✓
  • apps/web/src/messages/en.json (modified) ✓

Commits verified in git log:

  • c40a023 ✓ (feat(07-04): DkvService — pipeline orchestration + vehicle/config/history logic)
  • c22d367 ✓ (feat(07-04): DkvSchedulerService + DkvController — dynamic cron + REST surface)
  • 2a3d1c1 ✓ (feat(07-04): DkvModule + registry seed + AppModule registration + i18n keys)