release 1.0

This commit is contained in:
Reindl David (IT-PTR-CEN2-SL10)
2026-05-02 17:53:19 +02:00
parent b1cad04fdf
commit 170b2d2016
26 changed files with 5748 additions and 0 deletions
+368
View File
@@ -0,0 +1,368 @@
<?php
namespace UbsCsvTransformer;
use UbsCsvTransformer\CsvReader;
use UbsCsvTransformer\CsvWriter;
use UbsCsvTransformer\ConfigurationLoader;
use UbsCsvTransformer\MetadataExtractor;
use UbsCsvTransformer\ColumnTransformer;
use UbsCsvTransformer\FireflyImporter;
/**
* Orchestriert die gesamte CSV-Transformations-Pipeline
*
* Koordiniert alle Schritte von CSV-Einlesen über Metadaten-Extraktion
* und Spalten-Transformation bis zur Ausgabe und optional zum Import in Firefly III.
*
* @property ConfigurationLoader $configLoader Verwaltet Konfiguration
* @property CsvWriter $csvWriter Schreibt Output-CSV
* @property MetadataExtractor $metadataExtractor Extrahiert Metadaten aus Header
* @property ColumnTransformer $columnTransformer Transformiert Spalten
* @property array $csvStructure CSV-Struktur-Konfiguration
*/
class TransformerEngine
{
private ConfigurationLoader $configLoader;
private CsvWriter $csvWriter;
private MetadataExtractor $metadataExtractor;
private ColumnTransformer $columnTransformer;
private array $csvStructure;
private array $sampleData = [];
private int $rowsProcessed = 0;
private bool $debugMode = false;
/**
* Initialisiert TransformerEngine mit Konfiguration
*
* Lädt alle erforderlichen Konfigurationen und initialisiert
* die Komponenten (MetadataExtractor, ColumnTransformer, CsvWriter).
* CsvReader wird später in transform() und validate() initialisiert mit dem Dateipfad.
*
* @param ConfigurationLoader $configLoader Lädt Konfigurationsdateien
* @param bool $debugMode true = Debug-Modus aktivieren
*
* @throws \RuntimeException wenn erforderliche Konfigurationen fehlen
*/
public function __construct(ConfigurationLoader $configLoader, bool $debugMode = false)
{
$this->configLoader = $configLoader;
$this->debugMode = $debugMode;
$config = $configLoader->getAll();
$this->csvStructure = $config['csvStructure'] ?? [];
$this->metadataExtractor = new MetadataExtractor(
$config['metadata']['extractionRules'] ?? []
);
$this->columnTransformer = new ColumnTransformer(
$config['columnTransformations'] ?? [],
[],
$config['capitalizationExceptions'] ?? []
);
// Bestimme Output-Dateiname aus Konfiguration
$outputDir = $config['directories']['output'] ?? './output';
$outputFileName = $config['csvStructure']['outputFilename'] ?? 'transformed.csv';
$outputFile = rtrim($outputDir, '/') . '/' . $outputFileName;
$this->csvWriter = new CsvWriter(
$outputFile,
$config['csvStructure'] ?? []
);
}
/**
* Aktiviert oder deaktiviert den Debug-Modus
*
* @param bool $enabled true = Debug-Modus aktiviert
* @return void
*/
public function setDebugMode(bool $enabled): void
{
$this->debugMode = $enabled;
if ($enabled) {
DebugLogger::enable();
} else {
DebugLogger::disable();
}
}
/**
* Transformiert eine CSV-Datei
*
* Führt folgende Schritte durch:
* 1. CSV-Datei einlesen mit CsvReader
* 2. Metadaten aus Header extrahieren
* 3. Spalten gemäß Konfiguration transformieren
* 4. Daten in Output-CSV schreiben
* 5. Beispiel-Daten sammeln (maximal 3 Zeilen oder maxRows)
*
* Der Output-Dateipfad wird aus der Konfiguration bestimmt und kann nicht überschrieben werden.
*
* @param string $inputFile Pfad zur Input-CSV-Datei
* @param int $maxRows Maximale Anzahl Datenzeilen zu transformieren (0 = alle).
* Beispiel-Daten werden begrenzt auf min(3, maxRows)
*
* @return array Transformations-Ergebnis mit:
* - success: bool (true = erfolgreich, false = Fehler)
* - inputFile: string (Input-Dateipfad, nur bei Erfolg)
* - outputFile: string (Output-Dateipfad, nur bei Erfolg)
* - rowsProcessed: int (tatsächlich verarbeitete Datenzeilen)
* - sampleData: array (Erste Beispiel-Zeilen, max 3 oder maxRows)
* - metadata: array (Extrahierte Metadaten, nur bei Erfolg)
* - outputColumns: int (Anzahl Output-Spalten)
* - error: string (Fehlermeldung, nur bei Fehler)
*/
public function transform(string $inputFile, int $maxRows = 0): array
{
$this->sampleData = [];
$this->rowsProcessed = 0;
DebugLogger::reset();
try {
if ($this->debugMode) {
DebugLogger::log('transformer', 'Transformation started', [
'inputFile' => $inputFile,
'maxRows' => $maxRows
]);
}
// Validiere Input-Datei
if (!file_exists($inputFile)) {
throw new \RuntimeException("Input-Datei nicht gefunden: {$inputFile}");
}
// Initialisiere CsvReader mit Dateipfad und Konfiguration
$csvReader = new CsvReader($inputFile, $this->csvStructure);
// Lese Metadaten-Zeilen (vor der Header-Zeile)
$metadataLines = $csvReader->readMetadataLines();
// Extrahiere Metadaten aus den Metadaten-Zeilen
$metadata = $this->metadataExtractor->extract($metadataLines);
// Initialisiere ColumnTransformer mit extrahierten Metadaten
$this->columnTransformer = new ColumnTransformer(
$this->configLoader->get('columnTransformations', []),
$metadata,
$this->configLoader->get('capitalizationExceptions', [])
);
// Lese CSV-Daten mit Header-Keys als Array-Keys
$dataRows = $csvReader->readCsvData($maxRows);
if (empty($dataRows)) {
throw new \RuntimeException("Keine Datenzeilen in CSV-Datei");
}
// Berechne Limit für Beispiel-Daten
$sampleLimit = $maxRows == 0 ? 3 : $maxRows;
// Transformiere Zeilen und sammle sie
$transformedData = [];
foreach ($dataRows as $row) {
// Prüfe ob maxRows erreicht
if ($maxRows > 0 && $this->rowsProcessed >= $maxRows) {
break;
}
// Transformiere Zeile
$transformedRow = $this->columnTransformer->transformRow($row);
$transformedData[] = $transformedRow;
// Speichere Beispiel-Daten
if (count($this->sampleData) < $sampleLimit) {
$this->sampleData[] = $transformedRow;
}
$this->rowsProcessed++;
}
// Entferne Spalten die aus dem Output ausgeschlossen werden sollen
$excludeColumns = $this->csvStructure['excludeOutputColumns'] ?? [];
if (!empty($excludeColumns)) {
$excludeMap = array_flip($excludeColumns);
$transformedData = array_map(
static fn(array $row): array => array_diff_key($row, $excludeMap),
$transformedData
);
$this->sampleData = array_map(
static fn(array $row): array => array_diff_key($row, $excludeMap),
$this->sampleData
);
}
// Schreibe alle transformierten Daten in Output-CSV
$this->csvWriter->write($transformedData);
$result = [
'success' => true,
'inputFile' => $inputFile,
'outputFile' => $this->csvWriter->getOutputFile(),
'rowsProcessed' => $this->rowsProcessed,
'sampleData' => $this->sampleData,
'metadata' => $metadata,
'outputColumns' => $this->columnTransformer->getOutputColumns(),
];
if ($this->debugMode) {
$result['debug_logs'] = DebugLogger::getLogs();
}
return $result;
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage(),
'rowsProcessed' => $this->rowsProcessed,
'sampleData' => $this->sampleData,
'outputColumns' => [],
];
}
}
/**
* Transformiert und importiert CSV in Firefly III
*
* Führt Transformation durch und importiert die Ausgabe-Datei
* in Firefly III wenn in der Konfiguration aktiviert.
*
* Rückwärts-kompatibel mit legacy Signatur.
*
* @param string $inputFile Pfad zur Input-CSV-Datei
* @param int $maxRows Maximale Anzahl Datenzeilen zu verarbeiten (0 = alle)
*
* @return array Transformations- und Import-Ergebnis mit:
* - success: bool (true = transformation erfolgreich)
* - inputFile: string
* - outputFile: string
* - rowsProcessed: int
* - sampleData: array
* - metadata: array
* - outputColumns: int
* - import: array (Firefly Import-Ergebnis, wenn autoImport aktiv)
* - error: string (falls Fehler)
*/
public function transformAndImport(string $inputFile, int $maxRows = 0): array
{
// Zuerst transformieren
$transformResult = $this->transform($inputFile, $maxRows);
if (!$transformResult['success']) {
return $transformResult;
}
// Prüfe ob Auto-Import in Konfiguration aktiviert ist
$fireflyConfig = $this->configLoader->get('fireflyImport', []);
if (empty($fireflyConfig['autoImport'])) {
return $transformResult;
}
// Führe Firefly-Import durch
try {
$importer = new FireflyImporter($fireflyConfig);
$importResult = $importer->import($transformResult['outputFile']);
$transformResult['import'] = $importResult;
return $transformResult;
} catch (\Exception $e) {
$transformResult['import'] = [
'success' => false,
'error' => $e->getMessage(),
];
return $transformResult;
}
}
/**
* Validiert eine CSV-Datei gegen die Konfiguration
*
* Prüft ob erforderliche Metadaten vorhanden sind
* und ob die CSV-Struktur der Konfiguration entspricht.
*
* @param string $inputFile Pfad zur zu validierenden CSV-Datei
*
* @return array Validierungs-Ergebnis mit:
* - valid: bool (true = Validierung erfolgreich)
* - metadata: array (Extrahierte Metadaten, wenn valid)
* - line_count: int (Gesamtzahl Zeilen, wenn valid)
* - error: string (Fehlermeldung, wenn nicht valid)
* - metadata_found: array (Gefundene Metadaten trotz Fehler)
*/
public function validate(string $inputFile): array
{
try {
if (!file_exists($inputFile)) {
return [
'valid' => false,
'error' => "Datei nicht gefunden: {$inputFile}",
];
}
// Initialisiere CsvReader mit Dateipfad
$csvReader = new CsvReader($inputFile, $this->csvStructure);
// Extrahiere Metadaten-Zeilen (vor der Header-Zeile)
$metadataLines = $csvReader->readMetadataLines();
$metadata = $this->metadataExtractor->extract($metadataLines);
// Prüfe auf erforderliche Metadaten
$requiredMetadata = [
'account_iban',
'currency_code',
];
$missingMetadata = [];
foreach ($requiredMetadata as $key) {
if (empty($metadata[$key])) {
$missingMetadata[] = $key;
}
}
if (!empty($missingMetadata)) {
return [
'valid' => false,
'error' => 'Fehlende Metadaten: ' . implode(', ', $missingMetadata),
'metadata_found' => $metadata,
];
}
// Zähle Gesamtzahl Zeilen
$lineCount = $csvReader->countLines();
return [
'valid' => true,
'metadata' => $metadata,
'line_count' => $lineCount,
];
} catch (\Exception $e) {
return [
'valid' => false,
'error' => 'Validierungs-Fehler: ' . $e->getMessage(),
];
}
}
/**
* Gibt die gesammelten Beispiel-Daten zurück
*
* @return array Beispiel-Daten (maximal 3 oder maxRows Zeilen)
*/
public function getSampleData(): array
{
return $this->sampleData;
}
/**
* Gibt die Anzahl verarbeiteter Datenzeilen zurück
*
* @return int Anzahl transformierter Zeilen
*/
public function getRowsProcessed(): int
{
return $this->rowsProcessed;
}
}