feat(07-04): DkvModule + registry seed + AppModule registration + i18n keys
Tessera CI/CD / Build & Deploy (push) Blocked by required conditions
Tessera CI/CD / Lint & Type Check (push) Successful in 40s
Tessera CI/CD / Tests (push) Waiting to run

- dkv.seed.ts: seedDkvModule with slug=dkv-fleet, category=fleet, isSystem=true
- dkv.module.ts: imports ModuleRegistryModule + CalendarModule (CalendarCryptoService)
  provides all 7 DKV services + 2 providers + controller; OnModuleInit seeds registry
- app.module.ts: DkvModule added to imports (ScheduleModule + SettingsModule from Plans 01/03)
- de.json + en.json: complete dkvFleet namespace (50+ keys incl. status, col, form, errors)
- settings namespace extended with categoryGeneral, categorySmtp, smtp sub-object
This commit is contained in:
2026-06-27 00:30:08 +02:00
parent c22d36758c
commit 2a3d1c1e85
5 changed files with 290 additions and 2 deletions
+2
View File
@@ -11,6 +11,7 @@ import { LdapModule } from './ldap/ldap.module';
import { MailModule } from './mail/mail.module'; import { MailModule } from './mail/mail.module';
import { CalendarModule } from './calendar/calendar.module'; import { CalendarModule } from './calendar/calendar.module';
import { DashboardModule } from './dashboard/dashboard.module'; import { DashboardModule } from './dashboard/dashboard.module';
import { DkvModule } from './dkv/dkv.module';
import { DomaincheckModule } from './domaincheck/domaincheck.module'; import { DomaincheckModule } from './domaincheck/domaincheck.module';
import { ModuleRegistryModule } from './module-registry/module-registry.module'; import { ModuleRegistryModule } from './module-registry/module-registry.module';
import { PrismaModule } from './prisma/prisma.module'; import { PrismaModule } from './prisma/prisma.module';
@@ -35,6 +36,7 @@ import { UserModule } from './user/user.module';
DashboardModule, DashboardModule,
CalendarModule, CalendarModule,
SettingsModule, SettingsModule,
DkvModule,
], ],
providers: [ providers: [
// Global JWT guard: all routes require auth unless @Public() // Global JWT guard: all routes require auth unless @Public()
+70
View File
@@ -0,0 +1,70 @@
import { Logger, Module, OnModuleInit } from '@nestjs/common';
import { ModuleRegistryModule } from '../module-registry/module-registry.module';
import { ModuleRegistryService } from '../module-registry/module-registry.service';
import { CalendarModule } from '../calendar/calendar.module';
import { DkvController } from './dkv.controller';
import { DkvExportService } from './dkv-export.service';
import { DkvMailService } from './dkv-mail.service';
import { DkvParserService } from './dkv-parser.service';
import { DkvSchedulerService } from './dkv-scheduler.service';
import { DkvService } from './dkv.service';
import { ExchangeInboxProvider } from './providers/exchange-inbox.provider';
import { ImapProvider } from './providers/imap.provider';
import { seedDkvModule } from './dkv.seed';
/**
* NestJS module for the DKV Fleet feature.
*
* Provides the full DKV processing pipeline:
* - DkvService: orchestration (poll → parse → export → send → history)
* - DkvSchedulerService: dynamic cron job via SchedulerRegistry
* - DkvParserService: pdf-parse v2 text extraction + DKV-specific regex
* - DkvExportService: xlsx buffer generation + user-files/ management
* - DkvMailService: nodemailer.createTransport() at send time
* - ImapProvider / ExchangeInboxProvider: inbox access providers
*
* Imports:
* - ModuleRegistryModule: for registry self-seed on init
* - CalendarModule: provides CalendarCryptoService (AES-256-GCM encryption)
* re-exported from CalendarModule.exports — no re-declaration needed here.
*
* PrismaModule is global (no explicit import needed).
* ScheduleModule.forRoot() was registered in AppModule by Plan 01.
* SettingsModule (for DkvMailService → SettingsService) is imported in AppModule.
*
* Seeds itself into the module registry on application startup via
* OnModuleInit lifecycle hook — same pattern as DomaincheckModule.
*/
@Module({
imports: [
ModuleRegistryModule,
CalendarModule, // Provides CalendarCryptoService for inbox credential encryption
],
controllers: [DkvController],
providers: [
DkvService,
DkvSchedulerService,
DkvParserService,
DkvExportService,
DkvMailService,
ImapProvider,
ExchangeInboxProvider,
],
exports: [DkvService],
})
export class DkvModule implements OnModuleInit {
private readonly logger = new Logger(DkvModule.name);
constructor(
private readonly moduleRegistryService: ModuleRegistryService,
) {}
async onModuleInit(): Promise<void> {
try {
await seedDkvModule(this.moduleRegistryService);
this.logger.log('DKV Fleet module seeded in registry');
} catch (error) {
this.logger.error('Failed to seed DKV Fleet module', error);
}
}
}
+26
View File
@@ -0,0 +1,26 @@
import { ModuleRegistryService } from '../module-registry/module-registry.service';
/**
* Seeds the DKV Fleet module into the module registry.
*
* Called during DkvModule initialization (OnModuleInit) to ensure
* the 'dkv-fleet' module record exists in the database.
*
* Category 'fleet' identifies this as a fleet-management module.
* isSystem: true — not tenant-licensable in v1; available to all tenants.
*/
export async function seedDkvModule(
moduleRegistryService: ModuleRegistryService,
): Promise<void> {
await moduleRegistryService.seedModule({
slug: 'dkv-fleet',
name: 'DKV Flotte',
version: '1.0.0',
category: 'fleet',
description: {
de: 'DKV-Tankkartenrechnungen automatisch verarbeiten',
en: 'Automatically process DKV fuel card invoices',
},
isSystem: true,
});
}
+96 -1
View File
@@ -113,11 +113,25 @@
"categoryDashboard": "Dashboard", "categoryDashboard": "Dashboard",
"categoryWidgets": "Widgets", "categoryWidgets": "Widgets",
"categoryCalendar": "Kalender", "categoryCalendar": "Kalender",
"categoryGeneral": "Allgemein",
"categorySmtp": "SMTP",
"sourceDeleteConfirm": "Moechten Sie diese Kalenderquelle wirklich loeschen?", "sourceDeleteConfirm": "Moechten Sie diese Kalenderquelle wirklich loeschen?",
"sourceDeleteCta": "Quelle loeschen", "sourceDeleteCta": "Quelle loeschen",
"providerDeleteConfirm": "Moechten Sie diesen Suchanbieter wirklich loeschen?", "providerDeleteConfirm": "Moechten Sie diesen Suchanbieter wirklich loeschen?",
"providerDeleteCta": "Anbieter loeschen", "providerDeleteCta": "Anbieter loeschen",
"cancel": "Abbrechen" "cancel": "Abbrechen",
"smtp": {
"title": "SMTP-Konfiguration",
"host": "Host",
"port": "Port",
"encryption": "Verschluesselung",
"username": "Benutzername",
"password": "Passwort",
"fromAddress": "Absenderadresse (Von)",
"fromAddressHelp": "Die E-Mail-Adresse, die als Absender angezeigt wird.",
"save": "Einstellungen speichern",
"test": "Verbindung testen"
}
}, },
"widgets": { "widgets": {
"emptyHeading": "Keine Widgets aktiv", "emptyHeading": "Keine Widgets aktiv",
@@ -237,6 +251,87 @@
"checking": "Pruefe...", "checking": "Pruefe...",
"error": "Fehler bei der Pruefung" "error": "Fehler bei der Pruefung"
}, },
"dkvFleet": {
"pageTitle": "DKV Flotte",
"checkNow": "Jetzt pruefen",
"checking": "Wird geprueft...",
"lastChecked": "Zuletzt geprueft: {time} Uhr",
"neverChecked": "Noch nie geprueft",
"settingsTitle": "Moduleinstellungen",
"historyTitle": "Verarbeitungshistorie",
"exportFilesTitle": "Letzte Exportdateien",
"emptyHistory": "Noch keine Rechnungen verarbeitet",
"emptyHistoryBody": "Klicke auf 'Jetzt pruefen', um den Posteingang zu pruefen. Neue Rechnungen erscheinen hier automatisch.",
"emptyExports": "Noch keine Exportdateien vorhanden",
"emptyVehicles": "Keine Fahrzeuge hinterlegt",
"emptyVehiclesBody": "Fuege Fahrzeuge manuell hinzu oder lade eine CSV-Datei hoch.",
"status": {
"processed": "Verarbeitet",
"error": "Fehler",
"sendFailed": "Versand fehlgeschlagen"
},
"col": {
"dateTime": "Datum/Zeit",
"invoiceNumber": "Rechnungsnummer",
"vehicles": "Fahrzeuge",
"transactions": "Transaktionen",
"status": "Status",
"exportFile": "Exportdatei",
"licensePlate": "Kennzeichen",
"brand": "Marke",
"model": "Modell",
"driver": "Fahrer",
"actions": "Aktionen"
},
"addVehicle": "Fahrzeug hinzufuegen",
"editVehicle": "Fahrzeug bearbeiten",
"deleteVehicle": "Fahrzeug loeschen",
"deleteVehicleBody": "Das Fahrzeug {plate} wird dauerhaft aus der Stammdatenliste entfernt. Diese Aktion kann nicht rueckgaengig gemacht werden.",
"importCsv": "CSV importieren",
"importMode": "Importmodus",
"importMerge": "Zusammenfuehren — neue Fahrzeuge hinzufuegen, vorhandene behalten",
"importReplace": "Ersetzen — alle vorhandenen Fahrzeuge loeschen und neu importieren",
"importReplaceWarning": "Alle vorhandenen Fahrzeuge werden ersetzt.",
"importReplaceConfirmTitle": "Fahrzeuge ersetzen?",
"importReplaceConfirmBody": "Alle vorhandenen Fahrzeuge werden durch die CSV-Daten ersetzt. Diese Aktion kann nicht rueckgaengig gemacht werden.",
"importReplaceConfirmButton": "Ersetzen",
"importSuccess": "{count} Fahrzeuge importiert",
"vehicleCount": "{count} Fahrzeuge",
"tabs": {
"inbox": "Posteingang",
"vehicles": "Fahrzeuge"
},
"form": {
"protocol": "Protokoll",
"host": "Host",
"port": "Port",
"encryption": "Verschluesselung",
"encryptionNone": "Keine",
"folder": "Ordner",
"senderFilter": "Absenderfilter",
"pollInterval": "Abrufintervall",
"pollIntervalSuffix": "Minuten",
"username": "Benutzername",
"password": "Passwort",
"exportRecipient": "Export-Empfaenger",
"vehicleFormat": "Fahrzeug-Formatstring",
"vehicleFormatHelp": "Verfuegbare Platzhalter: {Marke}, {Modell}, {Kennzeichen}, {Fahrer}",
"active": "Aktiv",
"testConnection": "Verbindung testen",
"testTesting": "Verbindung wird getestet...",
"testSuccess": "Verbindung erfolgreich",
"testFailed": "Verbindung fehlgeschlagen: {error}",
"save": "Einstellungen speichern",
"cancel": "Bearbeitung abbrechen",
"cancelDialog": "Abbrechen",
"showPassword": "Passwort anzeigen",
"hidePassword": "Passwort verbergen"
},
"errors": {
"pollFailed": "Posteingang konnte nicht geprueft werden. Ueberpruefe die Konfiguration unter 'Moduleinstellungen > Posteingang'.",
"smtpFailed": "Export wurde erstellt, konnte aber nicht gesendet werden. Die Datei ist weiterhin zum Download verfuegbar."
}
},
"marketplace": { "marketplace": {
"title": "Marktplatz", "title": "Marktplatz",
"subtitle": "Entdecken und aktivieren Sie Module fuer Ihren Mandanten", "subtitle": "Entdecken und aktivieren Sie Module fuer Ihren Mandanten",
+96 -1
View File
@@ -113,11 +113,25 @@
"categoryDashboard": "Dashboard", "categoryDashboard": "Dashboard",
"categoryWidgets": "Widgets", "categoryWidgets": "Widgets",
"categoryCalendar": "Calendar", "categoryCalendar": "Calendar",
"categoryGeneral": "General",
"categorySmtp": "SMTP",
"sourceDeleteConfirm": "Are you sure you want to delete this calendar source?", "sourceDeleteConfirm": "Are you sure you want to delete this calendar source?",
"sourceDeleteCta": "Delete source", "sourceDeleteCta": "Delete source",
"providerDeleteConfirm": "Are you sure you want to delete this search provider?", "providerDeleteConfirm": "Are you sure you want to delete this search provider?",
"providerDeleteCta": "Delete provider", "providerDeleteCta": "Delete provider",
"cancel": "Cancel" "cancel": "Cancel",
"smtp": {
"title": "SMTP Configuration",
"host": "Host",
"port": "Port",
"encryption": "Encryption",
"username": "Username",
"password": "Password",
"fromAddress": "Sender Address (From)",
"fromAddressHelp": "The email address shown as the sender.",
"save": "Save Settings",
"test": "Test Connection"
}
}, },
"widgets": { "widgets": {
"emptyHeading": "No active widgets", "emptyHeading": "No active widgets",
@@ -237,6 +251,87 @@
"checking": "Checking...", "checking": "Checking...",
"error": "Error checking domain" "error": "Error checking domain"
}, },
"dkvFleet": {
"pageTitle": "DKV Fleet",
"checkNow": "Check Now",
"checking": "Checking...",
"lastChecked": "Last checked: {time}",
"neverChecked": "Never checked",
"settingsTitle": "Module Settings",
"historyTitle": "Processing History",
"exportFilesTitle": "Recent Export Files",
"emptyHistory": "No invoices processed yet",
"emptyHistoryBody": "Click 'Check Now' to poll the inbox. New invoices will appear here automatically.",
"emptyExports": "No export files available yet",
"emptyVehicles": "No vehicles configured",
"emptyVehiclesBody": "Add vehicles manually or upload a CSV file.",
"status": {
"processed": "Processed",
"error": "Error",
"sendFailed": "Send Failed"
},
"col": {
"dateTime": "Date/Time",
"invoiceNumber": "Invoice Number",
"vehicles": "Vehicles",
"transactions": "Transactions",
"status": "Status",
"exportFile": "Export File",
"licensePlate": "License Plate",
"brand": "Make",
"model": "Model",
"driver": "Driver",
"actions": "Actions"
},
"addVehicle": "Add Vehicle",
"editVehicle": "Edit Vehicle",
"deleteVehicle": "Delete Vehicle",
"deleteVehicleBody": "Vehicle {plate} will be permanently removed from the master list. This action cannot be undone.",
"importCsv": "Import CSV",
"importMode": "Import Mode",
"importMerge": "Merge — add new vehicles, keep existing",
"importReplace": "Replace — delete all existing vehicles and re-import",
"importReplaceWarning": "All existing vehicles will be replaced.",
"importReplaceConfirmTitle": "Replace Vehicles?",
"importReplaceConfirmBody": "All existing vehicles will be replaced with the CSV data. This action cannot be undone.",
"importReplaceConfirmButton": "Replace",
"importSuccess": "{count} vehicles imported",
"vehicleCount": "{count} vehicles",
"tabs": {
"inbox": "Inbox",
"vehicles": "Vehicles"
},
"form": {
"protocol": "Protocol",
"host": "Host",
"port": "Port",
"encryption": "Encryption",
"encryptionNone": "None",
"folder": "Folder",
"senderFilter": "Sender Filter",
"pollInterval": "Poll Interval",
"pollIntervalSuffix": "Minutes",
"username": "Username",
"password": "Password",
"exportRecipient": "Export Recipient",
"vehicleFormat": "Vehicle Format String",
"vehicleFormatHelp": "Available placeholders: {Marke}, {Modell}, {Kennzeichen}, {Fahrer}",
"active": "Active",
"testConnection": "Test Connection",
"testTesting": "Testing connection...",
"testSuccess": "Connection successful",
"testFailed": "Connection failed: {error}",
"save": "Save Settings",
"cancel": "Cancel Editing",
"cancelDialog": "Cancel",
"showPassword": "Show password",
"hidePassword": "Hide password"
},
"errors": {
"pollFailed": "Could not check inbox. Review the configuration under 'Module Settings > Inbox'.",
"smtpFailed": "Export was created but could not be sent. The file is still available for manual download."
}
},
"marketplace": { "marketplace": {
"title": "Marketplace", "title": "Marketplace",
"subtitle": "Discover and activate modules for your tenant", "subtitle": "Discover and activate modules for your tenant",