feat(07-04): DkvModule + registry seed + AppModule registration + i18n keys
- 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:
@@ -11,6 +11,7 @@ import { LdapModule } from './ldap/ldap.module';
|
||||
import { MailModule } from './mail/mail.module';
|
||||
import { CalendarModule } from './calendar/calendar.module';
|
||||
import { DashboardModule } from './dashboard/dashboard.module';
|
||||
import { DkvModule } from './dkv/dkv.module';
|
||||
import { DomaincheckModule } from './domaincheck/domaincheck.module';
|
||||
import { ModuleRegistryModule } from './module-registry/module-registry.module';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
@@ -35,6 +36,7 @@ import { UserModule } from './user/user.module';
|
||||
DashboardModule,
|
||||
CalendarModule,
|
||||
SettingsModule,
|
||||
DkvModule,
|
||||
],
|
||||
providers: [
|
||||
// Global JWT guard: all routes require auth unless @Public()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -113,11 +113,25 @@
|
||||
"categoryDashboard": "Dashboard",
|
||||
"categoryWidgets": "Widgets",
|
||||
"categoryCalendar": "Kalender",
|
||||
"categoryGeneral": "Allgemein",
|
||||
"categorySmtp": "SMTP",
|
||||
"sourceDeleteConfirm": "Moechten Sie diese Kalenderquelle wirklich loeschen?",
|
||||
"sourceDeleteCta": "Quelle loeschen",
|
||||
"providerDeleteConfirm": "Moechten Sie diesen Suchanbieter wirklich 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": {
|
||||
"emptyHeading": "Keine Widgets aktiv",
|
||||
@@ -237,6 +251,87 @@
|
||||
"checking": "Pruefe...",
|
||||
"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": {
|
||||
"title": "Marktplatz",
|
||||
"subtitle": "Entdecken und aktivieren Sie Module fuer Ihren Mandanten",
|
||||
|
||||
@@ -113,11 +113,25 @@
|
||||
"categoryDashboard": "Dashboard",
|
||||
"categoryWidgets": "Widgets",
|
||||
"categoryCalendar": "Calendar",
|
||||
"categoryGeneral": "General",
|
||||
"categorySmtp": "SMTP",
|
||||
"sourceDeleteConfirm": "Are you sure you want to delete this calendar source?",
|
||||
"sourceDeleteCta": "Delete source",
|
||||
"providerDeleteConfirm": "Are you sure you want to delete this search 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": {
|
||||
"emptyHeading": "No active widgets",
|
||||
@@ -237,6 +251,87 @@
|
||||
"checking": "Checking...",
|
||||
"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": {
|
||||
"title": "Marketplace",
|
||||
"subtitle": "Discover and activate modules for your tenant",
|
||||
|
||||
Reference in New Issue
Block a user