346 lines
11 KiB
PHP
Executable File
346 lines
11 KiB
PHP
Executable File
<?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];
|
|
}
|
|
}
|