level != env('LEVEL_ADMIN', 0)) return redirect()->to('/reports'); if (isset($_GET["type"])) $this->type = $_GET["type"]; $this->seasonFilter = $this->getCurrentSeason(); $this->courses = $this->getCoursesForSelect(); } public function render() { return view('livewire.reports'); } private function getCurrentSeason() { $now = Carbon::now(); $currentYear = $now->year; if ($now->month >= 9) { return $currentYear . '-' . ($currentYear + 1); } else { return ($currentYear - 1) . '-' . $currentYear; } } public function getAvailableSeasons() { $seasons = []; $currentYear = Carbon::now()->year; $startYear = 2023; $endYear = Carbon::now()->month >= 9 ? $currentYear + 1 : $currentYear; for ($year = $startYear; $year < $endYear; $year++) { $seasons[] = $year . '-' . ($year + 1); } return array_reverse($seasons); } private function parseSeason($season) { $parts = explode('-', $season); return [ 'start_year' => (int)$parts[0], 'end_year' => (int)$parts[1] ]; } private function getSeasonDateRange($season) { $years = $this->parseSeason($season); return [ 'start' => Carbon::create($years['start_year'], 9, 1), 'end' => Carbon::create($years['end_year'], 8, 31) ]; } public function setSelectedCourse($courseId) { $this->selectedCourse = $courseId; Log::info('Selected course set to: ' . $courseId); return $this->getCourseMonthlyEarnings(); } public function getTesseratiData() { $endYear = $this->parseSeason($this->seasonFilter)['end_year']; return self::getMemberCountChartData($endYear); } public function change($type) { $this->type = $type; } public function updateCharts() { $this->courses = $this->getCoursesForSelect(); $this->emit('chartsUpdated'); $this->dispatchBrowserEvent('chartsUpdated'); } public function updateCourseChart() { $this->emit('chartsUpdated'); $this->dispatchBrowserEvent('chartsUpdated'); } public function updatedSeasonFilter() { $this->courses = $this->getCoursesForSelect(); $this->emit('chartsUpdated'); $this->dispatchBrowserEvent('chartsUpdated'); } public function setSeasonFilter($season) { $this->seasonFilter = $season; } public function getMonthlyTotals() { Log::info('=== getMonthlyTotals called ==='); Log::info('Current seasonFilter: ' . $this->seasonFilter); $dateRange = $this->getSeasonDateRange($this->seasonFilter); Log::info('Date range start: ' . $dateRange['start']); Log::info('Date range end: ' . $dateRange['end']); $monthOrder = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8]; $monthNames = ['Set', 'Ott', 'Nov', 'Dic', 'Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago']; $incomeData = array_fill(0, 12, 0); $expenseData = array_fill(0, 12, 0); $incomeRecords = DB::table('records') ->join('records_rows', 'records.id', '=', 'records_rows.record_id') ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]) ->where('records.type', 'IN') ->select(DB::raw('MONTH(records.date) as month_num'), DB::raw('SUM(records_rows.amount) as total')) ->groupBy('month_num') ->get(); $expenseRecords = DB::table('records') ->join('records_rows', 'records.id', '=', 'records_rows.record_id') ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]) ->where('records.type', 'OUT') ->select(DB::raw('MONTH(records.date) as month_num'), DB::raw('SUM(records_rows.amount) as total')) ->groupBy('month_num') ->get(); foreach ($incomeRecords as $record) { $monthIndex = array_search($record->month_num, $monthOrder); if ($monthIndex !== false) { $incomeData[$monthIndex] = $record->total; } } foreach ($expenseRecords as $record) { $monthIndex = array_search($record->month_num, $monthOrder); if ($monthIndex !== false) { $expenseData[$monthIndex] = $record->total; } } Log::info('Income data: ' . json_encode($incomeData)); Log::info('Expense data: ' . json_encode($expenseData)); return [ 'labels' => $monthNames, 'datasets' => [ [ 'label' => 'Entrate', 'data' => $incomeData, 'backgroundColor' => 'rgba(54, 162, 235, 0.5)' ], [ 'label' => 'Uscite', 'data' => $expenseData, 'backgroundColor' => 'rgba(255, 99, 132, 0.5)' ], ] ]; } public function getYearlySummary() { $dateRange = $this->getSeasonDateRange($this->seasonFilter); $totalIncome = DB::table('records') ->join('records_rows', 'records.id', '=', 'records_rows.record_id') ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]) ->where('records.type', 'IN') ->sum('records_rows.amount'); $totalExpenses = DB::table('records') ->join('records_rows', 'records.id', '=', 'records_rows.record_id') ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]) ->where('records.type', 'OUT') ->sum('records_rows.amount'); $delta = $totalIncome - $totalExpenses; return [ 'totalIncome' => $totalIncome, 'totalExpenses' => $totalExpenses, 'delta' => $delta ]; } public function getTopCausalsByAmount($limit = 10) { $dateRange = $this->getSeasonDateRange($this->seasonFilter); $query = DB::table('records_rows') ->join('records', 'records_rows.record_id', '=', 'records.id') ->join('causals', 'records_rows.causal_id', '=', 'causals.id') ->whereBetween('records.date', [$dateRange['start'], $dateRange['end']]); $query->where('records.type', 'IN'); Log::info('Query: ' . $query->toSql()); $causals = $query->select( 'causals.id', 'causals.name', 'causals.parent_id', DB::raw('SUM(records_rows.amount) as total_amount') ) ->groupBy('causals.id', 'causals.name', 'causals.parent_id') ->orderBy('total_amount', 'desc') ->limit($limit) ->get(); Log::info('Causals: ' . json_encode($causals)); $inData = []; foreach ($causals as $causal) { $tempCausal = new \App\Models\Causal(); $tempCausal->id = $causal->id; $tempCausal->name = $causal->name; $tempCausal->parent_id = $causal->parent_id; $treeName = $tempCausal->getTree(); //$displayName = strlen($treeName) > 30 ? substr($treeName, 0, 27) . '...' : $treeName; $displayName = $treeName; $inData[] = [ 'label' => $displayName, 'value' => $causal->total_amount, 'fullName' => $treeName ]; } usort($inData, function ($a, $b) { return $b['value'] <=> $a['value']; }); $inData = array_slice($inData, 0, $limit); return [ 'inLabels' => array_column($inData, 'label'), 'inData' => $inData, 'datasets' => [ [ 'label' => 'Entrate per Causale', 'data' => array_column($inData, 'value'), ] ] ]; } public function getCoursesForSelect() { $seasonYears = $this->parseSeason($this->seasonFilter); Log::info('Getting courses for season: ' . $this->seasonFilter); Log::info('Season years: ' . json_encode($seasonYears)); $courses = Course::with(['level', 'type', 'frequency']) ->where('active', true) ->where(function ($query) use ($seasonYears) { $query->where('year', $this->seasonFilter) ->orWhere('year', 'like', '%' . $seasonYears['start_year'] . '-' . $seasonYears['end_year'] . '%') ->orWhere('year', 'like', '%' . $seasonYears['start_year'] . '%') ->orWhere('year', 'like', '%' . $seasonYears['end_year'] . '%'); }) ->orderBy('name') ->get() ->filter(function ($course) use ($seasonYears) { $courseYear = $course->year; if ($courseYear === $this->seasonFilter) { return true; } if ( str_contains($courseYear, $seasonYears['start_year']) && str_contains($courseYear, $seasonYears['end_year']) ) { return true; } if ($courseYear == $seasonYears['start_year'] || $courseYear == $seasonYears['end_year']) { return true; } return false; }) ->map(function ($course) { $type = null; if (!empty($course->course_type_id)) { $type = \App\Models\CourseType::find($course->course_type_id); } $levelName = is_object($course->level) ? $course->level->name : 'No Level'; $typeName = is_object($type) ? $type->name : 'No Type'; $frequencyName = is_object($course->frequency) ? $course->frequency->name : 'No Frequency'; $year = $course->year ?? ''; return [ 'id' => $course->id, 'name' => $course->name, 'full_name' => "{$course->name} - {$levelName} - {$typeName} - {$frequencyName} ({$year})", 'level_name' => $levelName, 'type_name' => $typeName, 'frequency_name' => $frequencyName, 'year' => $year ]; })->values()->toArray(); Log::info('Found ' . count($courses) . ' courses for season ' . $this->seasonFilter); return $courses; } public function getMonthlyTotalsForSeason($season) { $originalSeason = $this->seasonFilter; $this->seasonFilter = $season; $result = $this->getMonthlyTotals(); $this->seasonFilter = $originalSeason; return $result; } public function getTopCausalsByAmountForSeason($season, $limit = 10) { $originalSeason = $this->seasonFilter; $this->seasonFilter = $season; $result = $this->getTopCausalsByAmount($limit); $this->seasonFilter = $originalSeason; return $result; } public function getTesseratiDataForSeason($season) { $originalSeason = $this->seasonFilter; $this->seasonFilter = $season; $result = $this->getTesseratiData(); $this->seasonFilter = $originalSeason; return $result; } public function updatedSelectedCourse() { Log::info('updatedSelectedCourse called with: ' . $this->selectedCourse); if ($this->selectedCourse) { $this->emit('courseSelected', $this->selectedCourse); Log::info('Event emitted with course ID: ' . $this->selectedCourse); } } public function getCourseData($courseId) { $this->selectedCourse = $courseId; return $this->getCourseMonthlyEarnings($courseId); } public function getCourseMonthlyEarnings($courseId = null) { $courseId = $courseId ?? $this->selectedCourse; Log::info('Getting earnings for course ID: ' . $courseId); if (!$courseId) { return [ 'labels' => [], 'datasets' => [], 'tableData' => [], 'isEmpty' => true, 'message' => 'Seleziona un corso per visualizzare i dati' ]; } $monthOrder = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8]; $monthNames = [ 9 => 'Set', 10 => 'Ott', 11 => 'Nov', 12 => 'Dic', 1 => 'Gen', 2 => 'Feb', 3 => 'Mar', 4 => 'Apr', 5 => 'Mag', 6 => 'Giu', 7 => 'Lug', 8 => 'Ago' ]; $monthlyData = []; foreach ($monthOrder as $i) { $monthlyData[$i] = [ 'earned' => 0, 'total' => 0, 'participants' => 0 ]; } $memberCourses = \App\Models\MemberCourse::where('course_id', $courseId) ->with('member') ->get(); if ($memberCourses->isEmpty()) { return [ 'labels' => [], 'datasets' => [], 'tableData' => [], 'isEmpty' => true, 'message' => 'Nessun dato disponibile per questo corso nella stagione ' . $this->seasonFilter ]; } $hasData = false; foreach ($memberCourses as $memberCourse) { $price = (float)($memberCourse->price ?? 0); if ($memberCourse->months) { $monthsData = json_decode($memberCourse->months, true); if (is_array($monthsData)) { foreach ($monthsData as $monthData) { $month = $monthData['m'] ?? null; $status = $monthData['status'] ?? ''; if ($month !== null && isset($monthlyData[$month])) { $monthlyData[$month]['total'] += $price; $monthlyData[$month]['participants']++; $hasData = true; if ($status === 1) { $monthlyData[$month]['earned'] += $price; } } } } } } if (!$hasData) { return [ 'labels' => [], 'datasets' => [], 'tableData' => [], 'isEmpty' => true, 'message' => 'Nessun pagamento registrato per questo corso nella stagione ' . $this->seasonFilter ]; } $labels = []; $earnedData = []; $totalData = []; $participantData = []; $tableData = []; foreach ($monthOrder as $month) { $earned = round($monthlyData[$month]['earned'], 2); $total = round($monthlyData[$month]['total'], 2); $delta = max(0, $total - $earned); $participants = $monthlyData[$month]['participants']; $labels[] = $monthNames[$month]; $earnedData[] = $earned; $totalData[] = $total; $participantData[] = $participants; $percentage = $total > 0 ? round(($earned / $total) * 100, 1) : 0; $tableData[] = [ 'month' => $monthNames[$month], 'participants' => $participants, 'earned' => $earned, 'total' => $total, 'delta' => $delta, 'percentage' => $percentage ]; } return [ 'labels' => $labels, 'datasets' => [ [ 'label' => 'Pagamenti Effettuati', 'backgroundColor' => 'rgba(16, 185, 129, 0.8)', 'borderColor' => 'rgba(16, 185, 129, 1)', 'borderWidth' => 0, 'borderRadius' => 8, 'borderSkipped' => false, 'data' => $earnedData, 'type' => 'bar', 'order' => 2 ], [ 'label' => 'Pagamenti Attesi', 'backgroundColor' => 'transparent', 'borderColor' => 'rgba(59, 130, 246, 1)', 'borderWidth' => 3, 'pointBackgroundColor' => 'rgba(59, 130, 246, 1)', 'pointBorderColor' => '#ffffff', 'pointBorderWidth' => 3, 'pointRadius' => 7, 'pointHoverRadius' => 9, 'data' => $totalData, 'type' => 'line', 'tension' => 0.3, 'order' => 1, 'participantData' => $participantData ] ], 'tableData' => $tableData, 'isEmpty' => false ]; } public static function getMemberCountChartData($endYear = null, $span = 5) { if ($endYear === null) { $endYear = date('Y'); } $startYear = $endYear - $span + 1; $memberCards = MemberCard::select('member_id', 'expire_date', 'card_id') ->with('card:id,name') ->whereNotNull('expire_date') ->whereNotNull('member_id') ->whereNotNull('card_id') ->where('status', '!=', 'cancelled') ->whereRaw('YEAR(expire_date) >= ?', [$startYear]) ->whereRaw('YEAR(expire_date) <= ?', [$endYear]) ->get(); $cardTypes = $memberCards->pluck('card.name')->unique()->filter()->sort()->values(); $seasonCounts = []; $seasonCardCounts = []; for ($year = $startYear; $year <= $endYear; $year++) { $seasonPeriod = ($year - 1) . '-' . $year; $seasonCounts[$seasonPeriod] = []; $seasonCardCounts[$seasonPeriod] = []; foreach ($cardTypes as $cardType) { $seasonCardCounts[$seasonPeriod][$cardType] = []; } } foreach ($memberCards as $card) { $expireYear = date('Y', strtotime($card->expire_date)); $expireMonth = date('n', strtotime($card->expire_date)); if ($expireMonth >= 9) { $seasonPeriod = $expireYear . '-' . ($expireYear + 1); } else { $seasonPeriod = ($expireYear - 1) . '-' . $expireYear; } if (isset($seasonCounts[$seasonPeriod])) { $seasonCounts[$seasonPeriod][$card->member_id] = true; $cardTypeName = $card->card->name ?? 'Unknown'; if (isset($seasonCardCounts[$seasonPeriod][$cardTypeName])) { $seasonCardCounts[$seasonPeriod][$cardTypeName][$card->member_id] = true; } } } $seasonLabels = []; $memberCountData = []; $cardTypeDatasets = []; $colors = [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 205, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(153, 102, 255, 0.2)', 'rgba(255, 159, 64, 0.2)', 'rgba(199, 199, 199, 0.2)', 'rgba(83, 102, 255, 0.2)', ]; $borderColors = [ 'rgba(255, 99, 132, 1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 205, 86, 1)', 'rgba(75, 192, 192, 1)', 'rgba(153, 102, 255, 1)', 'rgba(255, 159, 64, 1)', 'rgba(199, 199, 199, 1)', 'rgba(83, 102, 255, 1)', ]; foreach ($cardTypes as $index => $cardType) { $cardTypeDatasets[$cardType] = [ 'label' => $cardType, 'data' => [], 'backgroundColor' => $colors[$index % count($colors)], 'borderColor' => $borderColors[$index % count($borderColors)], 'borderWidth' => 2, 'pointBackgroundColor' => $borderColors[$index % count($borderColors)], 'pointRadius' => 4, 'tension' => 0.3, 'fill' => true ]; } foreach ($seasonCounts as $seasonPeriod => $members) { $seasonLabels[] = $seasonPeriod; $memberCountData[] = count($members); foreach ($cardTypes as $cardType) { $cardTypeCount = isset($seasonCardCounts[$seasonPeriod][$cardType]) ? count($seasonCardCounts[$seasonPeriod][$cardType]) : 0; $cardTypeDatasets[$cardType]['data'][] = $cardTypeCount; } } $datasets = [ [ 'label' => 'Totale Membri Tesserati', 'data' => $memberCountData, 'backgroundColor' => 'rgba(54, 162, 235, 0.2)', 'borderColor' => 'rgba(54, 162, 235, 1)', 'borderWidth' => 3, 'pointBackgroundColor' => 'rgba(54, 162, 235, 1)', 'pointRadius' => 6, 'tension' => 0.3, 'fill' => true, 'type' => 'line' ] ]; foreach ($cardTypeDatasets as $dataset) { $datasets[] = $dataset; } return [ 'labels' => $seasonLabels, 'datasets' => $datasets ]; } }