netgescon-day0/app/Console/Commands/ImportMultiYearGesconCommand.php

346 lines
11 KiB
PHP

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\Import\MultiYearGesconImportService;
use App\Models\GestioneContabile;
use Illuminate\Support\Facades\DB;
/**
* COMMAND: Import Multi-Anno da Directory Gescon
*
* Import diretto da file MDB senza passaggio CSV
* Gestisce separazione multi-anno e gestioni
*/
class ImportMultiYearGesconCommand extends Command
{
protected $signature = 'import:multi-year-gescon
{tenant : Tenant ID}
{gescon-dir : Base directory with year subdirectories}
{--years= : Specific years to import (comma separated)}
{--force : Force reimport existing data}
{--skip-create-gestioni : Skip creation of gestioni}
{--dry-run : Simulate import without saving}';
protected $description = 'Import complete multi-year Gescon data from MDB files with proper separation';
public function handle()
{
$tenantId = $this->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];
}
}