netgescon-day0/app/Modules/Contabilita/Services/PagamentoFornitorePrimaNotaService.php

340 lines
15 KiB
PHP
Executable File

<?php
namespace App\Modules\Contabilita\Services;
use App\Models\Fornitore;
use App\Models\RegistroRitenuteAcconto;
use App\Models\Stabile;
use App\Models\User;
use App\Modules\Contabilita\Models\FatturaFornitore;
use App\Modules\Contabilita\Models\Movimento;
use App\Modules\Contabilita\Models\MovimentoBanca;
use App\Modules\Contabilita\Models\PianoConti;
use App\Modules\Contabilita\Models\Registrazione;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use RuntimeException;
class PagamentoFornitorePrimaNotaService
{
/**
* @param array{gestione_id?:int, conto_finanziario_id?:int, descrizione?:string, data_registrazione?:string, fattura_fornitore_id?:int} $override
*/
public function generaPagamentoDaMovimento(User $user, MovimentoBanca $movimento, int $fornitoreId, array $override = []): Registrazione
{
if (! Schema::hasTable('contabilita_registrazioni') || ! Schema::hasTable('contabilita_movimenti')) {
throw new RuntimeException('Tabelle prima nota mancanti.');
}
if ($movimento->registrazione_id) {
$existing = Registrazione::query()->find((int) $movimento->registrazione_id);
if ($existing) {
return $existing;
}
}
$stabileId = (int) ($movimento->stabile_id ?? 0);
if ($stabileId <= 0) {
throw new RuntimeException('Movimento banca senza stabile_id.');
}
$gestioneId = (int) (($override['gestione_id'] ?? null) ?: ($movimento->gestione_id ?? 0));
if ($gestioneId <= 0) {
throw new RuntimeException('Seleziona una gestione per questo movimento banca.');
}
if (Schema::hasTable('contabilita_chiusure_gestione')) {
$chiusa = app(ChiusuraGestioneService::class)->isGestioneChiusa($gestioneId);
if ($chiusa) {
throw new RuntimeException('Gestione chiusa: non è possibile generare nuove registrazioni.');
}
}
$importo = (float) ($movimento->importo ?? 0);
if (abs($importo) < 0.005) {
throw new RuntimeException('Importo movimento banca non valido.');
}
// Pagamento fornitore: tipicamente uscita (importo < 0). Accettiamo comunque il valore assoluto.
$amount = round(abs($importo), 2);
$contoFinanziarioId = (int) (($override['conto_finanziario_id'] ?? null) ?: 0);
if ($contoFinanziarioId <= 0) {
$contoFinanziarioId = (int) app(ContiFinanziariService::class)
->resolveContoFinanziarioDaMovimentoBanca($movimento)
->id;
}
$contoFinanziario = PianoConti::query()->find($contoFinanziarioId);
if (! $contoFinanziario) {
throw new RuntimeException('Conto finanziario non valido.');
}
$fornitore = Fornitore::query()->find($fornitoreId);
$fornitoreLabel = $fornitore && is_string($fornitore->ragione_sociale ?? null)
? trim((string) $fornitore->ragione_sociale)
: null;
$fornitoreCode = $fornitore && is_string($fornitore->codice_univoco ?? null)
? trim((string) $fornitore->codice_univoco)
: null;
$fattura = null;
$fatturaId = (int) (($override['fattura_fornitore_id'] ?? null) ?: 0);
if ($fatturaId > 0 && Schema::hasTable('contabilita_fatture_fornitori')) {
$fattura = FatturaFornitore::query()->find($fatturaId);
if (! $fattura) {
throw new RuntimeException('Fattura fornitore non trovata.');
}
if ((int) ($fattura->fornitore_id ?? 0) !== $fornitoreId) {
throw new RuntimeException('La fattura selezionata non appartiene al fornitore indicato.');
}
if ((int) ($fattura->gestione_id ?? 0) > 0 && $gestioneId <= 0) {
$gestioneId = (int) $fattura->gestione_id;
}
}
$contoDebitoFornitore = app(ContiSoggettiService::class)->resolveContoDebitoFornitore(
$stabileId,
$fornitoreId,
$fornitoreLabel,
$fornitoreCode
);
$data = isset($override['data_registrazione']) && is_string($override['data_registrazione'])
? trim((string) $override['data_registrazione'])
: null;
$data = $data !== '' ? $data : null;
$data = $data ?: ($movimento->data ? $movimento->data->toDateString() : now()->toDateString());
$descr = isset($override['descrizione']) && is_string($override['descrizione'])
? trim((string) $override['descrizione'])
: '';
if ($descr === '') {
$base = is_string($movimento->descrizione) ? trim((string) $movimento->descrizione) : '';
$descr = $base !== '' ? $base : 'Pagamento fornitore';
}
return DB::transaction(function () use ($user, $movimento, $stabileId, $gestioneId, $data, $descr, $amount, $contoFinanziarioId, $contoDebitoFornitore, $fornitoreId, $fattura) {
$splitRegs = [];
$righe = [];
if ($fattura && Schema::hasTable('contabilita_fatture_fornitori_righe')) {
$righe = DB::table('contabilita_fatture_fornitori_righe')
->where('fattura_id', $fattura->id)
->whereNotNull('gestione_id')
->select('gestione_id', DB::raw('SUM(totale_euro) as totale'))
->groupBy('gestione_id')
->get();
}
if (count($righe) > 1) {
$totalInvoice = (float) $righe->sum('totale');
if ($totalInvoice <= 0) {
$totalInvoice = 1.0;
}
$allocatedAmount = 0.0;
$lastIndex = count($righe) - 1;
foreach ($righe as $index => $riga) {
$gId = (int) $riga->gestione_id;
if ($index === $lastIndex) {
$splitAmount = round($amount - $allocatedAmount, 2);
} else {
$splitAmount = round($amount * ((float) $riga->totale / $totalInvoice), 2);
$allocatedAmount += $splitAmount;
}
if ($splitAmount <= 0) {
continue;
}
$regPayload = [
'stabile_id' => $stabileId,
'gestione_id' => (Schema::hasColumn('contabilita_registrazioni', 'gestione_id')
? Registrazione::resolveGestioneIdOrDefault($gId, $stabileId, $data)
: null),
'data_registrazione' => $data,
'descrizione' => $descr . ' (Quota Gestione #' . $gId . ')',
'created_by' => (int) $user->id,
'updated_by' => (int) $user->id,
];
if (Schema::hasColumn('contabilita_registrazioni', 'user_id')) {
$regPayload['user_id'] = (int) $user->id;
}
/** @var Registrazione $reg */
$reg = Registrazione::query()->create($regPayload);
// Dare: diminuisce il debito verso fornitore per la quota
Movimento::query()->create([
'registrazione_id' => (int) $reg->id,
'conto_id' => (int) $contoDebitoFornitore->id,
'tipo' => 'dare',
'importo' => $splitAmount,
]);
// Avere: diminuisce la banca/cassa per la quota
Movimento::query()->create([
'registrazione_id' => (int) $reg->id,
'conto_id' => $contoFinanziarioId,
'tipo' => 'avere',
'importo' => $splitAmount,
]);
$reg->assertBilanciata();
$splitRegs[] = $reg;
$this->syncRegistroRitenutaDaPagamento($fattura, $gId, $data);
}
if (! empty($splitRegs)) {
$firstReg = $splitRegs[0];
$movimento->registrazione_id = (int) $firstReg->id;
$movimento->gestione_id = $firstReg->gestione_id;
$matchData = is_string($movimento->match_data) ? json_decode($movimento->match_data, true) : (is_array($movimento->match_data) ? $movimento->match_data : []);
$matchData['split_registrazioni_ids'] = collect($splitRegs)->pluck('id')->all();
$movimento->match_data = $matchData;
if (Schema::hasColumn('contabilita_movimenti_banca', 'fornitore_id')) {
$movimento->fornitore_id = $fornitoreId;
}
$movimento->save();
}
if ($fattura) {
$fattura->data_pagamento = $fattura->data_pagamento ?: $data;
$fattura->movimento_pagamento_id = (int) $movimento->id;
$fattura->save();
}
return empty($splitRegs) ? Registrazione::query()->create([]) : $splitRegs[0];
}
$regPayload = [
'stabile_id' => $stabileId,
'gestione_id' => (Schema::hasColumn('contabilita_registrazioni', 'gestione_id')
? Registrazione::resolveGestioneIdOrDefault($gestioneId, $stabileId, $data)
: null),
'data_registrazione' => $data,
'descrizione' => $descr,
'created_by' => (int) $user->id,
'updated_by' => (int) $user->id,
];
if (Schema::hasColumn('contabilita_registrazioni', 'user_id')) {
$regPayload['user_id'] = (int) $user->id;
}
/** @var Registrazione $reg */
$reg = Registrazione::query()->create($regPayload);
// Dare: diminuisce il debito verso fornitore
Movimento::query()->create([
'registrazione_id' => (int) $reg->id,
'conto_id' => (int) $contoDebitoFornitore->id,
'tipo' => 'dare',
'importo' => $amount,
]);
// Avere: diminuisce la banca/cassa
Movimento::query()->create([
'registrazione_id' => (int) $reg->id,
'conto_id' => $contoFinanziarioId,
'tipo' => 'avere',
'importo' => $amount,
]);
$reg->assertBilanciata();
$movimento->registrazione_id = (int) $reg->id;
$movimento->gestione_id = $gestioneId;
if (Schema::hasColumn('contabilita_movimenti_banca', 'fornitore_id')) {
$movimento->fornitore_id = $fornitoreId;
}
$movimento->save();
if ($fattura) {
$fattura->data_pagamento = $fattura->data_pagamento ?: $data;
$fattura->movimento_pagamento_id = (int) $movimento->id;
$fattura->save();
$this->syncRegistroRitenutaDaPagamento($fattura, $gestioneId, $data);
}
return $reg;
});
}
private function syncRegistroRitenutaDaPagamento(FatturaFornitore $fattura, int $gestioneId, string $dataPagamento): void
{
if (! Schema::hasTable('registro_ritenute_acconto')) {
return;
}
$ritenutaImporto = round((float) ($fattura->ritenuta_importo ?? 0), 4);
$ritenutaAliquota = round((float) ($fattura->ritenuta_aliquota ?? 0), 2);
if ($ritenutaImporto <= 0 || $ritenutaAliquota <= 0) {
return;
}
$dataPagamentoCarbon = Carbon::parse($dataPagamento)->startOfDay();
$dataCompetenza = $fattura->data_documento?->copy()
?: $fattura->data_registrazione?->copy()
?: $dataPagamentoCarbon->copy();
$scadenzaVersamento = $dataPagamentoCarbon->copy()->addMonthNoOverflow()->day(16);
$tenantId = '';
if (Schema::hasTable('gestioni_contabili')) {
$gestione = \App\Models\GestioneContabile::query()->find($gestioneId);
$tenantId = is_string($gestione?->tenant_id ?? null) ? trim((string) $gestione->tenant_id) : '';
}
if ($tenantId === '') {
$stabile = Stabile::query()
->with('amministratore:id,codice_amministratore')
->select(['id', 'amministratore_id'])
->find((int) $fattura->stabile_id);
$tenantId = is_string($stabile?->amministratore?->codice_amministratore ?? null)
? trim((string) $stabile->amministratore->codice_amministratore)
: '';
}
$registro = RegistroRitenuteAcconto::query()
->where('gestione_id', $gestioneId)
->where(function ($query) use ($fattura, $dataCompetenza, $ritenutaImporto) {
$query->where('fattura_id', (int) $fattura->id)
->orWhere(function ($sub) use ($fattura, $dataCompetenza, $ritenutaImporto) {
$sub->where('fornitore_id', (int) $fattura->fornitore_id)
->whereDate('data_competenza', $dataCompetenza->toDateString())
->where('importo_ritenuta', $ritenutaImporto);
});
})
->orderByDesc('id')
->first();
if (! $registro) {
$registro = new RegistroRitenuteAcconto();
$registro->gestione_id = $gestioneId;
$registro->numero_progressivo = ((int) (RegistroRitenuteAcconto::query()
->where('gestione_id', $gestioneId)
->lockForUpdate()
->max('numero_progressivo') ?? 0)) + 1;
$registro->stato_versamento = 'da_versare';
$registro->fornitore_id = (int) $fattura->fornitore_id;
$registro->fattura_id = (int) $fattura->id;
}
$registro->tenant_id = $tenantId !== '' ? $tenantId : 'stabile-' . (int) $fattura->stabile_id;
$registro->data_competenza = $dataCompetenza->toDateString();
$registro->data_pagamento_fattura = $dataPagamentoCarbon->toDateString();
$registro->imponibile = round((float) ($fattura->imponibile ?? 0), 4);
$registro->aliquota_ritenuta = $ritenutaAliquota;
$registro->importo_ritenuta = $ritenutaImporto;
$registro->codice_tributo = trim((string) ($fattura->ritenuta_codice_tributo ?? '')) ?: '1040';
$registro->tipo_ritenuta = trim((string) ($fattura->ritenuta_previdenziale_codice ?? '')) ?: 'professionale';
$registro->data_scadenza_versamento = $scadenzaVersamento->toDateString();
$registro->anno_dichiarazione = (int) $dataCompetenza->format('Y');
$registro->save();
}
}