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 { 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()
|
||||||
|
|||||||
@@ -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",
|
"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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user