Allineamento sviluppo: risoluzione bug mdb-sql e o.num_fat, allineamento importazione voci/operazioni, integrazione tab pagamenti CBILL e migrazioni rate/operazioni
This commit is contained in:
parent
248bdd032c
commit
ab82c315fe
|
|
@ -75,7 +75,30 @@ public function handle(): int
|
|||
|
||||
if ($year) {
|
||||
if (Schema::connection('gescon_import')->hasColumn('operazioni', 'legacy_year')) {
|
||||
if (is_numeric($year) && strlen((string)$year) === 4 && (int)$year >= 1900) {
|
||||
$cartelle = DB::connection('gescon_import')
|
||||
->table('gestioni_annuali')
|
||||
->whereIn('cod_stabile', $legacyStableCodes)
|
||||
->get(['cartella', 'anno_ordinario', 'anno_riscaldamento', 'descrizione'])
|
||||
->filter(function ($row) use ($year): bool {
|
||||
$annoOrd = $this->extractLegacyYearInt((string) ($row->anno_ordinario ?? ''));
|
||||
$annoRisc = $this->extractLegacyYearInt((string) ($row->anno_riscaldamento ?? ''));
|
||||
$annoDesc = $this->extractLegacyYearInt((string) ($row->descrizione ?? ''));
|
||||
return $annoOrd === (int) $year || $annoRisc === (int) $year || $annoDesc === (int) $year;
|
||||
})
|
||||
->pluck('cartella')
|
||||
->filter()
|
||||
->unique()
|
||||
->toArray();
|
||||
|
||||
if ($cartelle !== []) {
|
||||
$query->whereIn('legacy_year', $cartelle);
|
||||
} else {
|
||||
$query->where('legacy_year', (string) $year);
|
||||
}
|
||||
} else {
|
||||
$query->where('legacy_year', (string) $year);
|
||||
}
|
||||
} else {
|
||||
$query->whereYear('dt_spe', $year);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1014,6 +1014,12 @@ private function stepUnita(?int $limit): int
|
|||
if (Schema::hasColumn('unita_immobiliari', 'deleted_at') && ! empty($exists->deleted_at)) {
|
||||
$patch['deleted_at'] = null;
|
||||
}
|
||||
if (Schema::hasColumn('unita_immobiliari', 'note')) {
|
||||
$uNote = trim((string) ($r->note ?? $r->note_cond ?? ''));
|
||||
if ($uNote !== '' && (string) ($exists->note ?? '') !== $uNote) {
|
||||
$patch['note'] = $uNote;
|
||||
}
|
||||
}
|
||||
if (($unitClassification['is_condominiale'] ?? false) && Schema::hasColumn('unita_immobiliari', 'denominazione')) {
|
||||
$condoName = $this->firstNonEmptyStr([
|
||||
$unitClassification['denominazione'] ?? null,
|
||||
|
|
@ -1078,6 +1084,7 @@ private function stepUnita(?int $limit): int
|
|||
'stato_occupazione' => 'occupata_proprietario',
|
||||
'attiva' => true,
|
||||
'unita_demo' => false,
|
||||
'note' => Schema::hasColumn('unita_immobiliari', 'note') ? (trim((string) ($r->note ?? $r->note_cond ?? '')) ?: null) : null,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
|
@ -1163,7 +1170,7 @@ private function stepUnita(?int $limit): int
|
|||
}
|
||||
|
||||
// Cleanup: rimuovi unità non presenti in condomin (evita "alieni")
|
||||
if (! $this->isDryRun && $this->option('stabile') && ! $this->option('update-only')) {
|
||||
if (false && ! $this->isDryRun && $this->option('stabile') && ! $this->option('update-only')) {
|
||||
$condRows = $this->currentSnapshotCondominQuery()
|
||||
->select('scala', 'interno', 'cod_cond')
|
||||
->get();
|
||||
|
|
@ -2665,12 +2672,13 @@ private function stepVoci(?int $limit): int
|
|||
}
|
||||
$exists = $q->orderBy('id')->first();
|
||||
|
||||
if (! $exists) {
|
||||
if (! $exists && $gestioneId === null) {
|
||||
$exists = DB::table('voci_spesa')
|
||||
->where('codice', $codiceDominio)
|
||||
->when($stabileId && Schema::hasColumn('voci_spesa', 'stabile_id'), function ($qq) use ($stabileId) {
|
||||
$qq->where('stabile_id', $stabileId);
|
||||
})
|
||||
->whereNull('gestione_contabile_id')
|
||||
->orderBy('id')
|
||||
->first();
|
||||
}
|
||||
|
|
@ -3270,12 +3278,18 @@ private function stepOperazioni(?int $limit): int
|
|||
$count++;
|
||||
continue;
|
||||
}
|
||||
$exists = DB::table('operazioni_contabili')->where('legacy_id', $legacy)->first();
|
||||
if ($exists && ! $this->option('update-only')) {
|
||||
$gestioneId = $this->resolveGestioneId($o->legacy_year ?? ($o->anno ?? null), $o->compet ?? null, $o->gestione ?? null, $o->n_stra ?? null);
|
||||
if (! $gestioneId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$gestioneId = $this->resolveGestioneId($o->legacy_year ?? ($o->anno ?? null), $o->compet ?? null, $o->gestione ?? null, $o->n_stra ?? null);
|
||||
$exists = DB::table('operazioni_contabili')
|
||||
->where('legacy_id', $legacy)
|
||||
->where('gestione_id', $gestioneId)
|
||||
->first();
|
||||
if ($exists && ! $this->option('update-only')) {
|
||||
continue;
|
||||
}
|
||||
$protoNum = (isset($o->n_spe) && is_numeric($o->n_spe) && (int) $o->n_spe > 0) ? (int) $o->n_spe : $this->nextProtocolNumber();
|
||||
$prefix = null;
|
||||
if ($gestioneId) {
|
||||
|
|
@ -4224,25 +4238,19 @@ private function ensurePianoRateizzazionePerEmissione(string $codStabile, int $n
|
|||
->where('numero_emissione', $numeroEmissione)
|
||||
->sum(DB::raw('COALESCE(importo_dovuto_euro, importo_dovuto, 0)'));
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'stabile_id' => $stabileId,
|
||||
'unita_immobiliare_id' => null,
|
||||
'descrizione' => $descr,
|
||||
'importo_totale' => $totale,
|
||||
'numero_rate' => 1,
|
||||
'importo_rata' => $totale,
|
||||
'data_inizio' => $dataInizio,
|
||||
'data_fine' => $dataFine,
|
||||
'data_prima_rata' => $dataInizio,
|
||||
'frequenza' => 'MENSILE',
|
||||
'stato' => 'ATTIVO',
|
||||
'importo_pagato' => 0,
|
||||
'importo_residuo' => $totale,
|
||||
'note' => 'emissione=' . $numeroEmissione . '|cod_stabile=' . $codStabile,
|
||||
'codice_piano' => \App\Models\PianoRateizzazione::generaCodicePiano(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
|
||||
if (! $this->isDryRun) {
|
||||
$id = DB::table('piano_rateizzazione')->insertGetId($payload);
|
||||
return is_numeric($id) ? (int) $id : null;
|
||||
|
|
@ -5272,20 +5280,18 @@ private function importLegacySubsystemForAssemblea(int $assembleaId, array $row,
|
|||
if (!$unita) continue;
|
||||
|
||||
// Trova il soggetto principale legato all'unità
|
||||
$soggettoId = DB::table('diritti_reali_unita')
|
||||
$soggettoId = DB::table('proprieta')
|
||||
->where('unita_immobiliare_id', $unita->id)
|
||||
->orderByRaw("CASE WHEN tipo_diritto = 'proprieta' THEN 1 ELSE 2 END")
|
||||
->value('soggetto_id');
|
||||
|
||||
if (!$soggettoId) {
|
||||
$soggettoId = DB::table('soggetti_unita_immobiliari')
|
||||
->where('unita_immobiliare_id', $unita->id)
|
||||
->value('soggetto_id');
|
||||
}
|
||||
|
||||
if (!$soggettoId) continue;
|
||||
|
||||
$tipoPartecipazione = 'personale';
|
||||
$pda = strtolower(trim((string)($pr['p_d_a'] ?? '')));
|
||||
if ($pda === 'a' || $pda === 'assente') {
|
||||
continue;
|
||||
}
|
||||
if ($pda === 'd' || strpos($pda, 'delega') !== false) {
|
||||
$tipoPartecipazione = 'delega';
|
||||
}
|
||||
|
|
@ -5351,16 +5357,11 @@ private function importLegacySubsystemForAssemblea(int $assembleaId, array $row,
|
|||
|
||||
if (!$unita) continue;
|
||||
|
||||
$soggettoId = DB::table('diritti_reali_unita')
|
||||
$soggettoId = DB::table('proprieta')
|
||||
->where('unita_immobiliare_id', $unita->id)
|
||||
->orderByRaw("CASE WHEN tipo_diritto = 'proprieta' THEN 1 ELSE 2 END")
|
||||
->value('soggetto_id');
|
||||
|
||||
if (!$soggettoId) {
|
||||
$soggettoId = DB::table('soggetti_unita_immobiliari')
|
||||
->where('unita_immobiliare_id', $unita->id)
|
||||
->value('soggetto_id');
|
||||
}
|
||||
|
||||
if (!$soggettoId) continue;
|
||||
|
||||
// Determina scelta voto
|
||||
|
|
@ -7253,7 +7254,7 @@ private function extractLegacySnapshotPartyMeta(object $row, string $role): arra
|
|||
'citta' => trim((string) ($isTenant ? ($row->inquil_citta ?? '') : ($row->citta ?? ''))) ?: null,
|
||||
'provincia' => trim((string) ($isTenant ? ($row->inquil_pr ?? '') : ($row->pr ?? ''))) ?: null,
|
||||
'titolo' => trim((string) ($isTenant ? ($row->titolo_inq ?? '') : ($row->titolo_cond ?? ''))) ?: null,
|
||||
'note' => trim((string) ($isTenant ? ($row->inquil_note ?? '') : ($row->note_cond ?? ''))) ?: null,
|
||||
'note' => trim((string) ($isTenant ? ($row->note_inquilino ?? $row->inquil_note ?? '') : ($row->note ?? $row->note_cond ?? ''))) ?: null,
|
||||
'e_lostesso_di' => $isTenant ? null : (trim((string) ($row->e_lostesso_di ?? '')) ?: null),
|
||||
'codice_fiscale' => $isTenant
|
||||
? (trim((string) ($row->inquilin_cod_fisc ?? $row->inquil_cod_fisc ?? '')) ?: null)
|
||||
|
|
@ -7319,68 +7320,118 @@ private function extractLegacyPrefixedPayload(object $row, array $prefixes): arr
|
|||
return $payload;
|
||||
}
|
||||
|
||||
private function resolveLinkedLegacyCondIds(string $codStabile, string $startCondId): array
|
||||
{
|
||||
$linked = [$startCondId];
|
||||
$toProcess = [$startCondId];
|
||||
$visited = [];
|
||||
|
||||
while (! empty($toProcess)) {
|
||||
$current = array_shift($toProcess);
|
||||
if (isset($visited[$current])) {
|
||||
continue;
|
||||
}
|
||||
$visited[$current] = true;
|
||||
|
||||
$rows = DB::connection('gescon_import')
|
||||
->table('condomin')
|
||||
->where('cod_stabile', $codStabile)
|
||||
->where(function($q) use ($current) {
|
||||
$q->where('cod_cond', $current);
|
||||
$colName = Schema::connection('gescon_import')->hasColumn('condomin', 'id_cond') ? 'id_cond' : null;
|
||||
if ($colName) {
|
||||
$q->orWhere($colName, $current);
|
||||
}
|
||||
if (Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_id_cond')) {
|
||||
$q->orWhere('legacy_id_cond', $current);
|
||||
}
|
||||
})
|
||||
->get();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$cc = trim((string) ($row->cod_cond ?? ''));
|
||||
if ($cc !== '' && ! in_array($cc, $linked, true)) {
|
||||
$linked[] = $cc;
|
||||
$toProcess[] = $cc;
|
||||
}
|
||||
|
||||
foreach (['subentro_prima_cera', 'subentro_adesso_ce'] as $field) {
|
||||
if (isset($row->$field)) {
|
||||
$val = trim((string) $row->$field);
|
||||
if ($val !== '' && ! in_array($val, $linked, true)) {
|
||||
$linked[] = $val;
|
||||
$toProcess[] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $linked;
|
||||
}
|
||||
|
||||
private function loadLegacyCondominHistoryRows(object $row, ?string $legacyCondId): array
|
||||
{
|
||||
$codStabile = trim((string) ($row->cod_stabile ?? ''));
|
||||
$codCond = trim((string) ($row->cod_cond ?? ''));
|
||||
$stableCondId = $this->extractStableLegacyCondId($row, $legacyCondId);
|
||||
|
||||
if ($codStabile === '' || ($stableCondId === null && $codCond === '')) {
|
||||
if ($codStabile === '') {
|
||||
return [$row];
|
||||
}
|
||||
|
||||
$startCondId = $stableCondId !== null && $stableCondId !== '' ? $stableCondId : ($codCond !== '' ? $codCond : null);
|
||||
$linkedCondIds = [];
|
||||
if ($startCondId !== null) {
|
||||
$linkedCondIds = $this->resolveLinkedLegacyCondIds($codStabile, $startCondId);
|
||||
}
|
||||
|
||||
$query = DB::connection('gescon_import')
|
||||
->table('condomin')
|
||||
->where('cod_stabile', $codStabile)
|
||||
->orderByDesc('legacy_year')
|
||||
->orderByDesc('id');
|
||||
->where('cod_stabile', $codStabile);
|
||||
|
||||
if ($stableCondId !== null && $stableCondId !== '') {
|
||||
$colName = Schema::connection('gescon_import')->hasColumn('condomin', 'cod_cond') ? 'cod_cond' : 'id_cond';
|
||||
$query->where(function($q) use ($linkedCondIds, $row, $codCond): void {
|
||||
if (! empty($linkedCondIds)) {
|
||||
$q->whereIn('cod_cond', $linkedCondIds);
|
||||
$colName = Schema::connection('gescon_import')->hasColumn('condomin', 'id_cond') ? 'id_cond' : null;
|
||||
if ($colName) {
|
||||
$q->orWhereIn($colName, $linkedCondIds);
|
||||
}
|
||||
if (Schema::connection('gescon_import')->hasColumn('condomin', 'legacy_id_cond')) {
|
||||
$query->where(function ($subQuery) use ($stableCondId, $colName): void {
|
||||
$subQuery->where($colName, $stableCondId)
|
||||
->orWhere('legacy_id_cond', $stableCondId);
|
||||
});
|
||||
} else {
|
||||
$query->where($colName, $stableCondId);
|
||||
$q->orWhereIn('legacy_id_cond', $linkedCondIds);
|
||||
}
|
||||
} elseif ($codCond !== '') {
|
||||
$query->where('cod_cond', $codCond);
|
||||
$q->where('cod_cond', $codCond);
|
||||
}
|
||||
|
||||
// Corrispondenza fisica (scala, interno, subalterno)
|
||||
$scala = trim((string) ($row->scala ?? ''));
|
||||
$interno = trim((string) ($row->interno ?? $row->int ?? ''));
|
||||
$sub = trim((string) ($row->subalterno ?? $row->sub ?? ''));
|
||||
|
||||
if ($scala !== '' && ($interno !== '' || $sub !== '')) {
|
||||
$q->orWhere(function($subQ) use ($scala, $interno, $sub): void {
|
||||
$subQ->where('scala', $scala);
|
||||
if ($interno !== '') {
|
||||
$subQ->where(function($sub2) use ($interno): void {
|
||||
$sub2->where('interno', $interno);
|
||||
if (Schema::connection('gescon_import')->hasColumn('condomin', 'int')) {
|
||||
$sub2->orWhere('int', $interno);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($sub !== '' && Schema::connection('gescon_import')->hasColumn('condomin', 'subalterno')) {
|
||||
$subQ->where('subalterno', $sub);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$query->orderByDesc('legacy_year')
|
||||
->orderByDesc('id');
|
||||
|
||||
$historyRows = $query->get()->all();
|
||||
|
||||
if ($historyRows === [] && $codCond !== '') {
|
||||
$historyRows = DB::connection('gescon_import')
|
||||
->table('condomin')
|
||||
->where('cod_stabile', $codStabile)
|
||||
->where('cod_cond', $codCond)
|
||||
->orderByDesc('legacy_year')
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
|
||||
if ($historyRows === []) {
|
||||
$partyName = $this->extractLegacyPartyName($row);
|
||||
if ($partyName !== null) {
|
||||
$historyRows = DB::connection('gescon_import')
|
||||
->table('condomin')
|
||||
->where('cod_stabile', $codStabile)
|
||||
->where(function ($subQuery) use ($partyName): void {
|
||||
$subQuery->where('nom_cond', $partyName);
|
||||
if (Schema::connection('gescon_import')->hasColumn('condomin', 'proprietario_denominazione')) {
|
||||
$subQuery->orWhere('proprietario_denominazione', $partyName);
|
||||
}
|
||||
})
|
||||
->orderByDesc('legacy_year')
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
if ($historyRows === []) {
|
||||
return [$row];
|
||||
}
|
||||
|
|
@ -7486,22 +7537,31 @@ private function loadLegacyComproprietariHistoryRows(object $row, ?string $legac
|
|||
}
|
||||
|
||||
$codStabile = trim((string) ($row->cod_stabile ?? ''));
|
||||
$idCondRaw = trim((string) ($this->extractStableLegacyCondId($row, $legacyCondId) ?? $row->cod_cond ?? ''));
|
||||
$codCond = trim((string) ($row->cod_cond ?? ''));
|
||||
$stableCondId = $this->extractStableLegacyCondId($row, $legacyCondId);
|
||||
|
||||
if ($codStabile === '' || $idCondRaw === '') {
|
||||
if ($codStabile === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$startCondId = $stableCondId !== null && $stableCondId !== '' ? $stableCondId : ($codCond !== '' ? $codCond : null);
|
||||
$linkedCondIds = [];
|
||||
if ($startCondId !== null) {
|
||||
$linkedCondIds = $this->resolveLinkedLegacyCondIds($codStabile, $startCondId);
|
||||
}
|
||||
|
||||
$query = DB::connection('gescon_import')
|
||||
->table('comproprietari')
|
||||
->where('cod_stabile', $codStabile)
|
||||
->orderByDesc('legacy_year')
|
||||
->orderByDesc('id');
|
||||
|
||||
if (is_numeric($idCondRaw)) {
|
||||
$query->where('id_cond', (int) $idCondRaw);
|
||||
if (! empty($linkedCondIds)) {
|
||||
$query->whereIn('id_cond', $linkedCondIds);
|
||||
} elseif ($startCondId !== null) {
|
||||
$query->where('id_cond', $startCondId);
|
||||
} else {
|
||||
$query->where('id_cond', $idCondRaw);
|
||||
return [];
|
||||
}
|
||||
|
||||
$historyRows = $query->get()->all();
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ public function handle()
|
|||
|
||||
$this->displayImportPlan($yearsToImport);
|
||||
|
||||
if (!$this->option('dry-run') && !$this->confirm('Proceed with multi-year import?')) {
|
||||
if (!$this->option('dry-run') && !$this->option('no-interaction') && !$this->confirm('Proceed with multi-year import?')) {
|
||||
$this->info('Import cancelled by user.');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
|
@ -102,7 +102,7 @@ private function scanAvailableYears(string $gesconDir): array
|
|||
}
|
||||
}
|
||||
|
||||
ksort($years);
|
||||
krsort($years);
|
||||
return $years;
|
||||
}
|
||||
|
||||
|
|
@ -228,9 +228,9 @@ private function countTableRecords(string $mdbPath, string $tableName): int
|
|||
}
|
||||
|
||||
$output = shell_exec(sprintf(
|
||||
'mdb-sql %s <<< "SELECT COUNT(*) FROM %s" 2>/dev/null',
|
||||
escapeshellarg($mdbPath),
|
||||
escapeshellarg($tableName)
|
||||
'echo "SELECT COUNT(*) FROM %s" | mdb-sql %s 2>/dev/null',
|
||||
$tableName,
|
||||
escapeshellarg($mdbPath)
|
||||
));
|
||||
|
||||
if ($output && preg_match('/(\d+)/', $output, $matches)) {
|
||||
|
|
@ -247,7 +247,14 @@ private function executeImport(string $tenantId, string $gesconDir, array $years
|
|||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$service = new MultiYearGesconImportService($tenantId);
|
||||
$stabileCode = basename(rtrim($gesconDir, '/'));
|
||||
$stabileRow = DB::table('stabili')
|
||||
->where('codice_stabile', $stabileCode)
|
||||
->orWhere('codice_stabile', ltrim($stabileCode, '0'))
|
||||
->first();
|
||||
$stabileId = $stabileRow?->id;
|
||||
|
||||
$service = new MultiYearGesconImportService($tenantId, $stabileId, $gesconDir);
|
||||
|
||||
// Delete existing data if force
|
||||
if ($this->option('force')) {
|
||||
|
|
@ -289,9 +296,66 @@ private function cleanExistingData(string $tenantId, array $years): void
|
|||
{
|
||||
$this->warn("🗑️ Cleaning existing data for years: " . implode(', ', $years));
|
||||
|
||||
$stabileCode = basename(rtrim($this->argument('gescon-dir'), '/'));
|
||||
$stabileRow = DB::table('stabili')
|
||||
->where('codice_stabile', $stabileCode)
|
||||
->orWhere('codice_stabile', ltrim($stabileCode, '0'))
|
||||
->first();
|
||||
$stabileId = $stabileRow?->id;
|
||||
|
||||
// Resolve calendar years
|
||||
$calendarYears = [];
|
||||
$generaleMdb = rtrim($this->argument('gescon-dir'), '/') . '/generale_stabile.mdb';
|
||||
if (is_file($generaleMdb)) {
|
||||
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 && in_array($id, array_map('intval', $years), true)) {
|
||||
$calendarYears[] = $yr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
if (empty($calendarYears)) {
|
||||
$calendarYears = array_map('intval', $years);
|
||||
}
|
||||
|
||||
$gestioneIds = GestioneContabile::forTenant($tenantId)
|
||||
->where('stabile_id', $stabileId)
|
||||
->whereIn('anno_gestione', $calendarYears)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
if (!empty($gestioneIds)) {
|
||||
// Nullify foreign keys to prevent constraint violations
|
||||
DB::table('lavori_straordinari')->whereIn('gestione_id', $gestioneIds)->update(['gestione_id' => null]);
|
||||
DB::table('contabilita_registrazioni')->whereIn('gestione_id', $gestioneIds)->update(['gestione_id' => null]);
|
||||
DB::table('contabilita_fatture_fornitori')->whereIn('gestione_id', $gestioneIds)->update(['gestione_id' => null]);
|
||||
|
||||
$deletedIncassi = DB::table('incassi')->whereIn('gestione_id', $gestioneIds)->delete();
|
||||
$deletedOps = DB::table('operazioni_contabili')->whereIn('gestione_id', $gestioneIds)->delete();
|
||||
$deletedRDA = DB::table('registro_ritenute_acconto')->whereIn('gestione_id', $gestioneIds)->delete();
|
||||
$deletedIncEc = DB::table('incassi_estratto_conto')->whereIn('gestione_id', $gestioneIds)->delete();
|
||||
|
||||
$this->line(" Deleted {$deletedIncassi} incassi, {$deletedOps} operazioni, {$deletedRDA} ritenute, {$deletedIncEc} incassi EC");
|
||||
}
|
||||
|
||||
// Delete gestioni and cascade
|
||||
$deletedGestioni = GestioneContabile::forTenant($tenantId)
|
||||
->whereIn('anno_gestione', $years)
|
||||
->where('stabile_id', $stabileId)
|
||||
->whereIn('anno_gestione', $calendarYears)
|
||||
->delete();
|
||||
|
||||
$this->line(" Deleted {$deletedGestioni} gestioni");
|
||||
|
|
|
|||
|
|
@ -114,7 +114,10 @@ private function ensureCondominColumns(): void
|
|||
return;
|
||||
}
|
||||
|
||||
$cols = ['inquil_nome', 'inquil_cod_fisc', 'e_mail_inquilino', 'inquil_tel1', 'inquil_indir'];
|
||||
$cols = [
|
||||
'inquil_nome', 'inquil_cod_fisc', 'e_mail_inquilino', 'inquil_tel1', 'inquil_indir',
|
||||
'subentro_prima_cera', 'subentro_adesso_ce', 'subentrato_dal', 'attivo_fino_al'
|
||||
];
|
||||
$connection = Schema::connection('gescon_import');
|
||||
|
||||
Schema::connection('gescon_import')->table('condomin', function (Blueprint $table) use ($cols, $connection): void {
|
||||
|
|
@ -160,6 +163,8 @@ private function persistStagingRow(string $stagingTable, array $filtered): void
|
|||
if ($stagingTable === 'operazioni' && array_key_exists('id_operaz', $filtered) && $filtered['id_operaz'] !== null) {
|
||||
$query->updateOrInsert([
|
||||
'id_operaz' => $filtered['id_operaz'],
|
||||
'cod_stabile' => $filtered['cod_stabile'] ?? null,
|
||||
'legacy_year' => $filtered['legacy_year'] ?? null,
|
||||
], $filtered);
|
||||
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
use Filament\Support\Contracts\TranslatableContentDriver;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\TextInputColumn;
|
||||
use Filament\Tables\Columns\DatePickerColumn;
|
||||
use Filament\Tables\Columns\SelectColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
|
|
@ -281,7 +285,15 @@ public function table(Table $table): Table
|
|||
return;
|
||||
}
|
||||
|
||||
$tenantId = GestioneContabile::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->value('tenant_id');
|
||||
if (! is_string($tenantId) || trim($tenantId) === '') {
|
||||
$tenantId = (string) ($user->tenant_id ?? 'default');
|
||||
}
|
||||
|
||||
$gestione = new GestioneContabile();
|
||||
$gestione->tenant_id = $tenantId;
|
||||
$gestione->stabile_id = $stabileId;
|
||||
$gestione->anno_gestione = (int) ($data['anno_gestione'] ?? now()->year);
|
||||
$gestione->tipo_gestione = (string) ($data['tipo_gestione'] ?? 'ordinaria');
|
||||
|
|
@ -356,43 +368,28 @@ public function table(Table $table): Table
|
|||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
TextColumn::make('denominazione')
|
||||
TextInputColumn::make('denominazione')
|
||||
->label('Denominazione')
|
||||
->searchable()
|
||||
->wrap(),
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('origine_dati')
|
||||
->label('Origine')
|
||||
->getStateUsing(function (GestioneContabile $record): string {
|
||||
if (filled($record->codice_archivio_legacy)) {
|
||||
return 'Legacy collegata';
|
||||
}
|
||||
DatePickerColumn::make('data_inizio')
|
||||
->label('Data inizio')
|
||||
->native(false),
|
||||
|
||||
return 'Locale';
|
||||
})
|
||||
->badge()
|
||||
->color(fn(string $state): string => $state === 'Legacy collegata' ? 'success' : 'gray'),
|
||||
DatePickerColumn::make('data_fine')
|
||||
->label('Data fine')
|
||||
->native(false),
|
||||
|
||||
TextColumn::make('periodo_label')
|
||||
->label('Periodo')
|
||||
->getStateUsing(fn(GestioneContabile $record): string => $record->periodo_label)
|
||||
->wrap(),
|
||||
|
||||
TextColumn::make('stato')
|
||||
SelectColumn::make('stato')
|
||||
->label('Stato')
|
||||
->formatStateUsing(fn(string $state): string => ucfirst($state))
|
||||
->badge()
|
||||
->color(fn(string $state): string => match ($state) {
|
||||
'aperta' => 'success',
|
||||
'consolidata' => 'warning',
|
||||
'chiusa' => 'gray',
|
||||
default => 'gray',
|
||||
})
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
->options([
|
||||
'aperta' => 'Aperta',
|
||||
'consolidata' => 'Consolidata',
|
||||
'chiusa' => 'Chiusa',
|
||||
]),
|
||||
|
||||
IconColumn::make('gestione_attiva')
|
||||
->label('Attiva')
|
||||
->boolean(),
|
||||
ToggleColumn::make('gestione_attiva')
|
||||
->label('Attiva'),
|
||||
|
||||
TextColumn::make('mesi_rate')
|
||||
->label('Mesi rate')
|
||||
|
|
|
|||
|
|
@ -1766,7 +1766,10 @@ private function resolveRiscaldamentoCandidateInvoices(?string $tipoGestione = n
|
|||
->where('f.stabile_id', $stabileId)
|
||||
->whereNotNull('f.fattura_elettronica_id')
|
||||
->when($fornitoreIds !== [], fn($builder) => $builder->whereIn('f.fornitore_id', $fornitoreIds))
|
||||
->where('g.tipo_gestione', $tipoGestione)
|
||||
->where(function ($b) use ($tipoGestione) {
|
||||
$b->where('g.tipo_gestione', $tipoGestione)
|
||||
->orWhereNull('f.gestione_id');
|
||||
})
|
||||
->when($dal && $al, fn($builder) => $builder->whereBetween('f.data_documento', [$dal, $al]))
|
||||
->when(!($dal && $al), function($builder) use ($year) {
|
||||
$builder->when(Schema::hasColumn('gestioni_contabili', 'anno_gestione'), fn($b) => $b->where('g.anno_gestione', $year))
|
||||
|
|
@ -2839,7 +2842,7 @@ public function getRiscaldamentoFeEstratteRowsProperty(): array
|
|||
$pagamento = is_array($payload['pagamento'] ?? null) ? $payload['pagamento'] : [];
|
||||
$riepilogoLetture = is_array($payload['riepilogo_letture'] ?? null) ? $payload['riepilogo_letture'] : [];
|
||||
|
||||
$servizio = $this->resolveRiscaldamentoServizioForIdentifiers($servizi, $codici, $contatore) ?? $defaultServizio;
|
||||
$servizio = $this->resolveRiscaldamentoServizioForIdentifiers($servizi, $codici, $contatore, $fattura);
|
||||
|
||||
[$periodoDal, $periodoAl] = $this->resolveRiscaldamentoPeriodsFromPayload($consumi, is_string($fattura->consumo_riferimento ?? null) ? (string) $fattura->consumo_riferimento : null);
|
||||
$windowFrom = $this->extractRiscaldamentoWindowDate($payload, ['dal', 'from', 'inizio', 'start']);
|
||||
|
|
@ -2916,7 +2919,9 @@ public function getRiscaldamentoFeEstratteRowsProperty(): array
|
|||
'numero_fattura' => trim((string) ($fattura->numero_fattura ?? $fattura->sdi_file ?? ('FE #' . (int) $fattura->id))),
|
||||
'data_fattura' => optional($fattura->data_fattura)?->format('d/m/Y'),
|
||||
'data_fattura_iso' => optional($fattura->data_fattura)?->format('Y-m-d'),
|
||||
'servizio' => trim((string) ($servizio?->nome ?? 'Riscaldamento stabile')),
|
||||
'servizio' => $servizio
|
||||
? (string) $servizio->nome
|
||||
: ($fattura->fornitore?->ragione_sociale ? 'Non abbinato: ' . $fattura->fornitore->ragione_sociale : 'Non abbinata / Fornitore sconosciuto'),
|
||||
'servizio_id' => (int) ($servizio?->id ?? 0),
|
||||
'matricola' => $matricola,
|
||||
'codice_utenza' => $codiceUtenza,
|
||||
|
|
@ -3021,7 +3026,7 @@ private function formatRiscaldamentoPeriodLabel(?string $from, ?string $to): str
|
|||
}
|
||||
|
||||
/** @param \Illuminate\Support\Collection<int, StabileServizio> $servizi */
|
||||
private function resolveRiscaldamentoServizioForIdentifiers($servizi, array $codici, array $contatore): ?StabileServizio
|
||||
private function resolveRiscaldamentoServizioForIdentifiers($servizi, array $codici, array $contatore, ?FatturaElettronica $fattura = null): ?StabileServizio
|
||||
{
|
||||
if (! $servizi instanceof \Illuminate\Support\Collection || $servizi->isEmpty()) {
|
||||
return null;
|
||||
|
|
@ -3031,20 +3036,24 @@ private function resolveRiscaldamentoServizioForIdentifiers($servizi, array $cod
|
|||
$targetUtenza = $this->normalizeRiscaldamentoIdentifier($codici['utenza'] ?? null);
|
||||
$targetCliente = $this->normalizeRiscaldamentoIdentifier($codici['cliente'] ?? null);
|
||||
$targetContratto = $this->normalizeRiscaldamentoIdentifier($codici['contratto'] ?? null);
|
||||
$fornitoreId = $fattura?->fornitore_id;
|
||||
|
||||
return $servizi
|
||||
->map(function (StabileServizio $servizio) use ($targetMatricola, $targetUtenza, $targetCliente, $targetContratto): array {
|
||||
->map(function (StabileServizio $servizio) use ($targetMatricola, $targetUtenza, $targetCliente, $targetContratto, $fornitoreId): array {
|
||||
$score = 0;
|
||||
if ($targetMatricola !== '' && $this->normalizeRiscaldamentoIdentifier($servizio->contatore_matricola) === $targetMatricola) {
|
||||
$score += 5;
|
||||
$score += 10;
|
||||
}
|
||||
if ($targetUtenza !== '' && $this->normalizeRiscaldamentoIdentifier($servizio->codice_utenza) === $targetUtenza) {
|
||||
$score += 4;
|
||||
$score += 8;
|
||||
}
|
||||
if ($targetContratto !== '' && $this->normalizeRiscaldamentoIdentifier($servizio->codice_contratto) === $targetContratto) {
|
||||
$score += 3;
|
||||
$score += 6;
|
||||
}
|
||||
if ($targetCliente !== '' && $this->normalizeRiscaldamentoIdentifier($servizio->codice_cliente) === $targetCliente) {
|
||||
$score += 4;
|
||||
}
|
||||
if ($fornitoreId && (int) $servizio->fornitore_id === (int) $fornitoreId) {
|
||||
$score += 2;
|
||||
}
|
||||
|
||||
|
|
@ -4043,8 +4052,7 @@ protected function ensureFornitoreStabileDefaultVoceSpesa(int $stabileId): void
|
|||
$voceSpesa = \App\Models\VoceSpesa::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->where(function ($q) use ($mostCommonVoceCode) {
|
||||
$q->where('conto_contabile', $mostCommonVoceCode)
|
||||
->orWhere('codice', $mostCommonVoceCode)
|
||||
$q->where('codice', $mostCommonVoceCode)
|
||||
->orWhere('legacy_codice', $mostCommonVoceCode);
|
||||
})
|
||||
->first();
|
||||
|
|
|
|||
|
|
@ -731,7 +731,7 @@ public function importElectronicReadings(): void
|
|||
|
||||
private function normalizeAcquaTab(string $tab): string
|
||||
{
|
||||
$allowed = ['dashboard', 'fatture', 'letture', 'generale', 'tariffe', 'servizi'];
|
||||
$allowed = ['dashboard', 'fatture', 'letture', 'generale', 'tariffe', 'servizi', 'pagamenti_cbill'];
|
||||
|
||||
return in_array($tab, $allowed, true) ? $tab : 'dashboard';
|
||||
}
|
||||
|
|
@ -3826,4 +3826,74 @@ public function getAcquaAltreVociLegacyProperty(): array
|
|||
'totale' => round((float) ($r->totale ?? 0), 2),
|
||||
])->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera pagamenti bancari associati ai codici CBILL delle fatture.
|
||||
*/
|
||||
public function getAcquaCbillPagamentiProperty(): array
|
||||
{
|
||||
$stabileId = $this->resolveActiveStabileId();
|
||||
if (! $stabileId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Recupera tutti i codici CBILL delle fatture elettroniche di questo stabile
|
||||
$cbillCodes = DB::table('fatture_elettroniche')
|
||||
->where('stabile_id', $stabileId)
|
||||
->whereNotNull('pagamento_cbill')
|
||||
->where('pagamento_cbill', '<>', '')
|
||||
->pluck('pagamento_cbill')
|
||||
->unique()
|
||||
->filter()
|
||||
->all();
|
||||
|
||||
if (empty($cbillCodes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Interroga i movimenti bancari contenenti uno qualsiasi di tali codici
|
||||
$movements = DB::table('contabilita_movimenti_banca')
|
||||
->where('stabile_id', $stabileId)
|
||||
->where(function ($query) use ($cbillCodes) {
|
||||
foreach ($cbillCodes as $code) {
|
||||
$query->orWhere('descrizione', 'like', "%{$code}%")
|
||||
->orWhere('descrizione_estesa', 'like', "%{$code}%");
|
||||
}
|
||||
})
|
||||
->orderByDesc('data')
|
||||
->get();
|
||||
|
||||
$rows = [];
|
||||
foreach ($movements as $m) {
|
||||
$matchedCbill = null;
|
||||
foreach ($cbillCodes as $code) {
|
||||
if (str_contains($m->descrizione, $code) || str_contains($m->descrizione_estesa, $code)) {
|
||||
$matchedCbill = $code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$invoice = null;
|
||||
if ($matchedCbill) {
|
||||
$invoice = DB::table('fatture_elettroniche')
|
||||
->where('stabile_id', $stabileId)
|
||||
->where('pagamento_cbill', $matchedCbill)
|
||||
->first(['id', 'numero_fattura', 'data_fattura', 'totale', 'fornitore_denominazione']);
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'data' => $m->data,
|
||||
'descrizione' => $m->descrizione,
|
||||
'importo' => $m->importo,
|
||||
'cbill' => $matchedCbill,
|
||||
'fattura_id' => $invoice?->id ?? null,
|
||||
'fattura_numero' => $invoice?->numero_fattura ?? '—',
|
||||
'fattura_data' => $invoice?->data_fattura ?? '—',
|
||||
'fattura_totale' => $invoice?->totale ?? 0.0,
|
||||
'fornitore' => $invoice?->fornitore_denominazione ?? '—',
|
||||
];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,9 +182,7 @@ protected function getTableQuery(): Builder
|
|||
return TabellaMillesimale::query()->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
$orderColumn = Schema::hasColumn('tabelle_millesimali', 'nord')
|
||||
? 'nord'
|
||||
: (Schema::hasColumn('tabelle_millesimali', 'ordine_visualizzazione') ? 'ordine_visualizzazione' : (Schema::hasColumn('tabelle_millesimali', 'ordinamento') ? 'ordinamento' : 'id'));
|
||||
$orderColumn = DB::raw('COALESCE(nord, ordinamento, ordine_visualizzazione, 999999)');
|
||||
|
||||
return TabellaMillesimale::query()
|
||||
->where('stabile_id', $activeStabileId)
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ public function table(Table $table): Table
|
|||
$inquilini = [];
|
||||
$altri = [];
|
||||
|
||||
$ruoli = $record->rubricaRuoliAttivi ?? collect();
|
||||
$ruoli = ($record->rubricaRuoliAttivi ?? collect())->unique(fn($r) => $r->rubrica_id . '|' . $r->ruolo_standard);
|
||||
foreach ($ruoli as $ruolo) {
|
||||
$nome = $ruolo->contatto?->nome_completo ?? '—';
|
||||
$cf = $ruolo->contatto?->codice_fiscale ? " ({$ruolo->contatto->codice_fiscale})" : '';
|
||||
|
|
@ -265,18 +265,32 @@ public function table(Table $table): Table
|
|||
|
||||
TextColumn::make('palazzina')
|
||||
->label('Pal.')
|
||||
->sortable(query: function (Builder $query, string $direction): Builder {
|
||||
return $query->orderBy('palazzina', $direction)
|
||||
->orderBy('scala', $direction)
|
||||
->orderByRaw("CASE WHEN unita_immobiliari.interno IS NULL OR unita_immobiliari.interno = '' THEN 1 ELSE 0 END")
|
||||
->orderByRaw("CASE WHEN unita_immobiliari.interno REGEXP '^[0-9]+' THEN CAST(unita_immobiliari.interno AS UNSIGNED) ELSE 999999 END")
|
||||
->orderBy('interno', $direction);
|
||||
})
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('scala')
|
||||
->label('Scala')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('piano')
|
||||
->label('Piano')
|
||||
->sortable()
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('interno')
|
||||
->label('Int.')
|
||||
->sortable(query: function (Builder $query, string $direction): Builder {
|
||||
return $query->orderByRaw("CASE WHEN unita_immobiliari.interno IS NULL OR unita_immobiliari.interno = '' THEN 1 ELSE 0 END")
|
||||
->orderByRaw("CASE WHEN unita_immobiliari.interno REGEXP '^[0-9]+' THEN CAST(unita_immobiliari.interno AS UNSIGNED) ELSE 999999 END")
|
||||
->orderBy('interno', $direction);
|
||||
})
|
||||
->formatStateUsing(function ($state, UnitaImmobiliare $record): string {
|
||||
$interno = trim((string) ($state ?? ''));
|
||||
if ($interno === '') {
|
||||
|
|
@ -315,6 +329,7 @@ public function table(Table $table): Table
|
|||
->label('Apri')
|
||||
->icon('heroicon-o-arrow-right')
|
||||
->url(fn(UnitaImmobiliare $record) => $unitaPageUrl . '?unita_id=' . $record->id),
|
||||
]);
|
||||
])
|
||||
->defaultSort('palazzina', 'asc');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ class CasseBancheMovimenti extends Page implements HasTable
|
|||
|
||||
public int $riconciliazioneOffset = 0;
|
||||
|
||||
public ?string $selectedCausaleForConfig = null;
|
||||
|
||||
public string $periodoTipo = 'mese';
|
||||
public int $periodoAnno;
|
||||
public int $periodoValore;
|
||||
|
|
@ -1134,6 +1136,72 @@ protected function getHeaderActions(): array
|
|||
->success()
|
||||
->send();
|
||||
}),
|
||||
|
||||
Action::make('configura_regola_causale')
|
||||
->label('Configura regola causale')
|
||||
->modalWidth('2xl')
|
||||
->form([
|
||||
TextInput::make('causale')
|
||||
->label('Causale bancaria')
|
||||
->required()
|
||||
->disabled(),
|
||||
|
||||
Select::make('conto_dare_id')
|
||||
->label('Conto Dare (addebito se importo > 0, es. Entrate)')
|
||||
->options(fn() => PianoConti::query()->orderBy('codice')->get()->mapWithKeys(fn(PianoConti $c) => [(string) $c->id => $c->codice . ' - ' . $c->descrizione])->all())
|
||||
->searchable()
|
||||
->required(),
|
||||
|
||||
Select::make('conto_avere_id')
|
||||
->label('Conto Avere (accredito se importo > 0, es. Uscite)')
|
||||
->options(fn() => PianoConti::query()->orderBy('codice')->get()->mapWithKeys(fn(PianoConti $c) => [(string) $c->id => $c->codice . ' - ' . $c->descrizione])->all())
|
||||
->searchable()
|
||||
->required(),
|
||||
|
||||
TextInput::make('label')
|
||||
->label('Nome regola (opzionale)')
|
||||
->nullable(),
|
||||
|
||||
Textarea::make('note')
|
||||
->label('Note')
|
||||
->rows(2)
|
||||
->nullable(),
|
||||
])
|
||||
->action(function (array $data): void {
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||||
if (! $stabileId) {
|
||||
Notification::make()->title('Seleziona uno stabile')->danger()->send();
|
||||
return;
|
||||
}
|
||||
|
||||
$causale = trim((string) ($this->selectedCausaleForConfig ?? ''));
|
||||
if ($causale === '') {
|
||||
Notification::make()->title('Causale non valida')->danger()->send();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
\App\Modules\Contabilita\Models\RegolaPrimaNota::updateOrCreate([
|
||||
'stabile_id' => $stabileId,
|
||||
'origine' => 'banca',
|
||||
'chiave' => $causale,
|
||||
], [
|
||||
'label' => isset($data['label']) && trim($data['label']) !== '' ? trim($data['label']) : 'Regola causale ' . $causale,
|
||||
'note' => $data['note'] ?? null,
|
||||
'conto_dare_id' => $data['conto_dare_id'] ? (int) $data['conto_dare_id'] : null,
|
||||
'conto_avere_id' => $data['conto_avere_id'] ? (int) $data['conto_avere_id'] : null,
|
||||
'attiva' => true,
|
||||
]);
|
||||
Notification::make()->title('Regola configurata con successo')->success()->send();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()->title('Errore')->body($e->getMessage())->danger()->send();
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -1698,6 +1766,317 @@ public function table(Table $table): Table
|
|||
Notification::make()->title('Errore')->body($e->getMessage())->danger()->send();
|
||||
}
|
||||
}),
|
||||
|
||||
Action::make('incassa_quote_condominiali')
|
||||
->label('Incassa quote')
|
||||
->icon('heroicon-o-currency-euro')
|
||||
->visible(function (MovimentoBanca $record): bool {
|
||||
if (! Schema::hasColumn('contabilita_movimenti_banca', 'registrazione_id')) {
|
||||
return false;
|
||||
}
|
||||
$importo = (float) ($record->importo ?? 0);
|
||||
if ($importo <= 0) {
|
||||
return false;
|
||||
}
|
||||
return empty($record->registrazione_id);
|
||||
})
|
||||
->form([
|
||||
Select::make('gestione_id')
|
||||
->label('Gestione')
|
||||
->options(function (): array {
|
||||
if (! DB::getSchemaBuilder()->hasTable('gestioni_contabili')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||||
if (! $stabileId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->getVisibleGestioneOptions($stabileId);
|
||||
})
|
||||
->searchable()
|
||||
->visible(fn(MovimentoBanca $record) => empty($record->gestione_id))
|
||||
->required(fn(MovimentoBanca $record) => empty($record->gestione_id)),
|
||||
|
||||
Select::make('soggetto_id')
|
||||
->label('Soggetto (Condomino/Inquilino)')
|
||||
->options(function (): array {
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
return [];
|
||||
}
|
||||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||||
if (! $stabileId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return \App\Models\RataEmessaNg::query()
|
||||
->join('unita_immobiliari', 'rate_emesse.unita_immobiliare_id', '=', 'unita_immobiliari.id')
|
||||
->join('soggetti', 'rate_emesse.soggetto_responsabile_id', '=', 'soggetti.id')
|
||||
->where('unita_immobiliari.stabile_id', $stabileId)
|
||||
->where(function($q) {
|
||||
$q->whereNull('rate_emesse.stato_rata')
|
||||
->orWhere('rate_emesse.stato_rata', '!=', 'pagata');
|
||||
})
|
||||
->groupBy('soggetti.id')
|
||||
->select(['soggetti.id', 'soggetti.ragione_sociale', 'soggetti.nome', 'soggetti.cognome'])
|
||||
->get()
|
||||
->mapWithKeys(function (object $s): array {
|
||||
$label = is_string($s->ragione_sociale ?? null) && trim((string) $s->ragione_sociale) !== ''
|
||||
? trim((string) $s->ragione_sociale)
|
||||
: trim(((string) ($s->cognome ?? '')) . ' ' . ((string) ($s->nome ?? '')));
|
||||
$label = $label !== '' ? $label : ('Soggetto #' . (string) $s->id);
|
||||
|
||||
return [(string) $s->id => $label];
|
||||
})
|
||||
->all();
|
||||
})
|
||||
->searchable()
|
||||
->reactive()
|
||||
->required()
|
||||
->afterStateUpdated(function (callable $set, $state, callable $get, MovimentoBanca $record) {
|
||||
if (empty($state)) {
|
||||
$set('rate', []);
|
||||
return;
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
return;
|
||||
}
|
||||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||||
if (! $stabileId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rate = \App\Models\RataEmessaNg::query()
|
||||
->join('unita_immobiliari', 'rate_emesse.unita_immobiliare_id', '=', 'unita_immobiliari.id')
|
||||
->where('unita_immobiliari.stabile_id', $stabileId)
|
||||
->where('rate_emesse.soggetto_responsabile_id', (int) $state)
|
||||
->where(function($q) {
|
||||
$q->whereNull('rate_emesse.stato_rata')
|
||||
->orWhere('rate_emesse.stato_rata', '!=', 'pagata');
|
||||
})
|
||||
->orderBy('rate_emesse.data_scadenza')
|
||||
->orderBy('rate_emesse.id')
|
||||
->select([
|
||||
'rate_emesse.id',
|
||||
'rate_emesse.descrizione',
|
||||
'rate_emesse.data_scadenza',
|
||||
'rate_emesse.importo_addebitato_soggetto',
|
||||
'rate_emesse.importo_pagato',
|
||||
'unita_immobiliari.codice_unita',
|
||||
])
|
||||
->get();
|
||||
|
||||
$importoBanca = round((float) ($record->importo ?? 0), 2);
|
||||
$residuoBanca = $importoBanca;
|
||||
|
||||
$rateRows = [];
|
||||
foreach ($rate as $r) {
|
||||
$dovuto = round((float) $r->importo_addebitato_soggetto, 2);
|
||||
$pagato = round((float) $r->importo_pagato, 2);
|
||||
$aperto = round($dovuto - $pagato, 2);
|
||||
if ($aperto <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$daPagare = 0.0;
|
||||
if ($residuoBanca > 0) {
|
||||
$daPagare = min($residuoBanca, $aperto);
|
||||
$residuoBanca = round($residuoBanca - $daPagare, 2);
|
||||
}
|
||||
|
||||
$rateRows[] = [
|
||||
'rata_id' => $r->id,
|
||||
'codice_unita' => $r->codice_unita,
|
||||
'descrizione' => $r->descrizione,
|
||||
'scadenza' => $r->data_scadenza ? $r->data_scadenza->format('d/m/Y') : '',
|
||||
'residuo_label' => 'Importo residuo: € ' . number_format($aperto, 2, ',', '.'),
|
||||
'importo_pagato' => $daPagare,
|
||||
];
|
||||
}
|
||||
|
||||
$set('rate', $rateRows);
|
||||
}),
|
||||
|
||||
\Filament\Forms\Components\Repeater::make('rate')
|
||||
->label('Rate da incassare')
|
||||
->schema([
|
||||
\Filament\Forms\Components\Hidden::make('rata_id'),
|
||||
\Filament\Forms\Components\Hidden::make('codice_unita'),
|
||||
\Filament\Forms\Components\Hidden::make('descrizione'),
|
||||
\Filament\Forms\Components\Hidden::make('scadenza'),
|
||||
\Filament\Forms\Components\Hidden::make('residuo_label'),
|
||||
\Filament\Forms\Components\Placeholder::make('codice_unita_lbl')
|
||||
->label('Unità')
|
||||
->content(fn ($get) => $get('codice_unita')),
|
||||
\Filament\Forms\Components\Placeholder::make('descrizione_lbl')
|
||||
->label('Descrizione')
|
||||
->content(fn ($get) => $get('descrizione')),
|
||||
\Filament\Forms\Components\Placeholder::make('scadenza_lbl')
|
||||
->label('Scadenza')
|
||||
->content(fn ($get) => $get('scadenza')),
|
||||
\Filament\Forms\Components\Placeholder::make('residuo_lbl')
|
||||
->label('Aperto')
|
||||
->content(fn ($get) => $get('residuo_label')),
|
||||
\Filament\Forms\Components\TextInput::make('importo_pagato')
|
||||
->label('Importo da pagare')
|
||||
->numeric()
|
||||
->required()
|
||||
->prefix('€'),
|
||||
])
|
||||
->addable(false)
|
||||
->deletable(false)
|
||||
->columns(5),
|
||||
])
|
||||
->action(function (MovimentoBanca $record, array $data): void {
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
Notification::make()->title('Utente non valido')->danger()->send();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($record, $data, $user) {
|
||||
$stabileId = StabileContext::resolveActiveStabileId($user);
|
||||
$gestioneId = (empty($record->gestione_id) && isset($data['gestione_id']) && is_numeric($data['gestione_id']))
|
||||
? (int) $data['gestione_id']
|
||||
: (int) ($record->gestione_id ?? 0);
|
||||
|
||||
if (empty($record->gestione_id)) {
|
||||
$record->gestione_id = $gestioneId;
|
||||
$record->save();
|
||||
}
|
||||
|
||||
$dataPagamento = $record->data ?: now()->toDateString();
|
||||
$importoMovimento = round((float) ($record->importo ?? 0), 2);
|
||||
|
||||
$rateSelections = $data['rate'] ?? [];
|
||||
$totalAllocated = 0.0;
|
||||
foreach ($rateSelections as $sel) {
|
||||
$totalAllocated += round((float) ($sel['importo_pagato'] ?? 0), 2);
|
||||
}
|
||||
$totalAllocated = round($totalAllocated, 2);
|
||||
|
||||
if ($totalAllocated > $importoMovimento) {
|
||||
throw new \RuntimeException('Il totale da pagare inserito supera l\'importo del movimento bancario.');
|
||||
}
|
||||
|
||||
// Crea la registrazione
|
||||
$reg = \App\Modules\Contabilita\Models\Registrazione::create([
|
||||
'stabile_id' => $stabileId,
|
||||
'gestione_id' => \App\Modules\Contabilita\Models\Registrazione::resolveGestioneIdOrDefault($gestioneId, $stabileId, $dataPagamento),
|
||||
'data_registrazione' => $dataPagamento,
|
||||
'descrizione' => 'Incasso rate da movimento banca: ' . $record->descrizione,
|
||||
'created_by' => $user->id,
|
||||
'updated_by' => $user->id,
|
||||
]);
|
||||
|
||||
// Dare: Banca c/c
|
||||
$contoDareId = null;
|
||||
$datiBancari = DatiBancari::query()->where('iban', $record->iban)->first();
|
||||
if ($datiBancari && is_string($datiBancari->codice)) {
|
||||
$byCode = \App\Modules\Contabilita\Models\PianoConti::query()->where('codice', trim((string) $datiBancari->codice))->first();
|
||||
if ($byCode) {
|
||||
$contoDareId = (int) $byCode->id;
|
||||
}
|
||||
}
|
||||
if (!$contoDareId) {
|
||||
$contoDareId = 7;
|
||||
}
|
||||
|
||||
\App\Modules\Contabilita\Models\Movimento::create([
|
||||
'registrazione_id' => $reg->id,
|
||||
'conto_id' => $contoDareId,
|
||||
'tipo' => 'dare',
|
||||
'importo' => $totalAllocated,
|
||||
]);
|
||||
|
||||
// Avere & incassi per ciascuna rata
|
||||
foreach ($rateSelections as $sel) {
|
||||
$rataId = (int) $sel['rata_id'];
|
||||
$importoPagato = round((float) ($sel['importo_pagato'] ?? 0), 2);
|
||||
if ($importoPagato <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rata = \App\Models\RataEmessaNg::query()->lockForUpdate()->find($rataId);
|
||||
if (!$rata) {
|
||||
throw new \RuntimeException('Rata non trovata.');
|
||||
}
|
||||
|
||||
$nuovoImportoPagato = round((float) $rata->importo_pagato + $importoPagato, 2);
|
||||
$importoDovuto = round((float) $rata->importo_addebitato_soggetto, 2);
|
||||
|
||||
$statoRata = 'parziale';
|
||||
if ($nuovoImportoPagato >= $importoDovuto) {
|
||||
$statoRata = 'pagata';
|
||||
}
|
||||
|
||||
$rata->update([
|
||||
'importo_pagato' => $nuovoImportoPagato,
|
||||
'stato_rata' => $statoRata,
|
||||
'data_ultimo_pagamento' => $dataPagamento,
|
||||
]);
|
||||
|
||||
$contoAvereId = null;
|
||||
$condominoId = $rata->soggetto_responsabile_id;
|
||||
if ($condominoId > 0) {
|
||||
$condomino = \App\Models\Condomino::query()->find($condominoId);
|
||||
$conto = app(\App\Modules\Contabilita\Services\ContiSoggettiService::class)->resolveContoCreditoCondomino(
|
||||
$stabileId,
|
||||
$condominoId,
|
||||
$condomino?->display_name,
|
||||
is_string($condomino?->codice_univoco ?? null) ? trim((string) $condomino->codice_univoco) : null
|
||||
);
|
||||
$contoAvereId = (int) $conto->id;
|
||||
}
|
||||
if (!$contoAvereId) {
|
||||
$contoAvereId = 8;
|
||||
}
|
||||
|
||||
\App\Modules\Contabilita\Models\Movimento::create([
|
||||
'registrazione_id' => $reg->id,
|
||||
'conto_id' => $contoAvereId,
|
||||
'tipo' => 'avere',
|
||||
'importo' => $importoPagato,
|
||||
]);
|
||||
|
||||
\App\Models\Incasso::create([
|
||||
'gestione_id' => $gestioneId,
|
||||
'condominio_id' => $stabileId,
|
||||
'conto_bancario_id' => $datiBancari?->id,
|
||||
'condomino_id' => $condominoId,
|
||||
'movimento_bancario_id' => $record->id,
|
||||
'importo_pagato_euro' => $importoPagato,
|
||||
'data_pagamento' => $dataPagamento,
|
||||
'descrizione' => 'Incasso rata: ' . $rata->descrizione,
|
||||
'stato_riconciliazione' => 'manuale',
|
||||
'registrazione_id' => $reg->id,
|
||||
'rate_associate' => [$rata->id],
|
||||
]);
|
||||
}
|
||||
|
||||
$reg->assertBilanciata();
|
||||
|
||||
$record->update([
|
||||
'registrazione_id' => $reg->id,
|
||||
]);
|
||||
});
|
||||
|
||||
Notification::make()->title('Incasso rate registrato con successo.')->success()->send();
|
||||
$this->resetTable();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()->title('Errore durante l\'incasso')->body($e->getMessage())->danger()->send();
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -2303,7 +2682,7 @@ protected function getActiveStabileId(): ?int
|
|||
protected function normalizeHubTabValue(?string $value): string
|
||||
{
|
||||
$value = is_string($value) ? trim(strtolower($value)) : '';
|
||||
return in_array($value, ['conti', 'saldi', 'movimenti', 'struttura', 'riconciliazione'], true) ? $value : 'conti';
|
||||
return in_array($value, ['conti', 'saldi', 'movimenti', 'struttura', 'riconciliazione', 'associazione_causali'], true) ? $value : 'conti';
|
||||
}
|
||||
|
||||
protected function syncSelectedContoFromId(?int $contoId): void
|
||||
|
|
@ -4240,4 +4619,73 @@ protected function storeSaldoDocumento(int $stabileId, DatiBancari $conto, mixed
|
|||
'caricato_da' => (int) $user->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getCausaliAssociazioneRows(): array
|
||||
{
|
||||
$stabileId = $this->getActiveStabileId();
|
||||
if (! $stabileId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$uniqueCausali = DB::table('contabilita_movimenti_banca')
|
||||
->where('stabile_id', $stabileId)
|
||||
->whereNotNull('causale')
|
||||
->where('causale', '!=', '')
|
||||
->select('causale')
|
||||
->selectRaw('count(*) as count')
|
||||
->selectRaw('max(descrizione) as sample_description')
|
||||
->groupBy('causale')
|
||||
->get();
|
||||
|
||||
$rows = [];
|
||||
foreach ($uniqueCausali as $uc) {
|
||||
$code = $uc->causale;
|
||||
|
||||
$official = DB::table('contabilita_causali_bancarie')
|
||||
->where('codice', $code)
|
||||
->first();
|
||||
$descr = $official?->descrizione ?? $uc->sample_description;
|
||||
|
||||
$regola = \App\Modules\Contabilita\Models\RegolaPrimaNota::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->where('origine', 'banca')
|
||||
->where('chiave', $code)
|
||||
->first();
|
||||
|
||||
$rows[] = [
|
||||
'causale' => $code,
|
||||
'descrizione' => $descr,
|
||||
'count' => $uc->count,
|
||||
'regola_id' => $regola?->id,
|
||||
'conto_dare' => $regola?->contoDare ? ($regola->contoDare->codice . ' - ' . $regola->contoDare->descrizione) : null,
|
||||
'conto_avere' => $regola?->contoAvere ? ($regola->contoAvere->codice . ' - ' . $regola->contoAvere->descrizione) : null,
|
||||
'attiva' => $regola?->attiva ?? false,
|
||||
];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function startConfiguringRegolaCausale(string $causale): void
|
||||
{
|
||||
$this->selectedCausaleForConfig = $causale;
|
||||
|
||||
$stabileId = $this->getActiveStabileId();
|
||||
$regola = null;
|
||||
if ($stabileId) {
|
||||
$regola = \App\Modules\Contabilita\Models\RegolaPrimaNota::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->where('origine', 'banca')
|
||||
->where('chiave', $causale)
|
||||
->first();
|
||||
}
|
||||
|
||||
$this->mountAction('configura_regola_causale', [
|
||||
'causale' => $causale,
|
||||
'conto_dare_id' => $regola?->conto_dare_id,
|
||||
'conto_avere_id' => $regola?->conto_avere_id,
|
||||
'label' => $regola?->label,
|
||||
'note' => $regola?->note,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3069,9 +3069,7 @@ public function getTabelleMillesimaliByTipo(string $tipoTabella): array
|
|||
return TabellaMillesimale::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->where('tipo_tabella', $tipoTabella)
|
||||
->orderBy('ordinamento')
|
||||
->orderBy('codice_tabella')
|
||||
->orderBy('nome_tabella')
|
||||
->ordinato()
|
||||
->get(['id', 'denominazione', 'nome_tabella', 'codice_tabella'])
|
||||
->map(function (TabellaMillesimale $t): array {
|
||||
$label = trim((string) ($t->denominazione ?? ''));
|
||||
|
|
|
|||
|
|
@ -147,11 +147,13 @@ public function form(Schema $schema): Schema
|
|||
Select::make('tipo')
|
||||
->label('Tipo')
|
||||
->options(['dare' => 'Dare', 'avere' => 'Avere'])
|
||||
->required(),
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('importo')
|
||||
->label('Importo')
|
||||
->numeric()
|
||||
->required(),
|
||||
->required()
|
||||
->live(onBlur: true),
|
||||
])
|
||||
->columns(3)
|
||||
->required(),
|
||||
|
|
@ -159,6 +161,46 @@ public function form(Schema $schema): Schema
|
|||
]);
|
||||
}
|
||||
|
||||
public function getHeaderSummary(): array
|
||||
{
|
||||
$state = is_array($this->data ?? null) ? $this->data : [];
|
||||
$movimenti = $state['movimenti'] ?? [];
|
||||
|
||||
if (empty($movimenti) && isset($this->registrazione) && $this->registrazione->id) {
|
||||
$movimenti = Movimento::query()
|
||||
->where('registrazione_id', (int) $this->registrazione->id)
|
||||
->orderBy('id')
|
||||
->get()
|
||||
->map(fn (Movimento $m) => [
|
||||
'conto_id' => $m->conto_id,
|
||||
'tipo' => $m->tipo,
|
||||
'importo' => $m->importo,
|
||||
])
|
||||
->all();
|
||||
}
|
||||
|
||||
$totDare = 0.0;
|
||||
$totAvere = 0.0;
|
||||
foreach ($movimenti as $m) {
|
||||
$tipo = (string) ($m['tipo'] ?? '');
|
||||
$importo = (float) str_replace(',', '.', str_replace('.', '', (string) ($m['importo'] ?? '0')));
|
||||
if ($tipo === 'dare') {
|
||||
$totDare += $importo;
|
||||
} elseif ($tipo === 'avere') {
|
||||
$totAvere += $importo;
|
||||
}
|
||||
}
|
||||
|
||||
$delta = round($totDare - $totAvere, 2);
|
||||
|
||||
return [
|
||||
'totale_dare' => $totDare,
|
||||
'totale_avere' => $totAvere,
|
||||
'delta' => $delta,
|
||||
'is_bilanciata' => abs($delta) < 0.005,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -448,10 +448,7 @@ public function getTabelleMillesimaliOptions(): array
|
|||
|
||||
$q = TabellaMillesimale::query()
|
||||
->where('stabile_id', $stabileId)
|
||||
->orderBy('ordine_visualizzazione')
|
||||
->orderBy('ordinamento')
|
||||
->orderBy('codice_tabella')
|
||||
->orderBy('nome_tabella');
|
||||
->ordinato();
|
||||
|
||||
if ($hasAnno) {
|
||||
$q->where('anno_gestione', $anno);
|
||||
|
|
|
|||
|
|
@ -750,6 +750,7 @@ public function applicaEnrichmentStabile(): void
|
|||
|
||||
public function aggiornaAnagraficheDaCondomin(): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -789,6 +790,7 @@ public function aggiornaAnagraficheDaCondomin(): void
|
|||
|
||||
public function risincronizzaRelazioniImportate(): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -826,6 +828,7 @@ public function risincronizzaRelazioniImportate(): void
|
|||
|
||||
public function aggiornaFornitoriGescon(): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -894,6 +897,7 @@ public function aggiornaFornitoriGescon(): void
|
|||
|
||||
public function refreshImportTagLegacyFornitoriGlobale(): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -968,6 +972,7 @@ public function refreshImportTagLegacyFornitoriGlobale(): void
|
|||
|
||||
public function applicaEnrichmentTuttiStabili(): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -1019,6 +1024,7 @@ public function applicaEnrichmentTuttiStabili(): void
|
|||
|
||||
public function eseguiImportSelezionato(EssentialImportService $service): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -1179,8 +1185,11 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
|
||||
if (! empty($options['sync_staging'])) {
|
||||
$singoloMdb = null;
|
||||
if (! empty($options['path']) && ! empty($code)) {
|
||||
$annoFolder = $options['anno'] ?: '0001';
|
||||
if (! empty($options['path']) && ! empty($code)) {
|
||||
if (is_numeric($annoFolder) && strlen($annoFolder) === 4 && (int)$annoFolder > 1900) {
|
||||
$annoFolder = $this->resolveYearToFolder($code, $options['path'], $annoFolder);
|
||||
}
|
||||
$singoloMdb = rtrim($options['path'], '/') . '/' . $code . '/' . $annoFolder . '/singolo_anno.mdb';
|
||||
}
|
||||
|
||||
|
|
@ -1189,7 +1198,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
: null;
|
||||
|
||||
try {
|
||||
$legacyYearArg = (string) ($options['anno'] ?? '');
|
||||
$legacyYearArg = $annoFolder;
|
||||
|
||||
if ((! empty($options['with_banche']) || ! empty($options['with_operazioni']) || ! empty($options['with_incassi'])) && ! empty($options['path']) && ! empty($code)) {
|
||||
foreach ($this->resolveLegacySingoloMdbPaths($code, (string) $options['path'], $legacyYearArg !== '' ? $legacyYearArg : null) as $mdbPath => $legacyDir) {
|
||||
|
|
@ -1209,7 +1218,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $singoloMdb,
|
||||
'--table' => 's_cassa',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1218,7 +1227,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $singoloMdb,
|
||||
'--table' => 'operazioni',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1227,7 +1236,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $singoloMdb,
|
||||
'--table' => 'voc_spe',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1236,7 +1245,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $singoloMdb,
|
||||
'--table' => 'tabelle',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1248,7 +1257,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $singoloMdb,
|
||||
'--table' => 'incassi',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1259,7 +1268,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $generaleMdb,
|
||||
'--table' => 'emes_gen',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1268,7 +1277,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--mdb' => $generaleMdb,
|
||||
'--table' => 'emes_det',
|
||||
'--stabile' => $code,
|
||||
'--legacy-year' => (string) ($options['anno'] ?? ''),
|
||||
'--legacy-year' => $annoFolder,
|
||||
'--refresh-slice' => true,
|
||||
]);
|
||||
$outputs[] = trim((string) Artisan::output());
|
||||
|
|
@ -1289,7 +1298,11 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
'--path' => $options['path'],
|
||||
];
|
||||
if (! empty($options['anno'])) {
|
||||
$params['--anno'] = $options['anno'];
|
||||
$annoParam = $options['anno'];
|
||||
if (is_numeric($annoParam) && strlen($annoParam) === 4 && (int)$annoParam > 1900) {
|
||||
$annoParam = $this->resolveYearToFolder($code, $options['path'], $annoParam);
|
||||
}
|
||||
$params['--anno'] = $annoParam;
|
||||
}
|
||||
if (! empty($options['dry'])) {
|
||||
$params['--dry-run'] = true;
|
||||
|
|
@ -1350,6 +1363,7 @@ public function eseguiImportSelezionato(EssentialImportService $service): void
|
|||
|
||||
public function eseguiImportAllineamentoCompleto(): void
|
||||
{
|
||||
@set_time_limit(0);
|
||||
$user = Auth::user();
|
||||
if (! $user instanceof User) {
|
||||
abort(403);
|
||||
|
|
@ -1793,8 +1807,15 @@ private function applyGestioneSetup(Stabile $stabile): int
|
|||
|
||||
private function upsertGestioneRecord(Stabile $stabile, int $year, string $tipo, array $months, bool $active): void
|
||||
{
|
||||
$tenantId = GestioneContabile::query()
|
||||
->where('stabile_id', $stabile->id)
|
||||
->value('tenant_id');
|
||||
if (! is_string($tenantId) || trim($tenantId) === '') {
|
||||
$tenantId = (string) (Auth::user()?->tenant_id ?? 'default');
|
||||
}
|
||||
|
||||
$gestione = GestioneContabile::query()->firstOrNew([
|
||||
'tenant_id' => 'default',
|
||||
'tenant_id' => $tenantId,
|
||||
'stabile_id' => $stabile->id,
|
||||
'anno_gestione' => $year,
|
||||
'tipo_gestione' => $tipo,
|
||||
|
|
@ -2383,4 +2404,42 @@ private function mdbExportToRows(string $mdbPath, array $tableCandidates): array
|
|||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function resolveYearToFolder(string $code, string $path, string $year): string
|
||||
{
|
||||
$code = str_pad(trim($code), 4, '0', STR_PAD_LEFT);
|
||||
$mdb = rtrim($path, '/') . '/' . $code . '/generale_stabile.mdb';
|
||||
if (! is_file($mdb) || ! is_readable($mdb)) {
|
||||
return $year;
|
||||
}
|
||||
|
||||
$rows = $this->mdbExportToRows($mdb, ['anni', 'Anni', 'ANNI']);
|
||||
if (empty($rows)) {
|
||||
return $year;
|
||||
}
|
||||
|
||||
foreach ($rows as $r) {
|
||||
$yearRaw = $this->firstNonEmpty($r, ['anno_o', 'anno_r', 'anno']);
|
||||
$y = null;
|
||||
if (is_scalar($yearRaw) && preg_match('/(19|20)\d{2}/', (string) $yearRaw, $m)) {
|
||||
$y = $m[0];
|
||||
}
|
||||
if ($y === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($y === $year) {
|
||||
$dirRaw = $this->firstNonEmpty($r, ['nome_dir', 'dir', 'cartella', 'id_anno']);
|
||||
$dir = trim((string) ($dirRaw ?? ''));
|
||||
if ($dir === '' && is_numeric((string) ($r['id_anno'] ?? ''))) {
|
||||
$dir = str_pad((string) ((int) $r['id_anno']), 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
if ($dir !== '') {
|
||||
return $dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $year;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class Ordinarie extends Page
|
|||
public string $viewTab = 'operazioni';
|
||||
public string $sortField = 'id_operaz';
|
||||
public string $sortDirection = 'desc';
|
||||
public bool $netgesconWorkflowMode = false;
|
||||
public bool $netgesconWorkflowMode = true;
|
||||
public bool $onlyStraordinarieMode = false;
|
||||
public bool $onlyRiscaldamentoMode = false;
|
||||
public ?int $selectedStraordinaria = null;
|
||||
|
|
@ -3306,6 +3306,7 @@ public function openDettPersModal(int $nSpe): void
|
|||
if ($activeStabileId && Schema::hasTable('tabelle_millesimali') && Schema::hasTable('dettaglio_millesimi')) {
|
||||
$localRows = DB::table('tabelle_millesimali')
|
||||
->where('stabile_id', (int) $activeStabileId)
|
||||
->orderByRaw('COALESCE(nord, ordinamento, ordine_visualizzazione, 999999)')
|
||||
->orderBy('codice_tabella')
|
||||
->get(['id', 'codice_tabella', 'nome_tabella', 'denominazione']);
|
||||
|
||||
|
|
@ -3716,8 +3717,7 @@ private function buildOperazioniQuery()
|
|||
$query->where(function ($q) use ($s) {
|
||||
$q->where('o.descrizione', 'like', $s)
|
||||
->orWhere('o.conto_contabile', 'like', $s)
|
||||
->orWhere('o.protocollo_completo', 'like', $s)
|
||||
->orWhere('o.num_fat', 'like', $s);
|
||||
->orWhere('o.protocollo_completo', 'like', $s);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -3739,8 +3739,8 @@ private function buildOperazioniQuery()
|
|||
'o.n_stra',
|
||||
'o.protocollo_completo',
|
||||
DB::raw("coalesce(o.dare, o.avere, 0) as importo_euro"),
|
||||
'o.num_fat',
|
||||
'o.fe_uid',
|
||||
DB::raw("null as num_fat"),
|
||||
DB::raw("null as fe_uid"),
|
||||
'o.voce_spesa_snapshot',
|
||||
'o.fornitore_snapshot',
|
||||
]);
|
||||
|
|
@ -3767,8 +3767,8 @@ private function buildOperazioniQuery()
|
|||
'o.protocollo_completo',
|
||||
DB::raw("coalesce(o.dare, o.avere, 0) as importo_euro"),
|
||||
DB::raw("coalesce(o.dare, o.avere, 0) as importo"),
|
||||
'o.num_fat',
|
||||
'o.fe_uid',
|
||||
DB::raw("null as num_fat"),
|
||||
DB::raw("null as fe_uid"),
|
||||
'o.voce_spesa_snapshot',
|
||||
'o.fornitore_snapshot',
|
||||
DB::raw("0 as importo_spese"),
|
||||
|
|
@ -3994,8 +3994,8 @@ private function applyOperazioniSorting($query)
|
|||
'dt_spe' => 'o.data_operazione',
|
||||
'cod_spe' => 'o.conto_contabile',
|
||||
'importo_euro' => 'o.dare',
|
||||
'num_fat' => 'o.num_fat',
|
||||
'fe_uid' => 'o.fe_uid',
|
||||
'num_fat' => 'o.id',
|
||||
'fe_uid' => 'o.id',
|
||||
default => 'o.id'
|
||||
};
|
||||
$query->orderBy($mappedField, $dir);
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ private function getTabelleOptions(): array
|
|||
|
||||
return TabellaMillesimale::query()
|
||||
->where('stabile_id', $this->stabileAttivo->id)
|
||||
->orderBy('codice_tabella')
|
||||
->ordinato()
|
||||
->get(['id', 'codice_tabella', 'nome_tabella', 'denominazione'])
|
||||
->mapWithKeys(function (TabellaMillesimale $t) {
|
||||
$label = $t->codice_tabella ?: ($t->denominazione ?: ($t->nome_tabella ?: null));
|
||||
|
|
|
|||
|
|
@ -30,11 +30,11 @@ class CausaliBancarie extends Page implements HasTable
|
|||
|
||||
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-tag';
|
||||
|
||||
protected static UnitEnum|string|null $navigationGroup = 'Super Admin';
|
||||
protected static UnitEnum|string|null $navigationGroup = 'Contabilità';
|
||||
|
||||
protected static ?int $navigationSort = 22;
|
||||
protected static ?int $navigationSort = 20;
|
||||
|
||||
protected static ?string $slug = 'superadmin/causali-bancarie';
|
||||
protected static ?string $slug = 'contabilita/causali-bancarie';
|
||||
|
||||
protected string $view = 'filament.pages.gescon.table';
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ public static function canAccess(): bool
|
|||
$user = Auth::user();
|
||||
|
||||
return $user instanceof User
|
||||
&& $user->hasAnyRole(['super-admin', 'admin'])
|
||||
&& ($user->hasAnyRole(['super-admin', 'admin']) || $user->can('contabilita.causali'))
|
||||
&& Schema::hasTable('contabilita_causali_bancarie');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Tables\Actions\Action as TableAction;
|
||||
use Filament\Actions\Action as TableAction;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
|
|
|||
|
|
@ -13,31 +13,29 @@ class IncassoPagamento extends Model
|
|||
protected $table = 'incassi_pagamenti';
|
||||
|
||||
protected $fillable = [
|
||||
'amministratore_id',
|
||||
'anno_gestione_id',
|
||||
'stabile_id',
|
||||
'anno_gestione_id',
|
||||
'condomino_id',
|
||||
'rata_id',
|
||||
'data_incasso',
|
||||
'data_valuta',
|
||||
'importo',
|
||||
'modalita_pagamento',
|
||||
'numero_assegno',
|
||||
'banca_assegno',
|
||||
'numero_bonifico',
|
||||
'causale',
|
||||
'note_bancarie',
|
||||
'cod_condomino',
|
||||
'n_riferimento',
|
||||
'data_pagamento',
|
||||
'importo_pagato',
|
||||
'importo_pagato_euro',
|
||||
'n_ricevuta',
|
||||
'tipo_gestione',
|
||||
'mese_riferimento',
|
||||
'descrizione',
|
||||
'cod_cassa',
|
||||
'riconciliato',
|
||||
'data_riconciliazione',
|
||||
'note'
|
||||
'rata_id',
|
||||
'movimento_banca_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'data_incasso' => 'date',
|
||||
'data_valuta' => 'date',
|
||||
'data_riconciliazione' => 'date',
|
||||
'importo' => 'decimal:2',
|
||||
'riconciliato' => 'boolean'
|
||||
'data_pagamento' => 'date',
|
||||
'importo_pagato' => 'decimal:2',
|
||||
'importo_pagato_euro' => 'decimal:2',
|
||||
'riconciliato' => 'boolean',
|
||||
];
|
||||
|
||||
const MODALITA_CONTANTI = 'contanti';
|
||||
|
|
|
|||
|
|
@ -588,6 +588,14 @@ public function syncRubricaContatto(): RubricaUniversale
|
|||
'stato' => 'attivo',
|
||||
];
|
||||
|
||||
if (! $rubrica && ! empty($data['codice_univoco'])) {
|
||||
$existing = RubricaUniversale::where('codice_univoco', $data['codice_univoco'])->first();
|
||||
if ($existing) {
|
||||
$rubrica = $existing;
|
||||
$this->update(['rubrica_id' => $rubrica->id]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($rubrica) {
|
||||
$rubrica->update($data);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -153,15 +153,8 @@ public function scopePerStabile($query, $stabileId)
|
|||
*/
|
||||
public function scopeOrdinato($query)
|
||||
{
|
||||
if (Schema::hasColumn('tabelle_millesimali', 'ordine_visualizzazione')) {
|
||||
$query->orderBy('ordine_visualizzazione');
|
||||
} elseif (Schema::hasColumn('tabelle_millesimali', 'nord')) {
|
||||
$query->orderBy('nord');
|
||||
} else {
|
||||
$query->orderBy('ordinamento');
|
||||
}
|
||||
|
||||
return $query
|
||||
->orderByRaw('COALESCE(ordine_visualizzazione, nord, ordinamento, 999999)')
|
||||
->orderBy('codice_tabella')
|
||||
->orderBy('nome_tabella');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,96 @@ public function getDescrizioneEstesaPulitaAttribute(): ?string
|
|||
return $text !== '' ? $text : null;
|
||||
}
|
||||
|
||||
public function getDettaglioEstrattoAttribute(): array
|
||||
{
|
||||
$rawText = $this->descrizione_estesa ?? $this->descrizione ?? '';
|
||||
$text = trim($rawText);
|
||||
|
||||
$commissioni = '';
|
||||
$pos = -1;
|
||||
foreach (['COMM', 'SPESE', 'TRN', 'COMMISSIONI', 'COMMISSIONE'] as $key) {
|
||||
$p = stripos($text, $key);
|
||||
if ($p !== false && ($pos === -1 || $p < $pos)) {
|
||||
$pos = $p;
|
||||
}
|
||||
}
|
||||
if ($pos !== -1) {
|
||||
$commissioni = trim(substr($text, $pos));
|
||||
$text = trim(substr($text, 0, $pos));
|
||||
}
|
||||
|
||||
$bankPrefixes = [
|
||||
'BONIFICO A VOSTRO FAVORE BONIFICO SEPA DA',
|
||||
'BONIFICO A VOSTRO FAVORE BONIFICO SEPA',
|
||||
'BONIFICO SEPA A VOSTRO FAVORE DA',
|
||||
'BONIFICO SEPA A VOSTRO FAVORE',
|
||||
'BONIFICO A VOSTRO FAVORE DA',
|
||||
'BONIFICO A VOSTRO FAVORE',
|
||||
'BONIFICO SEPA DA',
|
||||
'BONIFICO SEPA',
|
||||
'BONIFICO DA',
|
||||
'BONIFICO ESEGUITO DA',
|
||||
'ADDEBITO CORRISPETTIVI',
|
||||
'GIROCONTO DA',
|
||||
'GIROCONTO',
|
||||
'DISPOSIZIONE DI PAGAMENTO DA',
|
||||
'DISPOSIZIONE DI PAGAMENTO',
|
||||
'PAGAMENTO CBILL',
|
||||
'PAGAMENTO PAGOPA',
|
||||
'VERSAMENTO DI CASSA',
|
||||
'VERSAMENTO DA',
|
||||
'VERSAMENTO',
|
||||
'PRELEVAMENTO DA',
|
||||
'PRELEVAMENTO',
|
||||
'PAGAMENTO CON CARTA',
|
||||
'PAGAMENTO POS',
|
||||
'ADDEBITO DISPOSIZIONE SEPA',
|
||||
'ADDEBITO SEPA',
|
||||
];
|
||||
|
||||
$messaggioBanca = '';
|
||||
$messaggioCliente = $text;
|
||||
|
||||
foreach ($bankPrefixes as $prefix) {
|
||||
if (stripos($text, $prefix) === 0) {
|
||||
$messaggioBanca = $prefix;
|
||||
$messaggioCliente = trim(substr($text, strlen($prefix)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($messaggioBanca) && !empty($this->causale)) {
|
||||
$messaggioBanca = $this->causale;
|
||||
}
|
||||
|
||||
$cbillCode = null;
|
||||
if (preg_match('/(?:CBILL|PAGOPA|IUV|AVVISO|CODICE\s*AVVISO)\s*:?\s*(\d{10,20})/i', $rawText, $cbillMatches)) {
|
||||
$cbillCode = $cbillMatches[1];
|
||||
}
|
||||
|
||||
$abi = null;
|
||||
if (!empty($this->raw_line)) {
|
||||
$parts = explode(';', $this->raw_line);
|
||||
if (count($parts) >= 2) {
|
||||
$last = trim(end($parts));
|
||||
if (preg_match('/^[a-zA-Z0-9]{3,5}$/', $last)) {
|
||||
$abi = $last;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$abi && is_array($this->match_data) && !empty($this->match_data['cod_abi'])) {
|
||||
$abi = $this->match_data['cod_abi'];
|
||||
}
|
||||
|
||||
return [
|
||||
'abi' => $abi,
|
||||
'messaggio_banca' => $messaggioBanca ?: '—',
|
||||
'messaggio_cliente' => $messaggioCliente ?: '—',
|
||||
'commissioni_spese' => $commissioni ?: '—',
|
||||
'cbill_code' => $cbillCode,
|
||||
];
|
||||
}
|
||||
|
||||
protected function applyCleaningRules(string $text): string
|
||||
{
|
||||
$patterns = [];
|
||||
|
|
|
|||
|
|
@ -185,6 +185,8 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
}
|
||||
}
|
||||
|
||||
$isCreditNote = isset($data['tipo_documento']) && strtoupper(trim($data['tipo_documento'])) === 'TD04';
|
||||
|
||||
$stabile = Stabile::query()->select(['id', 'amministratore_id'])->find($stabileId);
|
||||
$amministratoreId = (int) ($stabile?->amministratore_id ?: 0);
|
||||
|
||||
|
|
@ -230,7 +232,17 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
|
||||
try {
|
||||
/** @var FatturaElettronica $created */
|
||||
$created = DB::transaction(function () use ($stabileId, $fornitoreId, $data, $xml, $hash, $originalFilename, $fornitorePiva, $extra, $importRighe, &$contabilitaFatturaId) {
|
||||
$created = DB::transaction(function () use ($stabileId, $fornitoreId, $data, $xml, $hash, $originalFilename, $fornitorePiva, $extra, $importRighe, &$contabilitaFatturaId, $isCreditNote) {
|
||||
$imponibile = (float) ($data['imponibile'] ?? 0);
|
||||
$iva = (float) ($data['iva'] ?? 0);
|
||||
$totale = (float) ($data['totale'] ?? 0);
|
||||
|
||||
if ($isCreditNote) {
|
||||
$imponibile = -abs($imponibile);
|
||||
$iva = -abs($iva);
|
||||
$totale = -abs($totale);
|
||||
}
|
||||
|
||||
$created = FatturaElettronica::query()->create([
|
||||
'stabile_id' => $stabileId,
|
||||
'fornitore_id' => $fornitoreId,
|
||||
|
|
@ -241,9 +253,9 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
'fornitore_piva' => $fornitorePiva,
|
||||
'fornitore_cf' => $data['fornitore_cf'] ?? null,
|
||||
'fornitore_denominazione' => $data['fornitore_denominazione'] ?? 'ND',
|
||||
'imponibile' => (float) ($data['imponibile'] ?? 0),
|
||||
'iva' => (float) ($data['iva'] ?? 0),
|
||||
'totale' => (float) ($data['totale'] ?? 0),
|
||||
'imponibile' => $imponibile,
|
||||
'iva' => $iva,
|
||||
'totale' => $totale,
|
||||
|
||||
'pagamento_modalita' => $data['pagamento_modalita'] ?? null,
|
||||
'pagamento_iban' => $data['pagamento_iban'] ?? null,
|
||||
|
|
@ -274,7 +286,10 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
$ritenuta = is_array($data['ritenuta'] ?? null) ? $data['ritenuta'] : [];
|
||||
$ritenutaImporto = (float) ($ritenuta['importo'] ?? 0);
|
||||
$ritenutaAliquota = $ritenuta['aliquota'] ?? null;
|
||||
$netto = round(((float) ($data['totale'] ?? 0)) - $ritenutaImporto, 2);
|
||||
if ($isCreditNote) {
|
||||
$ritenutaImporto = -abs($ritenutaImporto);
|
||||
}
|
||||
$netto = round($totale - $ritenutaImporto, 2);
|
||||
|
||||
$dataDoc = $data['data_fattura'] ?? null;
|
||||
$dataRegistrazione = null;
|
||||
|
|
@ -298,9 +313,9 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
'descrizione' => $data['fornitore_denominazione'] ?? null,
|
||||
'valuta' => 'EUR',
|
||||
'cambio' => 1,
|
||||
'imponibile' => round((float) ($data['imponibile'] ?? 0), 2),
|
||||
'iva' => round((float) ($data['iva'] ?? 0), 2),
|
||||
'totale' => round((float) ($data['totale'] ?? 0), 2),
|
||||
'imponibile' => round($imponibile, 2),
|
||||
'iva' => round($iva, 2),
|
||||
'totale' => round($totale, 2),
|
||||
'data_scadenza' => $data['data_scadenza'] ?? null,
|
||||
'modalita_pagamento' => $data['pagamento_modalita'] ?? null,
|
||||
'ritenuta_aliquota' => $ritenutaAliquota,
|
||||
|
|
@ -333,7 +348,7 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
'descrizione' => $data['numero_fattura'] ?? 'Fattura elettronica',
|
||||
'quantita' => null,
|
||||
'prezzo_unitario' => null,
|
||||
'prezzo_totale' => (float) ($data['imponibile'] ?? 0),
|
||||
'prezzo_totale' => $imponibile,
|
||||
'aliquota_iva' => null,
|
||||
'ritenuta' => null,
|
||||
]];
|
||||
|
|
@ -344,6 +359,9 @@ public function importXml(string $xml, int $fallbackStabileId, ?string $original
|
|||
if ($imponibileRiga <= 0 && isset($r['prezzo_unitario'], $r['quantita'])) {
|
||||
$imponibileRiga = (float) $r['prezzo_unitario'] * (float) $r['quantita'];
|
||||
}
|
||||
if ($isCreditNote) {
|
||||
$imponibileRiga = -abs($imponibileRiga);
|
||||
}
|
||||
|
||||
$aliquota = $r['aliquota_iva'] ?? null;
|
||||
$ivaRiga = 0.0;
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
namespace App\Services\Import;
|
||||
|
||||
use App\Models\GestioneContabile;
|
||||
use App\Models\Incasso;
|
||||
use App\Models\IncassoEstrattoConto;
|
||||
use App\Models\OperazioneContabile;
|
||||
use App\Models\RegistroRitenuteAcconto;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -21,12 +21,52 @@
|
|||
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)
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -87,6 +127,31 @@ private function scanGesconYears(string $baseDir): array
|
|||
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
|
||||
*/
|
||||
|
|
@ -104,13 +169,25 @@ private function scanGestioniFromMdb(string $mdbPath, int $year): array
|
|||
'protocollo_prefix' => "O{$year}",
|
||||
];
|
||||
|
||||
// Gestione Riscaldamento (se presente)
|
||||
$opRiscaldamento = $this->queryMdb($mdbPath, "
|
||||
SELECT COUNT(*) as cnt FROM operazioni
|
||||
WHERE gestione = 'R' OR gestione = 'r'
|
||||
");
|
||||
// Legge le colonne necessarie da Operazioni
|
||||
$operazioni = $this->queryMdb($mdbPath, "SELECT Gestione, n_stra FROM Operazioni");
|
||||
|
||||
if (! empty($opRiscaldamento) && ($opRiscaldamento[0]['cnt'] ?? 0) > 0) {
|
||||
$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',
|
||||
|
|
@ -121,18 +198,8 @@ private function scanGestioniFromMdb(string $mdbPath, int $year): array
|
|||
];
|
||||
}
|
||||
|
||||
// Gestioni Straordinarie (dinamiche)
|
||||
$straordinarie = $this->queryMdb($mdbPath, "
|
||||
SELECT DISTINCT N_STRA as numero
|
||||
FROM operazioni
|
||||
WHERE (gestione = 'S' OR gestione = 's')
|
||||
AND N_STRA IS NOT NULL
|
||||
AND N_STRA > 0
|
||||
ORDER BY N_STRA
|
||||
");
|
||||
|
||||
foreach ($straordinarie as $stra) {
|
||||
$numero = (int) $stra['numero'];
|
||||
ksort($straordinarie);
|
||||
foreach (array_keys($straordinarie) as $numero) {
|
||||
$gestioni[] = [
|
||||
'anno_gestione' => $year,
|
||||
'tipo_gestione' => 'straordinaria',
|
||||
|
|
@ -150,10 +217,11 @@ private function scanGestioniFromMdb(string $mdbPath, int $year): array
|
|||
/**
|
||||
* Crea o aggiorna gestione
|
||||
*/
|
||||
private function createOrUpdateGestione(array $gestioneData): GestioneContabile
|
||||
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,
|
||||
|
|
@ -166,7 +234,7 @@ private function createOrUpdateGestione(array $gestioneData): GestioneContabile
|
|||
]);
|
||||
|
||||
// Cache per lookup veloce
|
||||
$year = $gestioneData['anno_gestione'];
|
||||
$year = $yearKey ?? $gestioneData['anno_gestione'];
|
||||
$tipo = $gestioneData['tipo_gestione'];
|
||||
|
||||
if ($tipo === 'straordinaria') {
|
||||
|
|
@ -189,37 +257,67 @@ private function importOperazioniForGestione(GestioneContabile $gestione, string
|
|||
$whereClause = $this->buildGestioneWhereClause($gestione);
|
||||
|
||||
$operazioni = $this->queryMdb($mdbPath, "
|
||||
SELECT *,
|
||||
IIF(ISNULL(COMPET), 'C', COMPET) as compet_clean,
|
||||
IIF(ISNULL(NATURA2), '', NATURA2) as natura2_clean,
|
||||
IIF(ISNULL(N_STRA), 0, N_STRA) as n_stra_clean
|
||||
FROM operazioni
|
||||
SELECT *
|
||||
FROM Operazioni
|
||||
WHERE {$whereClause}
|
||||
ORDER BY n_operazione
|
||||
");
|
||||
|
||||
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);
|
||||
$voceSnapshot = $this->getVoceSpesaSnapshot($mdbPath, $op['cod_voce'] ?? '');
|
||||
|
||||
OperazioneContabile::create([
|
||||
$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,
|
||||
'numero_operazione' => $op['n_operazione'],
|
||||
'data_operazione' => $this->parseGesconDate($op['data_oper']),
|
||||
'importo' => (float) ($op['euro'] ?? 0),
|
||||
'descrizione' => $op['descrizione'] ?? '',
|
||||
'cod_voce_gescon' => $op['cod_voce'] ?? '',
|
||||
'cod_beneficiario_gescon' => $op['cod_ben'] ?? '',
|
||||
'compet' => $op['compet_clean'],
|
||||
'natura2' => $op['natura2_clean'],
|
||||
'n_stra' => (int) $op['n_stra_clean'],
|
||||
'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),
|
||||
'metadati_gescon' => json_encode($op),
|
||||
'dare' => $dareVal,
|
||||
'avere' => $avereVal,
|
||||
'conto_contabile' => $codVoce,
|
||||
'conto_bancario_id' => $contoBancarioId,
|
||||
'stato_operazione' => 'confermata',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$imported++;
|
||||
|
|
@ -396,8 +494,12 @@ private function buildGestioneJoinClause(GestioneContabile $gestione, string $al
|
|||
/**
|
||||
* Import dati per una specifica gestione/anno
|
||||
*/
|
||||
private function importGestioneYear(string $year, array $paths): array
|
||||
public function importGestioneYear(string $year, array $paths): array
|
||||
{
|
||||
if (! isset($this->gestioni[$year])) {
|
||||
$this->initGestioniForYear($year, $paths);
|
||||
}
|
||||
|
||||
$results = [
|
||||
'operazioni' => 0,
|
||||
'incassi' => 0,
|
||||
|
|
@ -421,7 +523,9 @@ private function importGestioneYear(string $year, array $paths): array
|
|||
}
|
||||
|
||||
// 5. Import incassi estratto conto
|
||||
$results['incassi_ec'] = $this->importIncassiEstrattoConto($year, $paths['singolo_anno']);
|
||||
if (file_exists($paths['generale_stabile'])) {
|
||||
$results['incassi_ec'] = $this->importIncassiEstrattoConto($year, $paths['generale_stabile']);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
|
@ -435,14 +539,16 @@ private function importOperazioni(string $year, string $mdbPath): int
|
|||
|
||||
// Query MDB per operazioni
|
||||
$operazioni = $this->queryMdb($mdbPath, "
|
||||
SELECT *,
|
||||
IIF(ISNULL(COMPET), 'C', COMPET) as compet_clean,
|
||||
IIF(ISNULL(NATURA2), '', NATURA2) as natura2_clean,
|
||||
IIF(ISNULL(N_STRA), 0, N_STRA) as n_stra_clean
|
||||
FROM operazioni
|
||||
ORDER BY n_operazione
|
||||
SELECT *
|
||||
FROM Operazioni
|
||||
");
|
||||
|
||||
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
|
||||
|
|
@ -452,24 +558,52 @@ private function importOperazioni(string $year, string $mdbPath): int
|
|||
$protocollo = $this->generateProtocollo($gestioneId, $op);
|
||||
|
||||
// Snapshot voce spesa
|
||||
$voceSnapshot = $this->getVoceSpesaSnapshot($mdbPath, $op['cod_voce'] ?? '');
|
||||
$voceSnapshot = $this->getVoceSpesaSnapshot($mdbPath, $op['cod_spe'] ?? '');
|
||||
|
||||
OperazioneContabile::create([
|
||||
$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,
|
||||
'numero_operazione' => $op['n_operazione'],
|
||||
'data_operazione' => $this->parseGesconDate($op['data_oper']),
|
||||
'importo' => (float) ($op['euro'] ?? 0),
|
||||
'descrizione' => $op['descrizione'] ?? '',
|
||||
'cod_voce_gescon' => $op['cod_voce'] ?? '',
|
||||
'cod_beneficiario_gescon' => $op['cod_ben'] ?? '',
|
||||
'compet' => $op['compet_clean'],
|
||||
'natura2' => $op['natura2_clean'],
|
||||
'n_stra' => (int) $op['n_stra_clean'],
|
||||
'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),
|
||||
'metadati_gescon' => json_encode($op),
|
||||
'dare' => $dareVal,
|
||||
'avere' => $avereVal,
|
||||
'conto_contabile' => $codVoce,
|
||||
'conto_bancario_id' => $contoBancarioId,
|
||||
'stato_operazione' => 'confermata',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$imported++;
|
||||
|
|
@ -486,6 +620,63 @@ private function importOperazioni(string $year, string $mdbPath): int
|
|||
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
|
||||
*/
|
||||
|
|
@ -493,11 +684,15 @@ 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.data_oper, o.euro as imponibile_operazione
|
||||
SELECT n.*, o.dt_spe, o.importo_euro as imponibile_operazione
|
||||
FROM Nettovers_RDA n
|
||||
LEFT JOIN operazioni o ON n.Rif_RDA = o.n_operazione
|
||||
LEFT JOIN Operazioni o ON n.Rif_RDA = o.id_operaz
|
||||
WHERE n.Rif_RDA IS NOT NULL
|
||||
ORDER BY n.Rif_RDA
|
||||
");
|
||||
|
|
@ -510,7 +705,7 @@ private function importRitenuteAcconto(string $year, string $mdbPath): int
|
|||
'tenant_id' => $this->tenantId,
|
||||
'gestione_id' => $gestioneId,
|
||||
'numero_progressivo' => $ra['Rif_RDA'],
|
||||
'data_competenza' => $this->parseGesconDate($ra['data_oper']),
|
||||
'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),
|
||||
|
|
@ -542,9 +737,14 @@ private function importIncassiEstrattoConto(string $year, string $mdbPath): int
|
|||
|
||||
$incassiEc = $this->queryMdb($mdbPath, "
|
||||
SELECT * FROM Inc_da_ec
|
||||
ORDER BY data_oper
|
||||
");
|
||||
|
||||
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;
|
||||
|
|
@ -552,11 +752,14 @@ private function importIncassiEstrattoConto(string $year, string $mdbPath): int
|
|||
IncassoEstrattoConto::create([
|
||||
'tenant_id' => $this->tenantId,
|
||||
'gestione_id' => $gestioneId,
|
||||
'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'] ?? '',
|
||||
'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),
|
||||
]);
|
||||
|
|
@ -582,11 +785,21 @@ private function importRateEmesse(string $year, string $mdbPath): int
|
|||
{
|
||||
$imported = 0;
|
||||
|
||||
// Unifica EMESS_DET, EMESS_DET_2, EMESS_GEN
|
||||
$tables = ['EMESS_DET', 'EMESS_DET_2', 'EMESS_GEN'];
|
||||
// Unifica emes_det, emes_det_2, emes_gen
|
||||
$tables = ['emes_det', 'emes_det_2', 'emes_gen'];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$rate = $this->queryMdb($mdbPath, "SELECT * FROM {$table} ORDER BY data_em");
|
||||
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 {
|
||||
|
|
@ -628,18 +841,20 @@ private function determineGestioneId(string $year, array $operazione): int
|
|||
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,
|
||||
'anno_gestione' => (int) $year,
|
||||
'stabile_id' => $this->stabileId,
|
||||
'anno_gestione' => $calendarYear,
|
||||
'tipo_gestione' => 'straordinaria',
|
||||
'numero_straordinaria' => $nStra,
|
||||
'denominazione' => "Gestione Straordinaria {$year} - {$nStra}",
|
||||
'data_inizio' => "{$year}-01-01",
|
||||
'data_fine' => "{$year}-12-31",
|
||||
'denominazione' => "Gestione Straordinaria {$calendarYear} - {$nStra}",
|
||||
'data_inizio' => "{$calendarYear}-01-01",
|
||||
'data_fine' => "{$calendarYear}-12-31",
|
||||
'stato' => 'aperta',
|
||||
'protocollo_prefix' => "S{$year}-{$nStra}",
|
||||
'protocollo_prefix' => "S{$calendarYear}-{$nStra}",
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -651,11 +866,13 @@ private function getOrCreateStraordinaria(string $year, int $nStra): int
|
|||
*/
|
||||
private function queryMdb(string $mdbPath, string $query): array
|
||||
{
|
||||
$query = preg_replace('/\s+/', ' ', trim($query));
|
||||
// Usa mdb-tools per query diretta
|
||||
$command = sprintf(
|
||||
'mdb-sql "%s" <<< "%s"',
|
||||
escapeshellarg($mdbPath),
|
||||
escapeshellarg($query)
|
||||
'echo %s | mdb-sql -P -F -d %s %s',
|
||||
escapeshellarg($query),
|
||||
escapeshellarg("\t"),
|
||||
escapeshellarg($mdbPath)
|
||||
);
|
||||
|
||||
$output = shell_exec($command);
|
||||
|
|
@ -664,6 +881,55 @@ private function queryMdb(string $mdbPath, string $query): array
|
|||
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
|
||||
*/
|
||||
|
|
@ -675,16 +941,35 @@ private function parseMdbOutput(string $output): array
|
|||
}
|
||||
|
||||
$headers = array_map('trim', explode("\t", array_shift($lines)));
|
||||
$headerCount = count($headers);
|
||||
$data = [];
|
||||
|
||||
$currentValues = [];
|
||||
foreach ($lines as $line) {
|
||||
if (empty(trim($line))) {
|
||||
if ($line === '' && empty($currentValues)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$values = explode("\t", $line);
|
||||
$row = array_combine($headers, $values);
|
||||
$data[] = $row;
|
||||
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;
|
||||
|
|
@ -696,11 +981,14 @@ private function parseMdbOutput(string $output): array
|
|||
private function generateProtocollo(int $gestioneId, array $operazione): array
|
||||
{
|
||||
$gestione = GestioneContabile::find($gestioneId);
|
||||
$numero = $gestione->getNextProtocolNumber();
|
||||
|
||||
$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 . '-' . str_pad($numero, 4, '0', STR_PAD_LEFT),
|
||||
'completo' => ($gestione->protocollo_prefix ?? 'O') . '-' . str_pad($numero, 4, '0', STR_PAD_LEFT),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('piano_rateizzazione', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('ripartizione_spese_id')->nullable()->change();
|
||||
$table->unsignedBigInteger('creato_da')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('piano_rateizzazione', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('ripartizione_spese_id')->nullable(false)->change();
|
||||
$table->unsignedBigInteger('creato_da')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$conn = 'gescon_import';
|
||||
|
||||
// 1. Aggiunta colonne subentro a condomin
|
||||
if (Schema::connection($conn)->hasTable('condomin')) {
|
||||
Schema::connection($conn)->table('condomin', function (Blueprint $table) use ($conn): void {
|
||||
if (! Schema::connection($conn)->hasColumn('condomin', 'subentro_prima_cera')) {
|
||||
$table->text('subentro_prima_cera')->nullable();
|
||||
}
|
||||
if (! Schema::connection($conn)->hasColumn('condomin', 'subentro_adesso_ce')) {
|
||||
$table->text('subentro_adesso_ce')->nullable();
|
||||
}
|
||||
if (! Schema::connection($conn)->hasColumn('condomin', 'subentrato_dal')) {
|
||||
$table->text('subentrato_dal')->nullable();
|
||||
}
|
||||
if (! Schema::connection($conn)->hasColumn('condomin', 'attivo_fino_al')) {
|
||||
$table->text('attivo_fino_al')->nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Tabella rate_emissioni
|
||||
if (! Schema::connection($conn)->hasTable('rate_emissioni')) {
|
||||
Schema::connection($conn)->create('rate_emissioni', function (Blueprint $table): void {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('cod_stabile', 10);
|
||||
$table->unsignedInteger('numero_emissione');
|
||||
$table->string('anno_emissione', 20)->nullable();
|
||||
$table->date('data_emissione')->nullable();
|
||||
$table->date('data_scadenza')->nullable();
|
||||
$table->string('gestione_tipo', 5)->nullable();
|
||||
$table->string('gestione_periodo', 50)->nullable();
|
||||
$table->string('descrizione', 255)->nullable();
|
||||
$table->string('nota_avvisi', 255)->nullable();
|
||||
$table->string('nota_ricevute', 255)->nullable();
|
||||
$table->string('nota_ccp', 255)->nullable();
|
||||
$table->string('stato_stampa', 20)->nullable();
|
||||
$table->string('provvisorio_definitivo', 20)->nullable();
|
||||
$table->json('payload')->nullable();
|
||||
$table->timestamps();
|
||||
$table->unique(['cod_stabile', 'numero_emissione']);
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Tabella rate_emissioni_dettaglio
|
||||
if (! Schema::connection($conn)->hasTable('rate_emissioni_dettaglio')) {
|
||||
Schema::connection($conn)->create('rate_emissioni_dettaglio', function (Blueprint $table): void {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('cod_stabile', 10);
|
||||
$table->unsignedInteger('numero_emissione');
|
||||
$table->string('anno_emissione', 20)->nullable();
|
||||
$table->string('cod_cond', 20)->nullable();
|
||||
$table->string('tipo_soggetto', 1)->nullable();
|
||||
$table->unsignedInteger('numero_mese')->nullable();
|
||||
$table->string('tipo_quota', 2)->nullable();
|
||||
$table->decimal('importo_dovuto', 14, 4)->nullable();
|
||||
$table->decimal('importo_dovuto_euro', 14, 4)->nullable();
|
||||
$table->date('data_emissione')->nullable();
|
||||
$table->string('descrizione', 255)->nullable();
|
||||
$table->unsignedInteger('numero_ricevuta')->nullable();
|
||||
$table->string('anno_gestione', 20)->nullable();
|
||||
$table->unsignedInteger('numero_straordinaria')->nullable();
|
||||
$table->decimal('gia_pagato', 14, 4)->nullable();
|
||||
$table->decimal('residuo_emesso', 14, 4)->nullable();
|
||||
$table->decimal('compensato', 14, 4)->nullable();
|
||||
$table->string('cond_inq', 5)->nullable();
|
||||
$table->string('raggruppamento', 20)->nullable();
|
||||
$table->json('payload')->nullable();
|
||||
$table->timestamps();
|
||||
$table->index(['cod_stabile', 'numero_emissione']);
|
||||
$table->index(['cod_stabile', 'cod_cond']);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Nessun down distruttivo per staging in produzione/sviluppo
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('incassi_pagamenti', function (Blueprint $table) {
|
||||
if (!Schema::hasColumn('incassi_pagamenti', 'rata_id')) {
|
||||
$table->unsignedBigInteger('rata_id')->nullable()->after('anno_gestione_id');
|
||||
}
|
||||
|
||||
if (!Schema::hasColumn('incassi_pagamenti', 'movimento_banca_id')) {
|
||||
$table->unsignedBigInteger('movimento_banca_id')->nullable()->after('rata_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('incassi_pagamenti', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('incassi_pagamenti', 'rata_id')) {
|
||||
$table->dropColumn('rata_id');
|
||||
}
|
||||
if (Schema::hasColumn('incassi_pagamenti', 'movimento_banca_id')) {
|
||||
$table->dropColumn('movimento_banca_id');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (!Schema::connection('gescon_import')->hasTable('operazioni')) {
|
||||
Schema::connection('gescon_import')->create('operazioni', function (Blueprint $table) {
|
||||
$table->bigIncrements('id_operaz');
|
||||
$table->integer('n_spe')->nullable();
|
||||
$table->date('dt_spe')->nullable();
|
||||
$table->string('cod_spe', 20)->nullable();
|
||||
$table->string('tabella')->nullable();
|
||||
$table->decimal('importo', 12, 2)->nullable();
|
||||
$table->decimal('importo_euro', 12, 2)->nullable();
|
||||
$table->string('cod_ben')->nullable();
|
||||
$table->string('benef')->nullable();
|
||||
$table->string('benef2')->nullable();
|
||||
$table->string('compet')->nullable();
|
||||
$table->text('note')->nullable();
|
||||
$table->integer('n_stra')->nullable();
|
||||
$table->string('cod_cassa')->nullable();
|
||||
$table->string('natura')->nullable();
|
||||
$table->string('natura2')->nullable();
|
||||
$table->string('cod_for')->nullable();
|
||||
$table->string('num_fat')->nullable();
|
||||
$table->date('dt_fat')->nullable();
|
||||
$table->integer('anno')->nullable();
|
||||
$table->decimal('imp_calcolato', 12, 2)->nullable();
|
||||
$table->decimal('imp_calcolato_euro', 12, 2)->nullable();
|
||||
$table->decimal('imp_2', 12, 2)->nullable();
|
||||
$table->decimal('imp_2_euro', 12, 2)->nullable();
|
||||
$table->decimal('importo_spese', 12, 2)->nullable();
|
||||
$table->decimal('importo_entrate', 12, 2)->nullable();
|
||||
$table->decimal('importo_crediti', 12, 2)->nullable();
|
||||
$table->decimal('importo_debiti', 12, 2)->nullable();
|
||||
$table->boolean('incluso')->default(false);
|
||||
$table->string('nord')->nullable();
|
||||
$table->boolean('temporaneo')->default(false);
|
||||
$table->string('d36_41')->nullable();
|
||||
$table->decimal('netto_vers_rda', 12, 2)->nullable();
|
||||
$table->string('rif_rda')->nullable();
|
||||
$table->decimal('importo_euro_ac', 12, 2)->nullable();
|
||||
$table->decimal('importo_euro_770', 12, 2)->nullable();
|
||||
$table->string('file_bonifico_telematico')->nullable();
|
||||
$table->decimal('detraz_36', 12, 2)->nullable();
|
||||
$table->string('etic_axivar')->nullable();
|
||||
$table->boolean('in_ac')->default(false);
|
||||
$table->string('gestione')->nullable();
|
||||
$table->string('proviene_ors')->nullable();
|
||||
$table->integer('proviene_n_stra')->nullable();
|
||||
$table->string('proviene_eserc')->nullable();
|
||||
$table->string('rif_ft_amm')->nullable();
|
||||
$table->string('fatt_amm_fc')->nullable();
|
||||
$table->string('df_tipo_lavori')->nullable();
|
||||
$table->string('fe_uid')->nullable();
|
||||
$table->string('cod_stabile', 10)->nullable();
|
||||
$table->string('legacy_year', 20)->nullable();
|
||||
$table->string('slice_id', 100)->nullable();
|
||||
$table->string('legacy_file', 255)->nullable();
|
||||
$table->string('row_hash', 64)->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::connection('gescon_import')->dropIfExists('operazioni');
|
||||
}
|
||||
};
|
||||
|
|
@ -2,14 +2,33 @@
|
|||
$gestioniTab = request()->query('gestioni_tab', 'gestioni');
|
||||
$gestioniTab = in_array($gestioniTab, ['gestioni', 'periodi', 'straordinarie', 'archivio'], true) ? $gestioniTab : 'gestioni';
|
||||
$baseUrl = \App\Filament\Pages\Condomini\StabilePage::getUrl(panel: 'admin-filament');
|
||||
$mostraRiscaldamento = (bool) data_get($stabile->configurazione_avanzata, 'mostra_riscaldamento', false);
|
||||
$gestioneSelezionata = $this->gestioneSelezionataId
|
||||
? \App\Models\GestioneContabile::query()->find($this->gestioneSelezionataId)
|
||||
: ($this->gestioneOrdinariaId ? \App\Models\GestioneContabile::query()->find($this->gestioneOrdinariaId) : null);
|
||||
$mesi = [];
|
||||
if ($gestioneSelezionata && $gestioneSelezionata->data_inizio && $gestioneSelezionata->data_fine) {
|
||||
$start = \Carbon\Carbon::parse($gestioneSelezionata->data_inizio);
|
||||
$end = \Carbon\Carbon::parse($gestioneSelezionata->data_fine);
|
||||
$curr = $start->copy()->startOfMonth();
|
||||
$limit = 0;
|
||||
while ($curr->lte($end) && $limit < 24) {
|
||||
$mVal = (int) $curr->month;
|
||||
$mName = match ($mVal) {
|
||||
1 => 'Gen', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'Mag', 6 => 'Giu',
|
||||
7 => 'Lug', 8 => 'Ago', 9 => 'Set', 10 => 'Ott', 11 => 'Nov', 12 => 'Dic',
|
||||
};
|
||||
$mYear = $curr->year;
|
||||
$mesi[$mVal] = "{$mName} {$mYear}";
|
||||
$curr->addMonth();
|
||||
$limit++;
|
||||
}
|
||||
}
|
||||
if (empty($mesi)) {
|
||||
$mesi = [
|
||||
1 => 'Gen', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'Mag', 6 => 'Giu',
|
||||
7 => 'Lug', 8 => 'Ago', 9 => 'Set', 10 => 'Ott', 11 => 'Nov', 12 => 'Dic',
|
||||
];
|
||||
$gestioneSelezionata = $this->gestioneSelezionataId
|
||||
? \App\Models\GestioneContabile::query()->find($this->gestioneSelezionataId)
|
||||
: ($this->gestioneOrdinariaId ? \App\Models\GestioneContabile::query()->find($this->gestioneOrdinariaId) : null);
|
||||
}
|
||||
$gestioneTipo = $gestioneSelezionata?->tipo_gestione;
|
||||
$checklistItems = \App\Models\ChecklistFineAnnoItem::query()
|
||||
->where('is_active', true)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ class="pb-3 px-1 border-b-2 font-medium text-sm {{ $acquaTab === 'tariffe' ? 'bo
|
|||
class="pb-3 px-1 border-b-2 font-medium text-sm {{ $acquaTab === 'servizi' ? 'border-blue-600 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||||
Servizi / Utenze (tabella)
|
||||
</button>
|
||||
<button type="button"
|
||||
wire:click="setAcquaTab('pagamenti_cbill')"
|
||||
class="pb-3 px-1 border-b-2 font-medium text-sm {{ $acquaTab === 'pagamenti_cbill' ? 'border-blue-600 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||||
Pagamenti CBILL
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
|
@ -363,6 +368,8 @@ class="rounded border-cyan-300 text-cyan-600 focus:ring-cyan-500">
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="rounded-lg border border-indigo-200 bg-indigo-50 p-4 dark:border-indigo-700/50 dark:bg-indigo-900/10">
|
||||
<div class="mb-2 text-sm font-semibold text-indigo-900 dark:text-indigo-200">Archivio legacy operazioni AC1 + AC2 (collegamento fatture)</div>
|
||||
@if(!empty($legacyOpsSummary['scope_note']))
|
||||
|
|
@ -913,6 +920,72 @@ class="inline-flex items-center rounded-md border border-cyan-300 bg-white px-3
|
|||
{{ $this->table }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($acquaTab === 'pagamenti_cbill')
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-gray-900 shadow-sm">
|
||||
<div class="mb-3 text-sm font-semibold text-gray-900 dark:text-gray-100">Pagamenti abbinati tramite codice CBILL</div>
|
||||
<div class="mb-3 text-xs text-gray-500">I seguenti movimenti bancari sono stati associati alle fatture elettroniche dello stabile tramite il codice CBILL estratto.</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200 dark:border-gray-800 text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
<th class="px-3 py-2 text-left">Data movimento</th>
|
||||
<th class="px-3 py-2 text-left font-mono">Codice CBILL</th>
|
||||
<th class="px-3 py-2 text-left">Fornitore</th>
|
||||
<th class="px-3 py-2 text-left">Fattura Elettronica di origine</th>
|
||||
<th class="px-3 py-2 text-right">Totale fattura €</th>
|
||||
<th class="px-3 py-2 text-left">Descrizione movimento bancario</th>
|
||||
<th class="px-3 py-2 text-right">Importo pagato €</th>
|
||||
<th class="px-3 py-2 text-center">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($this->acquaCbillPagamenti as $p)
|
||||
<tr class="border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50/50 text-xs text-gray-700 dark:text-gray-300">
|
||||
<td class="px-3 py-2 whitespace-nowrap">{{ \Carbon\Carbon::parse($p['data'])->format('d/m/Y') }}</td>
|
||||
<td class="px-3 py-2 font-mono text-[11px] text-gray-900 dark:text-gray-100">{{ $p['cbill'] }}</td>
|
||||
<td class="px-3 py-2">{{ $p['fornitore'] }}</td>
|
||||
<td class="px-3 py-2">
|
||||
@if($p['fattura_numero'] !== '—')
|
||||
<span class="font-semibold text-gray-900 dark:text-gray-100">N. {{ $p['fattura_numero'] }}</span> del {{ \Carbon\Carbon::parse($p['fattura_data'])->format('d/m/Y') }}
|
||||
@else
|
||||
—
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-3 py-2 text-right font-semibold tabular-nums">{{ $p['fattura_totale'] > 0 ? number_format((float) $p['fattura_totale'], 2, ',', '.') : '—' }}</td>
|
||||
<td class="px-3 py-2 max-w-xs truncate" title="{{ $p['descrizione'] }}">{{ $p['descrizione'] }}</td>
|
||||
<td class="px-3 py-2 text-right font-semibold text-danger-600 tabular-nums">{{ number_format((float) $p['importo'], 2, ',', '.') }}</td>
|
||||
<td class="px-3 py-2 text-center whitespace-nowrap space-x-2">
|
||||
@if(!empty($p['fattura_id']))
|
||||
<a href="/admin-filament/contabilita/fatture-ricevute/{{ $p['fattura_id'] }}"
|
||||
target="_blank"
|
||||
class="inline-flex items-center gap-1 rounded bg-sky-50 px-2.5 py-1 text-xs font-semibold text-sky-700 hover:bg-sky-100 dark:bg-sky-950 dark:text-sky-300">
|
||||
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m5.231 13.481L17 17.25m-1.769-1.769a3.75 3.75 0 11-5.303-5.303 3.75 3.75 0 015.303 5.303z" />
|
||||
</svg>
|
||||
Vedi FE
|
||||
</a>
|
||||
@endif
|
||||
<a href="/admin-filament/contabilita/casse-banche/movimenti?tab=movimenti"
|
||||
target="_blank"
|
||||
class="inline-flex items-center gap-1 rounded bg-teal-50 px-2.5 py-1 text-xs font-semibold text-teal-700 hover:bg-teal-100 dark:bg-teal-950 dark:text-teal-300">
|
||||
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18.75a60.07 60.07 0 0115.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5h.007m-.007 3h.007m-.007 3h.007m3-6h.007m-.007 3h.007m-.007 3h.007m3-6h.007m-.007 3h.007m-.007 3h.007m3-6h.007m-.007 3h.007m-.007 3h.007m-9 6h.007m-.007 3h.007m-.007 3h.007m3-6h.007m-.007 3h.007m-.007 3h.007m3-6h.007m-.007 3h.007m-.007 3h.007m3-6h.007m-.007 3h.007m-.007 3h.007m-12-6a3 3 0 013-3h15a3 3 0 013 3v6a3 3 0 01-3 3H3.75a3 3 0 01-3-3v-6z" />
|
||||
</svg>
|
||||
Vedi Movimenti
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="px-3 py-4 text-center text-gray-500 dark:text-gray-400">Nessun pagamento trovato abbinato tramite codice CBILL.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Modal per invio email ripartizione -->
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
<x-filament::tabs.item :active="$this->hubTab === 'movimenti'" wire:click="goToHubTab('movimenti')">Movimenti</x-filament::tabs.item>
|
||||
<x-filament::tabs.item :active="$this->hubTab === 'struttura'" wire:click="goToHubTab('struttura')">Struttura e gestioni</x-filament::tabs.item>
|
||||
<x-filament::tabs.item :active="$this->hubTab === 'riconciliazione'" wire:click="goToHubTab('riconciliazione')">Riconciliazione</x-filament::tabs.item>
|
||||
<x-filament::tabs.item :active="$this->hubTab === 'associazione_causali'" wire:click="goToHubTab('associazione_causali')">Associazione Causali</x-filament::tabs.item>
|
||||
</x-filament::tabs>
|
||||
|
||||
@if($this->hubTab === 'conti')
|
||||
|
|
@ -622,6 +623,90 @@
|
|||
{{ $this->table }}
|
||||
</div>
|
||||
</x-filament::section>
|
||||
@elseif($this->hubTab === 'associazione_causali')
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">Associazione Causali e Regole Bancarie</x-slot>
|
||||
<x-slot name="description">Configura i conti Dare e Avere per ciascun codice causale rilevato nei movimenti banca.</x-slot>
|
||||
|
||||
@php($assocRows = $this->getCausaliAssociazioneRows())
|
||||
|
||||
<div class="mt-4 overflow-x-auto rounded-lg border">
|
||||
<table class="w-full text-left text-sm divide-y divide-gray-200 dark:divide-gray-800">
|
||||
<thead class="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300">Causale bancaria</th>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300">Descrizione rilevata</th>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300 text-center">Movimenti</th>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300">Conto Dare (Entrate)</th>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300">Conto Avere (Uscite)</th>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300 text-center">Stato regola</th>
|
||||
<th class="px-4 py-3 font-semibold text-gray-700 dark:text-gray-300 text-right">Azioni</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-800 bg-white dark:bg-gray-900">
|
||||
@forelse($assocRows as $row)
|
||||
<tr>
|
||||
<td class="px-4 py-3 font-mono font-bold text-gray-900 dark:text-gray-100">
|
||||
{{ $row['causale'] }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-600 dark:text-gray-400">
|
||||
{{ $row['descrizione'] }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center font-semibold">
|
||||
{{ $row['count'] }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-900 dark:text-gray-100">
|
||||
@if($row['conto_dare'])
|
||||
<span class="inline-flex items-center rounded-md bg-blue-50 dark:bg-blue-900/30 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
|
||||
{{ $row['conto_dare'] }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-xs text-gray-400">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-900 dark:text-gray-100">
|
||||
@if($row['conto_avere'])
|
||||
<span class="inline-flex items-center rounded-md bg-blue-50 dark:bg-blue-900/30 px-2 py-1 text-xs font-medium text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
|
||||
{{ $row['conto_avere'] }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-xs text-gray-400">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center">
|
||||
@if($row['attiva'])
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-emerald-100 dark:bg-emerald-950/30 px-2.5 py-0.5 text-xs font-semibold text-emerald-800 dark:text-emerald-300 border border-emerald-200 dark:border-emerald-900">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
|
||||
Attiva
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-amber-100 dark:bg-amber-950/30 px-2.5 py-0.5 text-xs font-semibold text-amber-800 dark:text-amber-300 border border-amber-200 dark:border-amber-900">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span>
|
||||
Configurazione mancante
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<x-filament::button
|
||||
size="sm"
|
||||
color="{{ $row['attiva'] ? 'gray' : 'primary' }}"
|
||||
wire:click="startConfiguringRegolaCausale('{{ $row['causale'] }}')"
|
||||
>
|
||||
{{ $row['attiva'] ? 'Modifica regola' : 'Configura regola' }}
|
||||
</x-filament::button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-4 py-6 text-center text-gray-500">
|
||||
Nessun movimento banca importato con codici causale rilevati.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,30 @@
|
|||
@php
|
||||
$record = $record ?? null;
|
||||
$details = $record ? $record->dettaglio_estrato : [];
|
||||
@endphp
|
||||
|
||||
@if(!$record)
|
||||
<div class="text-sm text-gray-600">Nessun movimento selezionato.</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 p-4 text-center">Nessun movimento selezionato.</div>
|
||||
@else
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xs text-gray-500">ID #{{ $record->id }}</div>
|
||||
<div class="space-y-4">
|
||||
<!-- Header con Azioni di Navigazione e Codici Chiave -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 border-b pb-3 dark:border-gray-700">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="text-xs font-semibold px-2.5 py-1 rounded-full bg-slate-100 text-slate-800 border border-slate-200 dark:bg-slate-800 dark:text-slate-200 dark:border-slate-700">
|
||||
ID #{{ $record->id }}
|
||||
</span>
|
||||
@if(!empty($details['abi']))
|
||||
<span class="text-xs font-semibold px-2.5 py-1 rounded-full bg-blue-50 text-blue-700 border border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800">
|
||||
ABI: {{ $details['abi'] }}
|
||||
</span>
|
||||
@endif
|
||||
@if(!empty($details['cbill_code']))
|
||||
<span class="text-xs font-semibold px-2.5 py-1 rounded-full bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-900/30 dark:text-amber-400 dark:border-amber-800">
|
||||
CBILL: {{ $details['cbill_code'] }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
@if(!empty($prevId))
|
||||
<x-filament::button size="xs" color="gray" wire:click="setDetailMovement({{ (int) $prevId }})">← Prec</x-filament::button>
|
||||
|
|
@ -18,76 +35,83 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Data</div>
|
||||
<div class="font-semibold">{{ optional($record->data)->format('d/m/Y') ?: '—' }}</div>
|
||||
<!-- Sezione Dettagli Principali (Grid) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<!-- Colonna 1: Date ed Importo -->
|
||||
<div class="space-y-3 bg-slate-50 dark:bg-gray-800/40 p-4 rounded-xl border border-slate-100 dark:border-gray-700">
|
||||
<div>
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Importo</span>
|
||||
<span class="text-xl font-bold text-emerald-600 dark:text-emerald-400">
|
||||
€ {{ number_format((float) $record->importo, 2, ',', '.') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Valuta</div>
|
||||
<div class="font-semibold">{{ optional($record->valuta)->format('d/m/Y') ?: '—' }}</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 pt-2 border-t border-slate-200/60 dark:border-gray-700/60">
|
||||
<div>
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Data Operazione</span>
|
||||
<span class="text-xs font-semibold text-slate-700 dark:text-slate-300">
|
||||
{{ optional($record->data)->format('d/m/Y') ?: '—' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Importo</div>
|
||||
<div class="font-semibold">€ {{ number_format((float) $record->importo, 2, ',', '.') }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Causale</div>
|
||||
<div class="font-semibold">{{ $record->causale ?? '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Descrizione</div>
|
||||
<div class="font-semibold">{{ $record->descrizione ?? '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Descrizione estesa</div>
|
||||
<div class="font-semibold">{{ $record->descrizione_estesa_pulita ?? $record->descrizione_estesa ?? '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Saldo progressivo</div>
|
||||
<div class="font-semibold">
|
||||
@if(isset($record->saldo_progressivo) && is_numeric($record->saldo_progressivo))
|
||||
€ {{ number_format((float) $record->saldo_progressivo, 2, ',', '.') }}
|
||||
@else
|
||||
—
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">File sorgente</div>
|
||||
<div class="font-semibold">{{ $record->source_file ?? '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Disp</div>
|
||||
<div class="font-semibold">{{ is_array($record->match_data ?? null) ? ($record->match_data['cod_disp'] ?? '—') : '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Cash</div>
|
||||
<div class="font-semibold">{{ is_array($record->match_data ?? null) ? ($record->match_data['cash'] ?? '—') : '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500">Mittente</div>
|
||||
<div class="font-semibold">{{ is_array($record->match_data ?? null) ? ($record->match_data['mittente'] ?? '—') : '—' }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-white p-3 text-xs md:col-span-2">
|
||||
<div class="text-gray-500">Riga raw importata</div>
|
||||
<div class="font-semibold break-words">{{ $record->raw_line ?? '—' }}</div>
|
||||
<div>
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Data Valuta</span>
|
||||
<span class="text-xs font-semibold text-slate-700 dark:text-slate-300">
|
||||
{{ optional($record->valuta)->format('d/m/Y') ?: '—' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@php
|
||||
$match = $record->match_data;
|
||||
@endphp
|
||||
@if(is_array($match) && !empty($match))
|
||||
<div class="rounded-lg border bg-white p-3 text-xs">
|
||||
<div class="text-gray-500 mb-2">Dati estratti</div>
|
||||
<ul class="list-disc pl-4 space-y-1">
|
||||
@foreach($match as $k => $v)
|
||||
<li><span class="font-semibold">{{ $k }}:</span> {{ is_scalar($v) ? $v : json_encode($v) }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<div class="pt-2 border-t border-slate-200/60 dark:border-gray-700/60">
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Causale</span>
|
||||
<span class="text-xs font-semibold text-slate-700 dark:text-slate-300">
|
||||
{{ $record->causale ?? '—' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Colonna 2 e 3: Messaggi ed Estrazioni -->
|
||||
<div class="md:col-span-2 space-y-3 bg-white dark:bg-gray-800/20 p-4 rounded-xl border border-slate-100 dark:border-gray-700">
|
||||
<!-- Messaggio Banca (Standard) -->
|
||||
<div>
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Messaggio Standard Banca</span>
|
||||
<div class="text-xs font-semibold text-slate-500 dark:text-slate-400 bg-slate-50/50 dark:bg-gray-800/40 p-2.5 rounded-lg border border-slate-100 dark:border-gray-700 mt-1 font-mono">
|
||||
{{ $details['messaggio_banca'] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messaggio Cliente (Dati Riferimento) -->
|
||||
<div>
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Riferimento / Dettaglio Cliente</span>
|
||||
<div class="text-sm font-bold text-slate-800 dark:text-slate-200 bg-blue-50/20 dark:bg-blue-900/10 p-2.5 rounded-lg border border-blue-100/30 dark:border-blue-900/30 mt-1">
|
||||
{{ $details['messaggio_cliente'] }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Commissioni / Spese -->
|
||||
<div>
|
||||
<span class="block text-xxs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider">Commissioni, Spese e Codice TRN</span>
|
||||
<div class="text-xs font-medium text-slate-600 dark:text-slate-400 mt-1 bg-slate-50/50 dark:bg-gray-800/40 p-2.5 rounded-lg border border-slate-100 dark:border-gray-700 font-mono">
|
||||
{{ $details['commissioni_spese'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dati Aggiuntivi / Raw Line -->
|
||||
<div class="bg-slate-50/50 dark:bg-gray-800/10 p-3 rounded-lg border border-slate-100 dark:border-gray-700/60 text-xs">
|
||||
<details class="group">
|
||||
<summary class="flex justify-between items-center font-semibold text-slate-500 dark:text-slate-400 cursor-pointer select-none">
|
||||
<span>Visualizza Tracciato Originale (Raw Line)</span>
|
||||
<span class="transition group-open:rotate-180">
|
||||
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="mt-2.5 pt-2 border-t border-slate-200/60 dark:border-gray-700/60 text-slate-600 dark:text-slate-400 font-mono break-all leading-relaxed">
|
||||
{{ $record->raw_line ?? '—' }}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -8,14 +8,55 @@
|
|||
]"
|
||||
/>
|
||||
|
||||
<x-filament::section>
|
||||
<form wire:submit.prevent="submit" class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 lg:grid-cols-12">
|
||||
<div class="lg:col-span-9 space-y-4">
|
||||
<form wire:submit.prevent="submit" id="prima-nota-form" class="space-y-4">
|
||||
{{ $this->form }}
|
||||
<div class="flex items-center gap-3">
|
||||
<x-filament::button type="submit" icon="heroicon-o-check">Salva</x-filament::button>
|
||||
<x-filament::button color="gray" href="{{ \App\Filament\Pages\Contabilita\PrimaNotaArchivio::getUrl(panel: 'admin-filament') }}" tag="a">Annulla</x-filament::button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@php($hdr = $this->getHeaderSummary())
|
||||
@php($delta = (float) ($hdr['delta'] ?? 0))
|
||||
@php($isBilanciata = (bool) ($hdr['is_bilanciata'] ?? false))
|
||||
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">Totali e Quadratura</x-slot>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-3">
|
||||
<div class="rounded-lg border bg-gray-50 p-3">
|
||||
<div class="text-xs text-gray-500">Totale Dare</div>
|
||||
<div class="mt-1 text-lg font-semibold">€ {{ number_format((float) ($hdr['totale_dare'] ?? 0), 2, ',', '.') }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border bg-gray-50 p-3">
|
||||
<div class="text-xs text-gray-500">Totale Avere</div>
|
||||
<div class="mt-1 text-lg font-semibold">€ {{ number_format((float) ($hdr['totale_avere'] ?? 0), 2, ',', '.') }}</div>
|
||||
</div>
|
||||
<div class="rounded-lg border p-3 {{ $isBilanciata ? 'bg-success-50 border-success-200' : 'bg-danger-50 border-danger-200' }}">
|
||||
<div class="text-xs text-gray-500">Quadratura (delta)</div>
|
||||
<div class="mt-1 text-lg font-semibold {{ $isBilanciata ? 'text-success-700' : 'text-danger-700' }}">€ {{ number_format($delta, 2, ',', '.') }}</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
@if($isBilanciata)
|
||||
Registrazione bilanciata
|
||||
@else
|
||||
Sbilancio: € {{ number_format(abs($delta), 2, ',', '.') }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-3 space-y-4 lg:sticky lg:top-4 lg:self-start">
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">Funzioni base</x-slot>
|
||||
<div class="space-y-2">
|
||||
<x-filament::button form="prima-nota-form" type="submit" color="primary" class="w-full" icon="heroicon-o-check" :disabled="!$isBilanciata">
|
||||
Memorizza
|
||||
</x-filament::button>
|
||||
<x-filament::button color="gray" tag="a" href="{{ \App\Filament\Pages\Contabilita\PrimaNotaArchivio::getUrl(panel: 'admin-filament') }}" class="w-full">
|
||||
Annulla
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-filament-panels::page>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,28 @@
|
|||
</div>
|
||||
|
||||
<div class="bg-warning-50 px-4 py-4 space-y-2">
|
||||
@if(($this->tipoGestioneTab ?? 'ordinaria') === 'acqua')
|
||||
<div class="mb-4 p-4 rounded-lg bg-blue-50 border border-blue-200 flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<svg class="h-6 w-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 111.086 1.086L13 13.75l.04.02a.75.75 0 11-1.08 1.08l-.71-.71a.75.75 0 010-1.06l.71-.71z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v.008H12v-.008zm0-6.75C6.885 2.25 2.25 6.885 2.25 12s4.635 9.75 9.75 9.75 9.75-4.635 9.75-9.75S17.115 2.25 12 2.25z" />
|
||||
</svg>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-blue-900">Configurazione e Letture Acqua</div>
|
||||
<div class="text-xs text-blue-700">Le tariffe, i consumi e le letture dell'acqua sono gestite nella sezione Servizi e Utenze dello Stabile.</div>
|
||||
</div>
|
||||
</div>
|
||||
<x-filament::button
|
||||
color="info"
|
||||
tag="a"
|
||||
href="/admin-filament/condomini/servizi-utenze"
|
||||
icon="heroicon-m-arrow-right"
|
||||
>
|
||||
Vai a Servizi / Utenze Acqua
|
||||
</x-filament::button>
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
@php
|
||||
$tabs = [
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user