argument('tenant'); $gesconDir = $this->argument('gescon-dir'); $this->info("šŸ—„ļø Multi-Year Gescon Import"); $this->info("Tenant: {$tenantId}"); $this->info("Directory: {$gesconDir}"); $this->newLine(); // Validation if (!$this->validateInputs($gesconDir)) { return Command::FAILURE; } // Scan years $availableYears = $this->scanAvailableYears($gesconDir); $yearsToImport = $this->determineYearsToImport($availableYears); $this->displayImportPlan($yearsToImport); if (!$this->option('dry-run') && !$this->confirm('Proceed with multi-year import?')) { $this->info('Import cancelled by user.'); return Command::SUCCESS; } // Execute import if ($this->option('dry-run')) { $this->simulateImport($yearsToImport); } else { $this->executeImport($tenantId, $gesconDir, $yearsToImport); } return Command::SUCCESS; } private function validateInputs(string $gesconDir): bool { if (!is_dir($gesconDir)) { $this->error("āŒ Directory not found: {$gesconDir}"); return false; } // Check mdb-tools $mdbTest = shell_exec('which mdb-tables 2>/dev/null'); if (empty($mdbTest)) { $this->error("āŒ mdb-tools not installed. Install with: sudo apt-get install mdb-tools"); return false; } return true; } private function scanAvailableYears(string $gesconDir): array { $years = []; $subdirs = glob($gesconDir . '/*', GLOB_ONLYDIR); foreach ($subdirs as $dir) { $yearDir = basename($dir); if (preg_match('/^\d{4}$/', $yearDir)) { $mdbFiles = [ 'singolo_anno' => $dir . '/singolo_anno.mdb', 'generale_stabile' => $dir . '/generale_stabile.mdb' ]; if (file_exists($mdbFiles['singolo_anno'])) { $years[$yearDir] = [ 'path' => $dir, 'files' => $mdbFiles, 'has_generale' => file_exists($mdbFiles['generale_stabile']) ]; } } } ksort($years); return $years; } private function determineYearsToImport(array $availableYears): array { $yearsOption = $this->option('years'); if ($yearsOption) { $requestedYears = array_map('trim', explode(',', $yearsOption)); return array_intersect_key($availableYears, array_flip($requestedYears)); } return $availableYears; } private function displayImportPlan(array $years): void { $this->info('šŸ“‹ Import Plan:'); $this->newLine(); foreach ($years as $year => $data) { $this->line("šŸ“… Year {$year}:"); $this->line(" šŸ“ Path: {$data['path']}"); $this->line(" šŸ“„ singolo_anno.mdb: " . $this->formatFileInfo($data['files']['singolo_anno'])); if ($data['has_generale']) { $this->line(" šŸ“„ generale_stabile.mdb: " . $this->formatFileInfo($data['files']['generale_stabile'])); } else { $this->line(" āš ļø generale_stabile.mdb: NOT FOUND"); } // Preview tables $tables = $this->getTablesPreview($data['files']['singolo_anno']); $this->line(" šŸ“Š Tables: " . implode(', ', array_slice($tables, 0, 5)) . (count($tables) > 5 ? '... (+' . (count($tables) - 5) . ' more)' : '')); $this->newLine(); } $this->info("Total years to import: " . count($years)); // Check existing gestioni $tenantId = $this->argument('tenant'); $existingGestioni = GestioneContabile::forTenant($tenantId) ->whereIn('anno_gestione', array_keys($years)) ->get() ->groupBy('anno_gestione'); if ($existingGestioni->count() > 0) { $this->warn("āš ļø Existing gestioni found:"); foreach ($existingGestioni as $anno => $gestioni) { $this->line(" {$anno}: " . $gestioni->count() . " gestioni"); } if (!$this->option('force')) { $this->error("Use --force to reimport existing data"); return; } } } private function formatFileInfo(string $filePath): string { if (!file_exists($filePath)) { return "NOT FOUND"; } $size = filesize($filePath); $sizeFormatted = $this->formatBytes($size); $modified = date('Y-m-d H:i', filemtime($filePath)); return "āœ… {$sizeFormatted} (modified: {$modified})"; } private function getTablesPreview(string $mdbPath): array { if (!file_exists($mdbPath)) { return []; } $output = shell_exec("mdb-tables -1 " . escapeshellarg($mdbPath) . " 2>/dev/null"); if (!$output) { return []; } return array_filter(explode("\n", trim($output))); } private function simulateImport(array $years): void { $this->info("šŸ” DRY RUN - Simulating import..."); $this->newLine(); foreach ($years as $year => $data) { $this->info("šŸ“… Simulating Year {$year}:"); // Count records in key tables $tables = ['operazioni', 'incassi', 'Nettovers_RDA', 'Inc_da_ec']; foreach ($tables as $table) { $count = $this->countTableRecords($data['files']['singolo_anno'], $table); $this->line(" {$table}: {$count} records"); } // Check generale_stabile tables if ($data['has_generale']) { $generaleTables = ['EMESS_DET', 'EMESS_DET_2', 'EMESS_GEN']; foreach ($generaleTables as $table) { $count = $this->countTableRecords($data['files']['generale_stabile'], $table); $this->line(" {$table}: {$count} records"); } } $this->newLine(); } $this->info("āœ… Dry run completed - no data was imported"); } private function countTableRecords(string $mdbPath, string $tableName): int { if (!file_exists($mdbPath)) { return 0; } $output = shell_exec(sprintf( 'mdb-sql %s <<< "SELECT COUNT(*) FROM %s" 2>/dev/null', escapeshellarg($mdbPath), escapeshellarg($tableName) )); if ($output && preg_match('/(\d+)/', $output, $matches)) { return (int)$matches[1]; } return 0; } private function executeImport(string $tenantId, string $gesconDir, array $years): void { $this->info("šŸš€ Starting Multi-Year Import..."); DB::beginTransaction(); try { $service = new MultiYearGesconImportService($tenantId); // Delete existing data if force if ($this->option('force')) { $this->cleanExistingData($tenantId, array_keys($years)); } $this->newLine(); $this->info("šŸ“Š Importing data..."); $progressBar = $this->output->createProgressBar(count($years)); $progressBar->start(); $results = []; foreach ($years as $year => $data) { $this->line("\nšŸ“… Processing Year {$year}..."); $yearResult = $service->importGestioneYear($year, $data['files']); $results[$year] = $yearResult; $progressBar->advance(); } $progressBar->finish(); $this->newLine(2); DB::commit(); $this->displayResults($results); } catch (\Exception $e) { DB::rollBack(); $this->error("āŒ Import failed: " . $e->getMessage()); $this->line("Stack trace:"); $this->line($e->getTraceAsString()); } } private function cleanExistingData(string $tenantId, array $years): void { $this->warn("šŸ—‘ļø Cleaning existing data for years: " . implode(', ', $years)); // Delete gestioni and cascade $deletedGestioni = GestioneContabile::forTenant($tenantId) ->whereIn('anno_gestione', $years) ->delete(); $this->line(" Deleted {$deletedGestioni} gestioni"); } private function displayResults(array $results): void { $this->info("āœ… Multi-Year Import Completed!"); $this->newLine(); $totals = [ 'gestioni' => 0, 'operazioni' => 0, 'incassi' => 0, 'ritenute' => 0, 'rate_emesse' => 0, 'incassi_ec' => 0 ]; foreach ($results as $year => $yearData) { $this->info("šŸ“… Year {$year}:"); foreach ($yearData as $type => $count) { $this->line(" {$type}: {$count}"); if (isset($totals[$type])) { $totals[$type] += $count; } } $this->newLine(); } $this->info("šŸ“Š Grand Totals:"); foreach ($totals as $type => $total) { $this->line(" {$type}: {$total}"); } } private function formatBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB']; $unitIndex = 0; while ($bytes >= 1024 && $unitIndex < count($units) - 1) { $bytes /= 1024; $unitIndex++; } return round($bytes, 1) . ' ' . $units[$unitIndex]; } }