'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, }; } }