| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613 |
- <?php
- namespace App\Http\Livewire;
- use Livewire\Component;
- use Illuminate\Support\Facades\Auth;
- use Carbon\Carbon;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use App\Models\Course;
- use App\Models\MemberCard;
- class Reports extends Component
- {
- public $type = 'anagrafica';
- public $seasonFilter;
- public $courses = [];
- public $selectedCourse = null;
- public function mount()
- {
- if (Auth::user()->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;
- $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); // Only positive deltas (missing amounts)
- $participants = $monthlyData[$month]['participants'];
- $labels[] = $monthNames[$month];
- $earnedData[] = $earned;
- $totalData[] = $total;
- $participantData[] = $participants;
- // Fix percentage calculation: earned/total * 100 (not delta-based)
- $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')
- ->whereNotNull('expire_date')
- ->whereNotNull('member_id')
- ->where('status', '!=', 'cancelled')
- ->whereRaw('YEAR(expire_date) >= ?', [$startYear])
- ->whereRaw('YEAR(expire_date) <= ?', [$endYear])
- ->get();
- $seasonCounts = [];
- for ($year = $startYear; $year <= $endYear; $year++) {
- $seasonPeriod = ($year - 1) . '-' . $year;
- $seasonCounts[$seasonPeriod] = [];
- }
- 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;
- }
- }
- $seasonLabels = [];
- $memberCountData = [];
- foreach ($seasonCounts as $seasonPeriod => $members) {
- $seasonLabels[] = $seasonPeriod;
- $memberCountData[] = count($members);
- }
- return [
- 'labels' => $seasonLabels,
- 'datasets' => [
- [
- 'label' => 'Membri Tesserati',
- 'data' => $memberCountData,
- 'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
- 'borderColor' => 'rgba(54, 162, 235, 1)',
- 'borderWidth' => 2,
- 'pointBackgroundColor' => 'rgba(54, 162, 235, 1)',
- 'pointRadius' => 4,
- 'tension' => 0.3,
- 'fill' => true
- ]
- ]
- ];
- }
- }
|