webmoney/backend/app/Models/RecurringInstance.php

150 lines
3.7 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class RecurringInstance extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'user_id',
'recurring_template_id',
'occurrence_number',
'due_date',
'planned_amount',
'status',
'transaction_id',
'paid_at',
'paid_amount',
'paid_notes',
];
protected $casts = [
'due_date' => 'date',
'paid_at' => 'datetime',
'planned_amount' => 'decimal:2',
'paid_amount' => 'decimal:2',
'occurrence_number' => 'integer',
];
public const STATUS_PENDING = 'pending';
public const STATUS_PAID = 'paid';
public const STATUS_SKIPPED = 'skipped';
public const STATUS_CANCELLED = 'cancelled';
// ============================================
// Relacionamentos
// ============================================
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function template(): BelongsTo
{
return $this->belongsTo(RecurringTemplate::class, 'recurring_template_id');
}
public function transaction(): BelongsTo
{
return $this->belongsTo(Transaction::class);
}
// ============================================
// Scopes
// ============================================
public function scopePending($query)
{
return $query->where('status', self::STATUS_PENDING);
}
public function scopePaid($query)
{
return $query->where('status', self::STATUS_PAID);
}
public function scopeOverdue($query)
{
return $query->where('status', self::STATUS_PENDING)
->where('due_date', '<', now()->startOfDay());
}
public function scopeDueSoon($query, int $days = 7)
{
return $query->where('status', self::STATUS_PENDING)
->whereBetween('due_date', [
now()->startOfDay(),
now()->addDays($days)->endOfDay()
]);
}
// ============================================
// Métodos de Negócio
// ============================================
/**
* Verifica se está vencida
*/
public function isOverdue(): bool
{
return $this->status === self::STATUS_PENDING
&& $this->due_date->lt(now()->startOfDay());
}
/**
* Verifica se está paga
*/
public function isPaid(): bool
{
return $this->status === self::STATUS_PAID;
}
/**
* Verifica se está pendente
*/
public function isPending(): bool
{
return $this->status === self::STATUS_PENDING;
}
/**
* Retorna a diferença entre valor planejado e pago
*/
public function getDifferenceAttribute(): ?float
{
if ($this->paid_amount === null) {
return null;
}
return (float) $this->paid_amount - (float) $this->planned_amount;
}
/**
* Retorna dias até o vencimento (negativo se vencido)
*/
public function getDaysUntilDueAttribute(): int
{
return (int) now()->startOfDay()->diffInDays($this->due_date, false);
}
/**
* Retorna status formatado
*/
public function getStatusLabelAttribute(): string
{
return match($this->status) {
self::STATUS_PENDING => 'Pendente',
self::STATUS_PAID => 'Pago',
self::STATUS_SKIPPED => 'Pulado',
self::STATUS_CANCELLED => 'Cancelado',
default => $this->status,
};
}
}