'date', 'data_registrazione' => 'date', 'data_documento' => 'date', 'data_verifica' => 'datetime', 'data_conferma' => 'datetime', 'importo_lordo' => 'decimal:2', 'importo_iva' => 'decimal:2', 'importo_ritenute' => 'decimal:2', 'importo_netto' => 'decimal:2', 'dettagli_fiscali' => 'json', 'ripartito' => 'boolean', 'ripartizione_millesimale' => 'json', ]; protected static function boot() { parent::boot(); static::creating(function ($model) { if (!$model->codice_movimento) { $model->codice_movimento = static::generateCodiceMovimento(); } if (!$model->progressivo_anno) { $model->progressivo_anno = static::getNextProgressivo($model->stabile_id); } }); } /** * Genera codice movimento univoco */ public static function generateCodiceMovimento(): string { do { $codice = 'MOV' . sprintf('%09d', rand(100000000, 999999999)); } while (static::where('codice_movimento', $codice)->exists()); return $codice; } /** * Ottiene il prossimo progressivo per l'anno */ public static function getNextProgressivo($stabile_id): int { $anno = date('Y'); $ultimo = static::where('stabile_id', $stabile_id) ->whereYear('data_registrazione', $anno) ->max('progressivo_anno'); return ($ultimo ?? 0) + 1; } /** * Relazioni */ public function stabile(): BelongsTo { return $this->belongsTo(Stabile::class); } public function gestioneContabile(): BelongsTo { return $this->belongsTo(GestioneContabile::class, 'gestione_contabile_id'); } public function esercizioContabile(): BelongsTo { return $this->belongsTo(EsercizioContabile::class, 'esercizio_contabile_id'); } public function fornitore(): BelongsTo { return $this->belongsTo(Fornitore::class); } public function righeContabili(): HasMany { return $this->hasMany(RigaContabile::class, 'movimento_id'); } public function creatoBy(): BelongsTo { return $this->belongsTo(User::class, 'creato_da'); } public function verificatoBy(): BelongsTo { return $this->belongsTo(User::class, 'verificato_da'); } public function confermatoBy(): BelongsTo { return $this->belongsTo(User::class, 'confermato_da'); } /** * Scopes */ public function scopeConfermati($query) { return $query->where('stato_movimento', 'confermato'); } public function scopeByGestione($query, $gestione_id) { return $query->where('gestione_contabile_id', $gestione_id); } public function scopeByPeriodo($query, $data_inizio, $data_fine) { return $query->whereBetween('data_movimento', [$data_inizio, $data_fine]); } /** * Metodi di business logic */ public function verificaQuadratura(): bool { $totaleDare = $this->righeContabili()->where('dare_avere', 'dare')->sum('importo'); $totaleAvere = $this->righeContabili()->where('dare_avere', 'avere')->sum('importo'); return abs($totaleDare - $totaleAvere) < 0.01; // Tolleranza centesimi } public function confermaMovimento($user_id): bool { if (!$this->verificaQuadratura()) { return false; } $this->update([ 'stato_movimento' => 'confermato', 'confermato_da' => $user_id, 'data_conferma' => now(), ]); return true; } public function ripartisciSuMillesimi($tabella_id): void { // Implementare logica di ripartizione automatica $tabella = TabellaMillesimale::find($tabella_id); $dettagli = $tabella->dettagli; $ripartizione = []; foreach ($dettagli as $dettaglio) { $quota = ($this->importo_netto * $dettaglio->millesimi) / 1000; $ripartizione[$dettaglio->unita_immobiliare_id] = [ 'millesimi' => $dettaglio->millesimi, 'importo' => $quota, ]; } $this->update([ 'ripartito' => true, 'ripartizione_millesimale' => $ripartizione, 'tabella_millesimale_utilizzata' => $tabella_id, ]); } /** * Crea automaticamente le righe contabili standard */ public function creaRigheStandard($conto_dare, $conto_avere): void { // Riga in DARE $this->righeContabili()->create([ 'codice_conto' => $conto_dare, 'descrizione_riga' => $this->descrizione, 'dare_avere' => 'dare', 'importo' => $this->importo_netto, ]); // Riga in AVERE $this->righeContabili()->create([ 'codice_conto' => $conto_avere, 'descrizione_riga' => $this->descrizione, 'dare_avere' => 'avere', 'importo' => $this->importo_netto, ]); } }