feat(07-01): install DKV deps, add Prisma models, wire ScheduleModule + crypto export
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 37s
Tessera CI/CD / Tests (push) Waiting to run

- pnpm add imapflow@^1.4.3, pdf-parse@^2.4.5, xlsx@^0.18.5 to @tessera/api
- Append DkvModuleConfig, DkvVehicleMaster, DkvInvoiceHistory, SmtpConfig models to schema.prisma (15 models total)
- Add ScheduleModule.forRoot() to AppModule imports (prerequisite for DkvSchedulerService)
- Export CalendarCryptoService from CalendarModule (needed by DkvModule + SettingsModule)
- Create apps/api/src/dkv/dkv.types.ts with DkvVehicleBlock, DkvTransaction, InboxConfig, InboxEmail, InboxAttachment, ExportRow interfaces
This commit is contained in:
2026-06-26 19:25:42 +02:00
parent de06794e67
commit c4b39ccd1b
6 changed files with 527 additions and 8 deletions
+4 -1
View File
@@ -27,15 +27,18 @@
"class-validator": "^0.15.1",
"cookie-parser": "^1.4.7",
"ews-javascript-api": "0.15.3",
"imapflow": "^1.4.3",
"ldapts": "^8.1.8",
"node-ical": "0.26.1",
"nodemailer": "^9.0.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pdf-parse": "^2.4.5",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.0.0",
"tsdav": "2.2.2"
"tsdav": "2.2.2",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@nestjs/cli": "^11.0.0",
+63
View File
@@ -168,3 +168,66 @@ model CalendarSource {
@@index([userId])
@@index([tenantId])
}
model DkvModuleConfig {
id String @id @default(uuid())
tenantId String @unique
protocol String @default("imap") // 'imap' | 'exchange'
host String?
port Int?
encryption String @default("ssl-tls") // 'none' | 'starttls' | 'ssl-tls'
folder String @default("INBOX")
senderFilter String?
pollIntervalMin Int @default(60)
isActive Boolean @default(false)
exportRecipient String?
vehicleFormatString String @default("{Marke}/{Modell}/{Kennzeichen}")
encryptedInboxCreds String? // AES-256-GCM: JSON { username, password } encrypted
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([tenantId])
}
model DkvVehicleMaster {
id String @id @default(uuid())
tenantId String
kennzeichen String
marke String
modell String
fahrer String // "Vorname Nachname"
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([tenantId, kennzeichen])
@@index([tenantId])
}
model DkvInvoiceHistory {
id String @id @default(uuid())
tenantId String
datumZeit DateTime @default(now())
rechnungsnummer String
anzahlFahrzeuge Int @default(0)
anzahlTransaktionen Int @default(0)
status String // 'Verarbeitet' | 'Fehler' | 'Versand fehlgeschlagen'
errorMessage String?
exportFilename String?
createdAt DateTime @default(now())
@@index([tenantId])
@@index([datumZeit])
}
model SmtpConfig {
id String @id @default(uuid())
tenantId String @unique
host String
port Int @default(587)
encryption String @default("starttls") // 'none' | 'starttls' | 'ssl-tls'
username String?
encryptedPassword String? // AES-256-GCM via CalendarCryptoService
fromAddress String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
+2
View File
@@ -1,6 +1,7 @@
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { ScheduleModule } from '@nestjs/schedule';
import { AuthModule } from './auth/auth.module';
import { JwtAuthGuard } from './auth/guards/jwt-auth.guard';
import { RolesGuard } from './auth/guards/roles.guard';
@@ -20,6 +21,7 @@ import { UserModule } from './user/user.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ScheduleModule.forRoot(),
PrismaModule,
AuthModule,
UserModule,
+1 -1
View File
@@ -28,6 +28,6 @@ import { ExchangeProvider } from './providers/exchange.provider';
ICSProvider,
ExchangeProvider,
],
exports: [CalendarService],
exports: [CalendarService, CalendarCryptoService],
})
export class CalendarModule {}
+101
View File
@@ -0,0 +1,101 @@
/**
* Shared types for the DKV Fleet Module.
*
* These interfaces are the single source of truth for data shapes exchanged
* between the parser, scheduler, export, and mail services.
*/
/**
* A single fuel transaction row extracted from a DKV E-Rechnung PDF.
*/
export interface DkvTransaction {
/** Date string in German format "DD.MM.YYYY" */
lieferdatum: string;
/** Service station city / location */
ort: string;
/** Odometer reading at time of fuelling (numeric, German thousands-dots stripped) */
kilometerstand: number;
/** Product name (e.g., "Diesel", "AdBlue") */
produkt: string;
/** Quantity dispensed (German decimal notation converted to JS float) */
menge: number;
/** Unit of measure (e.g., "L") */
einheit: string;
/** Net amount in EUR */
netto: number;
/** Gross amount in EUR */
brutto: number;
}
/**
* A vehicle section extracted from a DKV E-Rechnung PDF.
* Each block corresponds to one VEHICLE: ... CARD NO.: ... section.
*/
export interface DkvVehicleBlock {
/** License plate (Kennzeichen), e.g. "GP-JL 728E" */
kennzeichen: string;
/** DKV card number */
cardNumber: string;
/** All fuel transactions for this vehicle in the invoice */
transactions: DkvTransaction[];
}
/**
* Inbox connection configuration (supports IMAP and Exchange).
* Mirrors DkvModuleConfig from Prisma but with decrypted credentials.
*/
export interface InboxConfig {
/** 'imap' | 'exchange' */
protocol: string;
host: string;
port: number;
/** Optional username (may be empty for anonymous or Exchange-with-integrated-auth) */
username?: string;
/** Decrypted password (never persisted in this form) */
password?: string;
/** 'none' | 'starttls' | 'ssl-tls' */
encryption: string;
/** IMAP folder to monitor (e.g., "INBOX") */
folder: string;
/** Sender filter: only process emails from this address */
senderFilter?: string;
}
/**
* A single email attachment, already downloaded as a Buffer.
*/
export interface InboxAttachment {
filename: string;
contentType: string;
buffer: Buffer;
}
/**
* A fetched email message with its PDF attachments.
*/
export interface InboxEmail {
/** IMAP UID or Exchange item ID */
uid: number | string;
/** RFC 2822 Message-ID header */
messageId: string;
subject: string;
from: string;
date: Date;
attachments: InboxAttachment[];
}
/**
* A single row in the Excel export (5 columns per D-13).
*/
export interface ExportRow {
/** "DD.MM.YYYY" */
lieferdatum: string;
/** Resolved from format string, e.g. "{Marke}/{Modell}/{Kennzeichen}" */
fahrzeug: string;
/** "Vorname Nachname" from vehicle master */
fahrer: string;
/** Service station city from PDF */
ort: string;
/** Numeric odometer reading */
kilometerstand: number;
}