'decimal:2', 'is_active' => 'boolean', 'is_cumulative' => 'boolean', ]; protected $appends = [ 'spent_amount', 'remaining_amount', 'usage_percentage', 'is_exceeded', 'period_label', ]; // ============================================ // Relaciones // ============================================ public function user() { return $this->belongsTo(User::class); } public function category() { return $this->belongsTo(Category::class); } public function subcategory() { return $this->belongsTo(Category::class, 'subcategory_id'); } public function costCenter() { return $this->belongsTo(CostCenter::class); } // ============================================ // Accessors // ============================================ public function getSpentAmountAttribute() { // Calcular el gasto real de las transacciones $query = Transaction::where('user_id', $this->user_id) ->where('type', 'debit'); // Definir el rango de fechas según el tipo de período $startDate = Carbon::create($this->year, $this->month, 1); if ($this->is_cumulative) { // Cumulativo: desde el inicio del año hasta el final del período actual $endDate = $this->getPeriodEndDate(); $query->whereYear('effective_date', $this->year) ->whereDate('effective_date', '<=', $endDate); } else { // No cumulativo: solo el período específico switch ($this->period_type) { case 'monthly': $query->whereYear('effective_date', $this->year) ->whereMonth('effective_date', $this->month); break; case 'bimestral': // 2 meses: mes actual + anterior (o siguiente según configuración) $endDate = $startDate->copy()->addMonths(2)->subDay(); $query->whereBetween('effective_date', [$startDate, $endDate]); break; case 'trimestral': // 3 meses $endDate = $startDate->copy()->addMonths(3)->subDay(); $query->whereBetween('effective_date', [$startDate, $endDate]); break; case 'semestral': // 6 meses $endDate = $startDate->copy()->addMonths(6)->subDay(); $query->whereBetween('effective_date', [$startDate, $endDate]); break; case 'yearly': $query->whereYear('effective_date', $this->year); break; } } // Se tem subcategoria específica, usa apenas ela if ($this->subcategory_id) { $query->where('category_id', $this->subcategory_id); } // Se tem apenas categoria, inclui todas as subcategorias elseif ($this->category_id) { $categoryIds = [$this->category_id]; $subcategories = Category::where('parent_id', $this->category_id)->pluck('id')->toArray(); $categoryIds = array_merge($categoryIds, $subcategories); $query->whereIn('category_id', $categoryIds); } // Filtrar por centro de custos se especificado if ($this->cost_center_id) { $query->where('cost_center_id', $this->cost_center_id); } return abs($query->sum('amount')); } /** * Obtém a data final do período */ private function getPeriodEndDate() { $startDate = Carbon::create($this->year, $this->month, 1); switch ($this->period_type) { case 'monthly': return $startDate->endOfMonth(); case 'bimestral': return $startDate->copy()->addMonths(2)->subDay(); case 'trimestral': return $startDate->copy()->addMonths(3)->subDay(); case 'semestral': return $startDate->copy()->addMonths(6)->subDay(); case 'yearly': return $startDate->endOfYear(); default: return $startDate->endOfMonth(); } } public function getRemainingAmountAttribute() { return $this->amount - $this->spent_amount; } public function getUsagePercentageAttribute() { if ($this->amount <= 0) return 0; return round(($this->spent_amount / $this->amount) * 100, 1); } public function getIsExceededAttribute() { return $this->spent_amount > $this->amount; } public function getPeriodLabelAttribute() { $months = [ 1 => 'Enero', 2 => 'Febrero', 3 => 'Marzo', 4 => 'Abril', 5 => 'Mayo', 6 => 'Junio', 7 => 'Julio', 8 => 'Agosto', 9 => 'Septiembre', 10 => 'Octubre', 11 => 'Noviembre', 12 => 'Diciembre' ]; $monthName = $months[$this->month] ?? ''; switch ($this->period_type) { case 'yearly': return $this->year; case 'bimestral': return $monthName . ' ' . $this->year . ' (Bimestral)'; case 'trimestral': return $monthName . ' ' . $this->year . ' (Trimestral)'; case 'semestral': return $monthName . ' ' . $this->year . ' (Semestral)'; default: return $monthName . ' ' . $this->year; } } // ============================================ // Methods // ============================================ public static function copyToNextMonth($userId, $fromYear, $fromMonth) { $nextMonth = $fromMonth === 12 ? 1 : $fromMonth + 1; $nextYear = $fromMonth === 12 ? $fromYear + 1 : $fromYear; $budgets = self::where('user_id', $userId) ->where('year', $fromYear) ->where('month', $fromMonth) ->where('is_active', true) ->get(); foreach ($budgets as $budget) { self::firstOrCreate([ 'user_id' => $userId, 'category_id' => $budget->category_id, 'year' => $nextYear, 'month' => $nextMonth, ], [ 'name' => $budget->name, 'amount' => $budget->amount, 'currency' => $budget->currency, 'period_type' => 'monthly', 'is_active' => true, ]); } } // ============================================ // Scopes // ============================================ public function scopeActive($query) { return $query->where('is_active', true); } public function scopeForPeriod($query, $year, $month = null) { $query->where('year', $year); if ($month) { $query->where('month', $month); } return $query; } public function scopeForUser($query, $userId) { return $query->where('user_id', $userId); } }