1040 lines
36 KiB
PHP
Executable File
1040 lines
36 KiB
PHP
Executable File
<?php
|
|
namespace App\Services\Import;
|
|
|
|
use App\Models\GestioneContabile;
|
|
use App\Models\Incasso;
|
|
use App\Models\IncassoEstrattoConto;
|
|
use App\Models\RegistroRitenuteAcconto;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* MULTI-YEAR GESCON IMPORT SERVICE
|
|
*
|
|
* Gestisce import diretto da file MDB multipli con:
|
|
* - Separazione gestioni per anno
|
|
* - Continuità contabile multi-anno
|
|
* - Partita doppia con NATURA2
|
|
* - Tracciabilità modifiche
|
|
* - Registro ritenute d'acconto
|
|
*/
|
|
class MultiYearGesconImportService
|
|
{
|
|
private string $tenantId;
|
|
private ?int $stabileId = null;
|
|
private ?string $baseDir = null;
|
|
private array $directoryToYearMap = [];
|
|
private array $mdbConnections = [];
|
|
private array $gestioni = [];
|
|
|
|
public function __construct(string $tenantId, ?int $stabileId = null, ?string $baseDir = null)
|
|
{
|
|
$this->tenantId = $tenantId;
|
|
$this->stabileId = $stabileId;
|
|
$this->baseDir = $baseDir;
|
|
|
|
if ($baseDir) {
|
|
$this->loadYearMap($baseDir);
|
|
}
|
|
}
|
|
|
|
private function loadYearMap(string $baseDir): void
|
|
{
|
|
$generaleMdb = rtrim($baseDir, '/') . '/generale_stabile.mdb';
|
|
if (!is_file($generaleMdb)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$output = shell_exec(sprintf(
|
|
'echo "SELECT id_anno, anno_o FROM anni" | mdb-sql %s 2>/dev/null',
|
|
escapeshellarg($generaleMdb)
|
|
));
|
|
if ($output) {
|
|
$lines = explode("\n", $output);
|
|
foreach ($lines as $line) {
|
|
if (str_contains($line, '|')) {
|
|
$parts = explode('|', $line);
|
|
$id = (int) trim($parts[1] ?? '');
|
|
$yr = (int) trim($parts[2] ?? '');
|
|
if ($id > 0 && $yr >= 2000 && $yr <= 2100) {
|
|
$dir = str_pad((string)$id, 4, '0', STR_PAD_LEFT);
|
|
$this->directoryToYearMap[$dir] = $yr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::warning("Could not load year map from general MDB: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import completo multi-anno da directory Gescon
|
|
*/
|
|
public function importFromGesconDirectory(string $baseDir): array
|
|
{
|
|
Log::info("Starting multi-year Gescon import", ['tenant' => $this->tenantId, 'dir' => $baseDir]);
|
|
|
|
// 1. Scansiona directory per anni
|
|
$years = $this->scanGesconYears($baseDir);
|
|
|
|
// 2. Crea gestioni contabili per ogni anno
|
|
$this->createGestioni($years);
|
|
|
|
// 3. Import dati per ogni gestione
|
|
$results = [];
|
|
foreach ($years as $year => $paths) {
|
|
$results[$year] = $this->importGestioneYear($year, $paths);
|
|
}
|
|
|
|
// 4. Crea bilanci di apertura per continuità
|
|
$this->createBilanciApertura();
|
|
|
|
return [
|
|
'success' => true,
|
|
'gestioni_create' => count($this->gestioni),
|
|
'years_imported' => array_keys($results),
|
|
'details' => $results,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Scansiona directory Gescon per identificare anni
|
|
*/
|
|
private function scanGesconYears(string $baseDir): array
|
|
{
|
|
$years = [];
|
|
|
|
// Pattern directory: /Gescon/2024/singolo_anno.mdb, /Gescon/2025/singolo_anno.mdb
|
|
$gesconDirs = glob($baseDir . '/*/');
|
|
|
|
foreach ($gesconDirs as $dir) {
|
|
$year = basename(rtrim($dir, '/'));
|
|
if (is_numeric($year) && strlen($year) == 4) {
|
|
$paths = [
|
|
'singolo_anno' => $dir . 'singolo_anno.mdb',
|
|
'generale_stabile' => $dir . 'generale_stabile.mdb',
|
|
];
|
|
|
|
// Verifica esistenza file
|
|
if (file_exists($paths['singolo_anno'])) {
|
|
$years[$year] = $paths;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $years;
|
|
}
|
|
|
|
/**
|
|
* Crea gestioni contabili per tutti gli anni scansionati
|
|
*/
|
|
public function createGestioni(array $years): void
|
|
{
|
|
foreach ($years as $year => $paths) {
|
|
$this->initGestioniForYear($year, $paths);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Inizializza le gestioni contabili per un singolo anno
|
|
*/
|
|
public function initGestioniForYear(string $year, array $paths): void
|
|
{
|
|
$calendarYear = $this->directoryToYearMap[$year] ?? (int) $year;
|
|
$mdbPath = $paths['singolo_anno'];
|
|
|
|
$gestioniData = $this->scanGestioniFromMdb($mdbPath, $calendarYear);
|
|
|
|
foreach ($gestioniData as $data) {
|
|
$this->createOrUpdateGestione($data, $year);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scansiona gestioni direttamente dal file MDB
|
|
*/
|
|
private function scanGestioniFromMdb(string $mdbPath, int $year): array
|
|
{
|
|
$gestioni = [];
|
|
|
|
// Gestione Ordinaria (sempre presente)
|
|
$gestioni[] = [
|
|
'anno_gestione' => $year,
|
|
'tipo_gestione' => 'ordinaria',
|
|
'denominazione' => "Gestione Ordinaria {$year}",
|
|
'data_inizio' => "{$year}-01-01",
|
|
'data_fine' => "{$year}-12-31",
|
|
'protocollo_prefix' => "O{$year}",
|
|
];
|
|
|
|
// Legge le colonne necessarie da Operazioni
|
|
$operazioni = $this->queryMdb($mdbPath, "SELECT Gestione, n_stra FROM Operazioni");
|
|
|
|
$hasRiscaldamento = false;
|
|
$straordinarie = [];
|
|
|
|
foreach ($operazioni as $op) {
|
|
$g = strtoupper(trim($op['gestione'] ?? ''));
|
|
if ($g === 'R') {
|
|
$hasRiscaldamento = true;
|
|
} elseif ($g === 'S') {
|
|
$nStra = (int) ($op['n_stra'] ?? 0);
|
|
if ($nStra > 0) {
|
|
$straordinarie[$nStra] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($hasRiscaldamento) {
|
|
$gestioni[] = [
|
|
'anno_gestione' => $year,
|
|
'tipo_gestione' => 'riscaldamento',
|
|
'denominazione' => "Gestione Riscaldamento {$year}",
|
|
'data_inizio' => "{$year}-01-01",
|
|
'data_fine' => "{$year}-12-31",
|
|
'protocollo_prefix' => "R{$year}",
|
|
];
|
|
}
|
|
|
|
ksort($straordinarie);
|
|
foreach (array_keys($straordinarie) as $numero) {
|
|
$gestioni[] = [
|
|
'anno_gestione' => $year,
|
|
'tipo_gestione' => 'straordinaria',
|
|
'numero_straordinaria' => $numero,
|
|
'denominazione' => "Gestione Straordinaria {$year} - {$numero}",
|
|
'data_inizio' => "{$year}-01-01",
|
|
'data_fine' => "{$year}-12-31",
|
|
'protocollo_prefix' => "S{$year}-{$numero}",
|
|
];
|
|
}
|
|
|
|
return $gestioni;
|
|
}
|
|
|
|
/**
|
|
* Crea o aggiorna gestione
|
|
*/
|
|
private function createOrUpdateGestione(array $gestioneData, ?string $yearKey = null): GestioneContabile
|
|
{
|
|
$gestione = GestioneContabile::updateOrCreate([
|
|
'tenant_id' => $this->tenantId,
|
|
'stabile_id' => $this->stabileId,
|
|
'anno_gestione' => $gestioneData['anno_gestione'],
|
|
'tipo_gestione' => $gestioneData['tipo_gestione'],
|
|
'numero_straordinaria' => $gestioneData['numero_straordinaria'] ?? null,
|
|
], [
|
|
'denominazione' => $gestioneData['denominazione'],
|
|
'data_inizio' => $gestioneData['data_inizio'],
|
|
'data_fine' => $gestioneData['data_fine'],
|
|
'stato' => 'aperta',
|
|
'protocollo_prefix' => $gestioneData['protocollo_prefix'],
|
|
]);
|
|
|
|
// Cache per lookup veloce
|
|
$year = $yearKey ?? $gestioneData['anno_gestione'];
|
|
$tipo = $gestioneData['tipo_gestione'];
|
|
|
|
if ($tipo === 'straordinaria') {
|
|
$key = "straordinaria_{$gestioneData['numero_straordinaria']}";
|
|
} else {
|
|
$key = $tipo;
|
|
}
|
|
|
|
$this->gestioni[$year][$key] = $gestione;
|
|
|
|
return $gestione;
|
|
}
|
|
|
|
/**
|
|
* Import operazioni per gestione specifica
|
|
*/
|
|
private function importOperazioniForGestione(GestioneContabile $gestione, string $mdbPath): int
|
|
{
|
|
// Filtro per tipo gestione
|
|
$whereClause = $this->buildGestioneWhereClause($gestione);
|
|
|
|
$operazioni = $this->queryMdb($mdbPath, "
|
|
SELECT *
|
|
FROM Operazioni
|
|
WHERE {$whereClause}
|
|
");
|
|
|
|
usort($operazioni, function($a, $b) {
|
|
$idA = (int) ($a['id_operaz'] ?? 0);
|
|
$idB = (int) ($b['id_operaz'] ?? 0);
|
|
return $idA <=> $idB;
|
|
});
|
|
|
|
$imported = 0;
|
|
foreach ($operazioni as $op) {
|
|
try {
|
|
$voceSnapshot = $this->getVoceSpesaSnapshot($mdbPath, $op['cod_spe'] ?? '');
|
|
$protocollo = $this->generateProtocollo($gestione->id, $op);
|
|
|
|
$competClean = empty($op['compet']) ? 'C' : trim($op['compet']);
|
|
$natura2Clean = empty($op['natura2']) ? '' : trim($op['natura2']);
|
|
$nStraClean = empty($op['n_stra']) ? 0 : (int) $op['n_stra'];
|
|
|
|
$opId = $op['id_operaz'] ?? $op['id'] ?? null;
|
|
$dtSpe = $op['dt_spe'] ?? $op['data_oper'] ?? null;
|
|
$importo = $op['importo_euro'] ?? $op['importo'] ?? $op['euro'] ?? 0;
|
|
$codVoce = $op['cod_spe'] ?? $op['cod_voce'] ?? '';
|
|
$codBen = $op['cod_ben'] ?? '';
|
|
|
|
$isEntrata = strtolower(trim((string) ($natura2Clean ?? ''))) === 'entrata';
|
|
$amount = (float) $importo;
|
|
$dareVal = (float) ($op['importo_spese'] ?? 0);
|
|
$avereVal = (float) ($op['importo_entrate'] ?? 0);
|
|
|
|
if ($dareVal == 0 && $avereVal == 0 && $amount != 0) {
|
|
if ($isEntrata) {
|
|
$avereVal = $amount;
|
|
} else {
|
|
$dareVal = $amount;
|
|
}
|
|
}
|
|
|
|
$contoBancarioId = $this->resolveContoBancarioId($mdbPath, $op['cod_cassa'] ?? null);
|
|
|
|
DB::table('operazioni_contabili')->insert([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestione->id,
|
|
'legacy_id' => $opId,
|
|
'descrizione' => $op['note'] ?? $op['descrizione'] ?? $op['natura'] ?? 'Operazione',
|
|
'data_operazione' => $this->parseGesconDate($dtSpe),
|
|
'compet' => $competClean,
|
|
'natura2' => $natura2Clean,
|
|
'n_stra' => $nStraClean,
|
|
'protocollo_numero' => $protocollo['numero'],
|
|
'protocollo_completo' => $protocollo['completo'],
|
|
'voce_spesa_snapshot' => json_encode($voceSnapshot),
|
|
'dare' => $dareVal,
|
|
'avere' => $avereVal,
|
|
'conto_contabile' => $codVoce,
|
|
'conto_bancario_id' => $contoBancarioId,
|
|
'stato_operazione' => 'confermata',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing operazione for gestione", [
|
|
'gestione_id' => $gestione->id,
|
|
'operazione' => $op,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import incassi per gestione
|
|
*/
|
|
private function importIncassiForGestione(GestioneContabile $gestione, string $mdbPath): int
|
|
{
|
|
$whereClause = $this->buildGestioneWhereClause($gestione);
|
|
|
|
$incassi = $this->queryMdb($mdbPath, "
|
|
SELECT * FROM incassi
|
|
WHERE {$whereClause}
|
|
ORDER BY data_inc
|
|
");
|
|
|
|
$imported = 0;
|
|
foreach ($incassi as $inc) {
|
|
try {
|
|
// Import incasso...
|
|
$imported++;
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing incasso", [
|
|
'gestione_id' => $gestione->id,
|
|
'incasso' => $inc,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import ritenute per gestione
|
|
*/
|
|
private function importRitenuteFOrGestione(GestioneContabile $gestione, string $mdbPath): int
|
|
{
|
|
$ritenute = $this->queryMdb($mdbPath, "
|
|
SELECT n.*, o.data_oper, o.euro as imponibile_operazione
|
|
FROM Nettovers_RDA n
|
|
LEFT JOIN operazioni o ON n.Rif_RDA = o.n_operazione
|
|
WHERE n.Rif_RDA IS NOT NULL
|
|
AND {$this->buildGestioneJoinClause($gestione, 'o')}
|
|
ORDER BY n.Rif_RDA
|
|
");
|
|
|
|
$imported = 0;
|
|
foreach ($ritenute as $ra) {
|
|
try {
|
|
RegistroRitenuteAcconto::create([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestione->id,
|
|
'numero_progressivo' => $ra['rif_rda'],
|
|
'data_competenza' => $this->parseGesconDate($ra['data_oper']),
|
|
'imponibile' => (float) ($ra['imponibile_operazione'] ?? 0),
|
|
'aliquota_ritenuta' => (float) ($ra['aliquota'] ?? 20),
|
|
'importo_ritenuta' => (float) ($ra['ritenuta'] ?? 0),
|
|
'rif_rda' => $ra['rif_rda'],
|
|
'stato_versamento' => 'da_versare',
|
|
'metadati_gescon' => json_encode($ra),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing ritenuta", [
|
|
'gestione_id' => $gestione->id,
|
|
'ritenuta' => $ra,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import incassi estratto conto per gestione
|
|
*/
|
|
private function importIncassiEstrattoContoForGestione(GestioneContabile $gestione, string $mdbPath): int
|
|
{
|
|
$incassiEc = $this->queryMdb($mdbPath, "
|
|
SELECT * FROM Inc_da_ec
|
|
ORDER BY data_oper
|
|
");
|
|
|
|
$imported = 0;
|
|
foreach ($incassiEc as $inc) {
|
|
try {
|
|
IncassoEstrattoConto::create([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestione->id,
|
|
'id_gescon' => $inc['id'] ?? null,
|
|
'data_operazione' => $this->parseGesconDate($inc['data_oper']),
|
|
'data_valuta' => $this->parseGesconDate($inc['data_valuta']),
|
|
'importo' => (float) ($inc['importo'] ?? 0),
|
|
'descrizione' => $inc['descrizione'] ?? '',
|
|
'stato' => 'da_riconciliare',
|
|
'metadati_gescon' => json_encode($inc),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing incasso EC", [
|
|
'gestione_id' => $gestione->id,
|
|
'incasso' => $inc,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Costruisce WHERE clause per filtrare per gestione
|
|
*/
|
|
private function buildGestioneWhereClause(GestioneContabile $gestione): string
|
|
{
|
|
switch ($gestione->tipo_gestione) {
|
|
case 'ordinaria':
|
|
return "(gestione = 'O' OR gestione = 'o' OR gestione IS NULL OR gestione = '')";
|
|
|
|
case 'riscaldamento':
|
|
return "(gestione = 'R' OR gestione = 'r')";
|
|
|
|
case 'straordinaria':
|
|
$numero = $gestione->numero_straordinaria;
|
|
return "(gestione = 'S' OR gestione = 's') AND N_STRA = {$numero}";
|
|
|
|
default:
|
|
return "1=1";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Costruisce JOIN clause per filtrare per gestione
|
|
*/
|
|
private function buildGestioneJoinClause(GestioneContabile $gestione, string $alias = ''): string
|
|
{
|
|
$prefix = $alias ? "{$alias}." : '';
|
|
|
|
switch ($gestione->tipo_gestione) {
|
|
case 'ordinaria':
|
|
return "({$prefix}gestione = 'O' OR {$prefix}gestione = 'o' OR {$prefix}gestione IS NULL OR {$prefix}gestione = '')";
|
|
|
|
case 'riscaldamento':
|
|
return "({$prefix}gestione = 'R' OR {$prefix}gestione = 'r')";
|
|
|
|
case 'straordinaria':
|
|
$numero = $gestione->numero_straordinaria;
|
|
return "({$prefix}gestione = 'S' OR {$prefix}gestione = 's') AND {$prefix}N_STRA = {$numero}";
|
|
|
|
default:
|
|
return "1=1";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Import dati per una specifica gestione/anno
|
|
*/
|
|
public function importGestioneYear(string $year, array $paths): array
|
|
{
|
|
if (! isset($this->gestioni[$year])) {
|
|
$this->initGestioniForYear($year, $paths);
|
|
}
|
|
|
|
$results = [
|
|
'operazioni' => 0,
|
|
'incassi' => 0,
|
|
'ritenute' => 0,
|
|
'rate_emesse' => 0,
|
|
'incassi_ec' => 0,
|
|
];
|
|
|
|
// 1. Import operazioni contabili
|
|
$results['operazioni'] = $this->importOperazioni($year, $paths['singolo_anno']);
|
|
|
|
// 2. Import incassi
|
|
$results['incassi'] = $this->importIncassi($year, $paths['singolo_anno']);
|
|
|
|
// 3. Import ritenute d'acconto
|
|
$results['ritenute'] = $this->importRitenuteAcconto($year, $paths['singolo_anno']);
|
|
|
|
// 4. Import rate emesse (da generale_stabile.mdb)
|
|
if (file_exists($paths['generale_stabile'])) {
|
|
$results['rate_emesse'] = $this->importRateEmesse($year, $paths['generale_stabile']);
|
|
}
|
|
|
|
// 5. Import incassi estratto conto
|
|
if (file_exists($paths['generale_stabile'])) {
|
|
$results['incassi_ec'] = $this->importIncassiEstrattoConto($year, $paths['generale_stabile']);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Import operazioni con gestione COMPET e NATURA2
|
|
*/
|
|
private function importOperazioni(string $year, string $mdbPath): int
|
|
{
|
|
$imported = 0;
|
|
|
|
$operazioni = $this->queryMdb($mdbPath, "
|
|
SELECT *
|
|
FROM Operazioni
|
|
");
|
|
echo "DEBUG: Found " . count($operazioni) . " operations in MDB for year {$year}\n";
|
|
|
|
usort($operazioni, function($a, $b) {
|
|
$idA = (int) ($a['id_operaz'] ?? 0);
|
|
$idB = (int) ($b['id_operaz'] ?? 0);
|
|
return $idA <=> $idB;
|
|
});
|
|
|
|
foreach ($operazioni as $op) {
|
|
try {
|
|
// Determina gestione
|
|
$gestioneId = $this->determineGestioneId($year, $op);
|
|
|
|
// Genera protocollo
|
|
$protocollo = $this->generateProtocollo($gestioneId, $op);
|
|
|
|
// Snapshot voce spesa
|
|
$voceSnapshot = $this->getVoceSpesaSnapshot($mdbPath, $op['cod_spe'] ?? '');
|
|
|
|
$competClean = empty($op['compet']) ? 'C' : trim($op['compet']);
|
|
$natura2Clean = empty($op['natura2']) ? '' : trim($op['natura2']);
|
|
$nStraClean = empty($op['n_stra']) ? 0 : (int) $op['n_stra'];
|
|
|
|
$opId = $op['id_operaz'] ?? $op['id'] ?? null;
|
|
$dtSpe = $op['dt_spe'] ?? $op['data_oper'] ?? null;
|
|
$importo = $op['importo_euro'] ?? $op['importo'] ?? $op['euro'] ?? 0;
|
|
$codVoce = $op['cod_spe'] ?? $op['cod_voce'] ?? '';
|
|
$codBen = $op['cod_ben'] ?? '';
|
|
|
|
$isEntrata = strtolower(trim((string) ($natura2Clean ?? ''))) === 'entrata';
|
|
$amount = (float) $importo;
|
|
$dareVal = (float) ($op['importo_spese'] ?? 0);
|
|
$avereVal = (float) ($op['importo_entrate'] ?? 0);
|
|
|
|
if ($dareVal == 0 && $avereVal == 0 && $amount != 0) {
|
|
if ($isEntrata) {
|
|
$avereVal = $amount;
|
|
} else {
|
|
$dareVal = $amount;
|
|
}
|
|
}
|
|
|
|
$contoBancarioId = $this->resolveContoBancarioId($mdbPath, $op['cod_cassa'] ?? null);
|
|
|
|
DB::table('operazioni_contabili')->insert([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestioneId,
|
|
'legacy_id' => $opId,
|
|
'descrizione' => (trim($op['benef'] ?? '') !== '') ? trim($op['benef']) : ((trim($op['note'] ?? '') !== '') ? trim($op['note']) : ((trim($op['descrizione'] ?? '') !== '') ? trim($op['descrizione']) : 'Operazione')),
|
|
'data_operazione' => $this->parseGesconDate($dtSpe),
|
|
'compet' => $competClean,
|
|
'natura2' => $natura2Clean,
|
|
'n_stra' => $nStraClean,
|
|
'protocollo_numero' => $protocollo['numero'],
|
|
'protocollo_completo' => $protocollo['completo'],
|
|
'voce_spesa_snapshot' => json_encode($voceSnapshot),
|
|
'dare' => $dareVal,
|
|
'avere' => $avereVal,
|
|
'conto_contabile' => $codVoce,
|
|
'conto_bancario_id' => $contoBancarioId,
|
|
'stato_operazione' => 'confermata',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
echo "Error importing operazione " . ($op['id_operaz'] ?? 'unknown') . " for year {$year}: " . $e->getMessage() . "\n";
|
|
Log::error("Error importing operazione", [
|
|
'year' => $year,
|
|
'operazione' => $op,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import incassi da incassi
|
|
*/
|
|
private function importIncassi(string $year, string $mdbPath): int
|
|
{
|
|
$imported = 0;
|
|
|
|
$incassi = $this->queryMdb($mdbPath, "
|
|
SELECT * FROM incassi
|
|
");
|
|
|
|
usort($incassi, function($a, $b) {
|
|
$dateA = $a['dt_empag'] ?? '';
|
|
$dateB = $b['dt_empag'] ?? '';
|
|
return strcmp($dateA, $dateB);
|
|
});
|
|
|
|
foreach ($incassi as $inc) {
|
|
try {
|
|
$gestioneId = $this->gestioni[$year]['ordinaria']->id;
|
|
|
|
Incasso::create([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestioneId,
|
|
'condominio_id' => $this->gestioni[$year]['ordinaria']->stabile_id ?? 1,
|
|
'conto_bancario_id' => $this->resolveContoBancarioId($mdbPath, $inc['cod_cassa'] ?? null),
|
|
'id_incasso_gescon' => $inc['id_incasso'] ?? null,
|
|
'cod_cond_gescon' => $inc['cod_cond'] ?? null,
|
|
'cond_inquil' => $inc['cond_inquil'] ?? null,
|
|
'n_riferimento' => $inc['n_riferimento'] ?? null,
|
|
'anno_rif' => (int) ($inc['anno_rif'] ?? $year),
|
|
'n_ricevuta' => $inc['n_ricevuta'] ?? null,
|
|
'anno_ricev' => $inc['anno_ricev'] ?? null,
|
|
'n_mese' => $inc['n_mese'] ?? null,
|
|
'o_r_s' => $inc['o_r_s'] ?? null,
|
|
'importo_pagato' => (float) ($inc['importo_pagato'] ?? 0),
|
|
'importo_pagato_euro' => (float) ($inc['importo_pagato_euro'] ?? 0),
|
|
'dt_empag' => $this->parseGesconDate($inc['dt_empag'] ?? null),
|
|
'descrizione' => $inc['descrizione'] ?? null,
|
|
'cod_cassa_gescon' => $inc['cod_cassa'] ?? null,
|
|
'metadati_gescon' => json_encode($inc),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing incasso", [
|
|
'year' => $year,
|
|
'incasso' => $inc,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import ritenute d'acconto da Nettovers_RDA
|
|
*/
|
|
private function importRitenuteAcconto(string $year, string $mdbPath): int
|
|
{
|
|
$imported = 0;
|
|
|
|
if (!$this->mdbTableExists($mdbPath, 'Nettovers_RDA')) {
|
|
return 0;
|
|
}
|
|
|
|
// Query per ritenute
|
|
$ritenute = $this->queryMdb($mdbPath, "
|
|
SELECT n.*, o.dt_spe, o.importo_euro as imponibile_operazione
|
|
FROM Nettovers_RDA n
|
|
LEFT JOIN Operazioni o ON n.Rif_RDA = o.id_operaz
|
|
WHERE n.Rif_RDA IS NOT NULL
|
|
ORDER BY n.Rif_RDA
|
|
");
|
|
|
|
foreach ($ritenute as $ra) {
|
|
try {
|
|
$gestioneId = $this->gestioni[$year]['ordinaria']->id;
|
|
|
|
RegistroRitenuteAcconto::create([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestioneId,
|
|
'numero_progressivo' => $ra['rif_rda'],
|
|
'data_competenza' => $this->parseGesconDate($ra['dt_spe'] ?? null),
|
|
'imponibile' => (float) ($ra['imponibile_operazione'] ?? 0),
|
|
'aliquota_ritenuta' => (float) ($ra['aliquota'] ?? 20),
|
|
'importo_ritenuta' => (float) ($ra['ritenuta'] ?? 0),
|
|
'rif_rda' => $ra['rif_rda'],
|
|
'stato_versamento' => 'da_versare', // Default
|
|
'metadati_gescon' => json_encode($ra),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing ritenuta", [
|
|
'year' => $year,
|
|
'ritenuta' => $ra,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import incassi estratto conto da Inc_da_ec
|
|
*/
|
|
private function importIncassiEstrattoConto(string $year, string $mdbPath): int
|
|
{
|
|
$imported = 0;
|
|
|
|
$incassiEc = $this->queryMdb($mdbPath, "
|
|
SELECT * FROM Inc_da_ec
|
|
");
|
|
|
|
usort($incassiEc, function($a, $b) {
|
|
$protoA = (int) ($a['protocollo'] ?? 0);
|
|
$protoB = (int) ($b['protocollo'] ?? 0);
|
|
return $protoA <=> $protoB;
|
|
});
|
|
|
|
foreach ($incassiEc as $inc) {
|
|
try {
|
|
$gestioneId = $this->gestioni[$year]['ordinaria']->id;
|
|
|
|
IncassoEstrattoConto::create([
|
|
'tenant_id' => $this->tenantId,
|
|
'gestione_id' => $gestioneId,
|
|
'id_gescon' => $inc['protocollo'] ?? null,
|
|
'data_operazione' => $this->parseGesconDate($inc['data_pag'] ?? null),
|
|
'data_valuta' => $this->parseGesconDate($inc['data_pag'] ?? null),
|
|
'importo' => (float) ($inc['importo'] ?? 0),
|
|
'descrizione' => trim(($inc['descrizione_aggiuntiva'] ?? '') . ' ' . ($inc['nome_condomino'] ?? '')),
|
|
'ordinante' => $inc['nome_condomino'] ?? null,
|
|
'riferimento_bancario' => $inc['num_incasso'] ?? null,
|
|
'file_origine' => $inc['nome_file_pdf'] ?? null,
|
|
'stato' => 'da_riconciliare',
|
|
'metadati_gescon' => json_encode($inc),
|
|
]);
|
|
|
|
$imported++;
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing incasso EC", [
|
|
'year' => $year,
|
|
'incasso' => $inc,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Import rate emesse da generale_stabile.mdb
|
|
*/
|
|
private function importRateEmesse(string $year, string $mdbPath): int
|
|
{
|
|
$imported = 0;
|
|
|
|
// Unifica emes_det, emes_det_2, emes_gen
|
|
$tables = ['emes_det', 'emes_det_2', 'emes_gen'];
|
|
|
|
foreach ($tables as $table) {
|
|
if (!$this->mdbTableExists($mdbPath, $table)) {
|
|
continue;
|
|
}
|
|
|
|
$rate = $this->queryMdb($mdbPath, "SELECT * FROM {$table}");
|
|
|
|
usort($rate, function($a, $b) {
|
|
$dateA = $a['data_em'] ?? '';
|
|
$dateB = $b['data_em'] ?? '';
|
|
return strcmp($dateA, $dateB);
|
|
});
|
|
|
|
foreach ($rate as $rata) {
|
|
try {
|
|
// Logica import rate...
|
|
$imported++;
|
|
} catch (\Exception $e) {
|
|
Log::error("Error importing rata", ['table' => $table, 'rata' => $rata, 'error' => $e->getMessage()]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $imported;
|
|
}
|
|
|
|
/**
|
|
* Determina gestione ID basata su dati operazione
|
|
*/
|
|
private function determineGestioneId(string $year, array $operazione): int
|
|
{
|
|
$gestione = $operazione['gestione'] ?? 'O';
|
|
$nStra = (int) ($operazione['n_stra'] ?? 0);
|
|
|
|
switch (strtoupper($gestione)) {
|
|
case 'R':
|
|
return $this->gestioni[$year]['riscaldamento']->id;
|
|
|
|
case 'S':
|
|
// Crea gestione straordinaria se non esiste
|
|
return $this->getOrCreateStraordinaria($year, $nStra);
|
|
|
|
default:
|
|
return $this->gestioni[$year]['ordinaria']->id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ottiene o crea gestione straordinaria
|
|
*/
|
|
private function getOrCreateStraordinaria(string $year, int $nStra): int
|
|
{
|
|
$key = "straordinaria_{$nStra}";
|
|
$calendarYear = $this->directoryToYearMap[$year] ?? (int) $year;
|
|
|
|
if (! isset($this->gestioni[$year][$key])) {
|
|
$this->gestioni[$year][$key] = GestioneContabile::create([
|
|
'tenant_id' => $this->tenantId,
|
|
'stabile_id' => $this->stabileId,
|
|
'anno_gestione' => $calendarYear,
|
|
'tipo_gestione' => 'straordinaria',
|
|
'numero_straordinaria' => $nStra,
|
|
'denominazione' => "Gestione Straordinaria {$calendarYear} - {$nStra}",
|
|
'data_inizio' => "{$calendarYear}-01-01",
|
|
'data_fine' => "{$calendarYear}-12-31",
|
|
'stato' => 'aperta',
|
|
'protocollo_prefix' => "S{$calendarYear}-{$nStra}",
|
|
]);
|
|
}
|
|
|
|
return $this->gestioni[$year][$key]->id;
|
|
}
|
|
|
|
/**
|
|
* Query diretta su file MDB
|
|
*/
|
|
private function queryMdb(string $mdbPath, string $query): array
|
|
{
|
|
$query = preg_replace('/\s+/', ' ', trim($query));
|
|
// Usa mdb-tools per query diretta
|
|
$command = sprintf(
|
|
'echo %s | mdb-sql -P -F -d %s %s',
|
|
escapeshellarg($query),
|
|
escapeshellarg("\t"),
|
|
escapeshellarg($mdbPath)
|
|
);
|
|
|
|
$output = shell_exec($command);
|
|
|
|
// Parser output MDB in array associativo
|
|
return $this->parseMdbOutput($output);
|
|
}
|
|
|
|
private function mdbTableExists(string $mdbPath, string $tableName): bool
|
|
{
|
|
if (!file_exists($mdbPath)) {
|
|
return false;
|
|
}
|
|
$output = shell_exec("mdb-tables -1 " . escapeshellarg($mdbPath) . " 2>/dev/null");
|
|
if (!$output) {
|
|
return false;
|
|
}
|
|
$tables = array_map('strtolower', array_map('trim', explode("\n", trim($output))));
|
|
return in_array(strtolower($tableName), $tables, true);
|
|
}
|
|
|
|
private function resolveContoBancarioId(string $mdbPath, ?string $codCassa): int
|
|
{
|
|
$stabileCode = '0021'; // Default fallback
|
|
if (preg_match('/legacy\/([^\/]+)/', str_replace('\\', '/', $mdbPath), $matches)) {
|
|
$stabileCode = $matches[1];
|
|
}
|
|
|
|
$cod = strtoupper(trim($codCassa ?? ''));
|
|
if (empty($cod)) {
|
|
$cod = 'CON'; // Default fallback to cash cassa
|
|
}
|
|
|
|
// Try to match by code: stabileCode-cod
|
|
$conto = DB::table('conti_bancari')
|
|
->where('tenant_id', $this->tenantId)
|
|
->where('codice', "{$stabileCode}-{$cod}")
|
|
->first();
|
|
|
|
if ($conto) {
|
|
return (int) $conto->id;
|
|
}
|
|
|
|
// Try fallback to any account for this stable prefix
|
|
$conto = DB::table('conti_bancari')
|
|
->where('tenant_id', $this->tenantId)
|
|
->where('codice', 'like', "{$stabileCode}-%")
|
|
->first();
|
|
|
|
if ($conto) {
|
|
return (int) $conto->id;
|
|
}
|
|
|
|
// Final fallback to 1
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Parser output mdb-sql in array
|
|
*/
|
|
private function parseMdbOutput(string $output): array
|
|
{
|
|
$lines = explode("\n", trim($output));
|
|
if (empty($lines)) {
|
|
return [];
|
|
}
|
|
|
|
$headers = array_map('strtolower', array_map('trim', explode("\t", array_shift($lines))));
|
|
$headerCount = count($headers);
|
|
$data = [];
|
|
|
|
$currentValues = [];
|
|
foreach ($lines as $line) {
|
|
if ($line === '' && empty($currentValues)) {
|
|
continue;
|
|
}
|
|
|
|
$values = explode("\t", $line);
|
|
if (empty($currentValues)) {
|
|
$currentValues = $values;
|
|
} else {
|
|
$lastIdx = count($currentValues) - 1;
|
|
$currentValues[$lastIdx] .= "\n" . array_shift($values);
|
|
$currentValues = array_merge($currentValues, $values);
|
|
}
|
|
|
|
if (count($currentValues) >= $headerCount) {
|
|
$rowValues = array_slice($currentValues, 0, $headerCount);
|
|
$leftover = array_slice($currentValues, $headerCount);
|
|
|
|
try {
|
|
$data[] = array_combine($headers, $rowValues);
|
|
} catch (\Throwable $e) {
|
|
// Ignora o logga errori per evitare crash
|
|
}
|
|
$currentValues = $leftover;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Genera protocollo per operazione
|
|
*/
|
|
private function generateProtocollo(int $gestioneId, array $operazione): array
|
|
{
|
|
$gestione = GestioneContabile::find($gestioneId);
|
|
|
|
$numero = (isset($operazione['n_spe']) && is_numeric($operazione['n_spe']) && (int) $operazione['n_spe'] > 0)
|
|
? (int) $operazione['n_spe']
|
|
: (DB::table('operazioni_contabili')->where('gestione_id', $gestioneId)->max('protocollo_numero') ?? 0) + 1;
|
|
|
|
return [
|
|
'numero' => $numero,
|
|
'completo' => ($gestione->protocollo_prefix ?? 'O') . '-' . str_pad($numero, 4, '0', STR_PAD_LEFT),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Snapshot voce spesa
|
|
*/
|
|
private function getVoceSpesaSnapshot(string $mdbPath, string $codVoce): array
|
|
{
|
|
if (empty($codVoce)) {
|
|
return [];
|
|
}
|
|
|
|
$voci = $this->queryMdb($mdbPath, "
|
|
SELECT * FROM voci_spesa
|
|
WHERE cod_voce = '{$codVoce}'
|
|
");
|
|
|
|
return $voci[0] ?? [];
|
|
}
|
|
|
|
/**
|
|
* Parse data Gescon
|
|
*/
|
|
private function parseGesconDate($dateValue): ?string
|
|
{
|
|
if (empty($dateValue)) {
|
|
return null;
|
|
}
|
|
|
|
// Prova vari formati date Gescon/Access
|
|
$formats = [
|
|
'm/d/y H:i:s', 'd/m/y H:i:s',
|
|
'd/m/y', 'm/d/y', 'y-m-d',
|
|
'Y-m-d H:i:s', 'd/m/Y H:i:s', 'm/d/Y H:i:s',
|
|
'Y-m-d', 'd/m/Y', 'm/d/Y',
|
|
];
|
|
|
|
foreach ($formats as $format) {
|
|
$date = \DateTime::createFromFormat($format, $dateValue);
|
|
if ($date !== false) {
|
|
return $date->format('Y-m-d');
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|