Reports.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <?php
  2. namespace App\Http\Livewire;
  3. use Livewire\Component;
  4. use Illuminate\Support\Facades\Auth;
  5. use Carbon\Carbon;
  6. use App\Models\Receipt;
  7. use App\Models\ReceiptRow;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. use App\Models\Course;
  11. use App\Models\MemberCard;
  12. class Reports extends Component
  13. {
  14. public $type = 'anagrafica';
  15. public $yearFilter;
  16. public $courses = [];
  17. public $selectedCourse = null;
  18. protected $listeners = ['refreshData' => '$refresh'];
  19. public function mount()
  20. {
  21. if (Auth::user()->level != env('LEVEL_ADMIN', 0))
  22. return redirect()->to('/reports');
  23. if (isset($_GET["type"]))
  24. $this->type = $_GET["type"];
  25. $this->yearFilter = Carbon::now()->year;
  26. $this->courses = $this->getCoursesForSelect();
  27. $this->emit('dataUpdated');
  28. }
  29. public function render()
  30. {
  31. return view('livewire.reports');
  32. }
  33. public function updateCourseChart()
  34. {
  35. $this->emit('courseDataUpdated');
  36. }
  37. public function updatedSelectedCourse($value)
  38. {
  39. Log::info('Selected course changed to: ' . $value);
  40. $this->emit('courseDataUpdated', $value);
  41. }
  42. public function getTesseratiData()
  43. {
  44. $endYear = $this->yearFilter;
  45. return self::getMemberCountChartData($endYear);
  46. }
  47. public function change($type)
  48. {
  49. $this->type = $type;
  50. }
  51. public function setYearFilter($year)
  52. {
  53. $this->yearFilter = $year;
  54. $this->emit('dataUpdated');
  55. }
  56. public function getMonthlyTotals()
  57. {
  58. $year = $this->yearFilter;
  59. $months = range(1, 12);
  60. $monthNames = [];
  61. $incomeData = array_fill(0, 12, 0);
  62. $expenseData = array_fill(0, 12, 0);
  63. foreach ($months as $month) {
  64. $date = Carbon::createFromDate($year, $month, 1);
  65. $monthNames[] = ucfirst($date->locale('it')->monthName);
  66. }
  67. $incomeReceipts = DB::table('receipts')
  68. ->join('receipts_rows', 'receipts.id', '=', 'receipts_rows.receip_id')
  69. ->where('receipts.year', $year)
  70. ->where('receipts.type', 'IN')
  71. ->select(DB::raw('MONTH(receipts.date) as month_num'), DB::raw('SUM(receipts_rows.amount) as total'))
  72. ->groupBy('month_num')
  73. ->get();
  74. $expenseReceipts = DB::table('receipts')
  75. ->join('receipts_rows', 'receipts.id', '=', 'receipts_rows.receip_id')
  76. ->where('receipts.year', $year)
  77. ->where('receipts.type', 'OUT')
  78. ->select(DB::raw('MONTH(receipts.date) as month_num'), DB::raw('SUM(receipts_rows.amount) as total'))
  79. ->groupBy('month_num')
  80. ->get();
  81. foreach ($incomeReceipts as $receipt) {
  82. $index = $receipt->month_num - 1;
  83. if (isset($incomeData[$index])) {
  84. $incomeData[$index] = $receipt->total;
  85. }
  86. }
  87. foreach ($expenseReceipts as $receipt) {
  88. $index = $receipt->month_num - 1;
  89. if (isset($expenseData[$index])) {
  90. $expenseData[$index] = $receipt->total;
  91. }
  92. }
  93. return [
  94. 'labels' => $monthNames,
  95. 'datasets' => [
  96. [
  97. 'label' => 'Entrate',
  98. 'data' => $incomeData,
  99. 'backgroundColor' => 'rgba(54, 162, 235, 0.5)'
  100. ],
  101. [
  102. 'label' => 'Uscite',
  103. 'data' => $expenseData,
  104. 'backgroundColor' => 'rgba(255, 99, 132, 0.5)'
  105. ],
  106. ]
  107. ];
  108. }
  109. public function getYearlySummary()
  110. {
  111. $year = $this->yearFilter;
  112. $totalIncome = DB::table('receipts')
  113. ->join('receipts_rows', 'receipts.id', '=', 'receipts_rows.receip_id')
  114. ->where('receipts.year', $year)
  115. ->where('receipts.type', 'IN')
  116. ->sum('receipts_rows.amount');
  117. $totalExpenses = DB::table('receipts')
  118. ->join('receipts_rows', 'receipts.id', '=', 'receipts_rows.receip_id')
  119. ->where('receipts.year', $year)
  120. ->where('receipts.type', 'OUT')
  121. ->sum('receipts_rows.amount');
  122. $delta = $totalIncome - $totalExpenses;
  123. return [
  124. 'totalIncome' => $totalIncome,
  125. 'totalExpenses' => $totalExpenses,
  126. 'delta' => $delta
  127. ];
  128. }
  129. public function getTopCausalsByAmount($limit = 10)
  130. {
  131. $year = $this->yearFilter;
  132. $query = DB::table('receipts_rows')
  133. ->join('receipts', 'receipts_rows.receip_id', '=', 'receipts.id')
  134. ->join('causals', 'receipts_rows.causal_id', '=', 'causals.id')
  135. ->where('receipts.year', $year);
  136. $query->where('receipts.type', 'IN');
  137. Log::info('Query: ' . $query->toSql());
  138. $causals = $query->select(
  139. 'causals.id',
  140. 'causals.name',
  141. 'causals.parent_id',
  142. DB::raw('SUM(receipts_rows.amount) as total_amount')
  143. )
  144. ->groupBy('causals.id', 'causals.name', 'causals.parent_id')
  145. ->orderBy('total_amount', 'desc')
  146. ->limit($limit)
  147. ->get();
  148. Log::info('Causals: ' . json_encode($causals));
  149. $inData = [];
  150. foreach ($causals as $causal) {
  151. $tempCausal = new \App\Models\Causal();
  152. $tempCausal->id = $causal->id;
  153. $tempCausal->name = $causal->name;
  154. $tempCausal->parent_id = $causal->parent_id;
  155. $treeName = $tempCausal->getTree();
  156. $displayName = strlen($treeName) > 30 ? substr($treeName, 0, 27) . '...' : $treeName;
  157. $inData[] = [
  158. 'label' => $displayName,
  159. 'value' => $causal->total_amount,
  160. 'fullName' => $treeName
  161. ];
  162. }
  163. usort($inData, function ($a, $b) {
  164. return $b['value'] <=> $a['value'];
  165. });
  166. $inData = array_slice($inData, 0, $limit);
  167. return [
  168. 'inLabels' => array_column($inData, 'label'),
  169. 'inData' => $inData,
  170. 'datasets' => [
  171. [
  172. 'label' => 'Entrate per Causale',
  173. 'data' => array_column($inData, 'value'),
  174. ]
  175. ]
  176. ];
  177. }
  178. public function getCoursesForSelect()
  179. {
  180. $currentYear = date('Y');
  181. $courses = Course::with(['level', 'type', 'frequency'])
  182. ->where('active', true)
  183. ->where('year', 'like', '%' . $currentYear . '%')
  184. ->orderBy('name')
  185. ->get()
  186. ->map(function ($course) {
  187. $levelName = $course->level ? $course->level->name : 'No Level';
  188. $typeName = $course->type ? $course->type->name : 'No Type';
  189. $frequencyName = $course->frequency ? $course->frequency->name : 'No Frequency';
  190. $year = $course->year ?? '';
  191. return [
  192. 'id' => $course->id,
  193. 'name' => $course->name,
  194. 'full_name' => "{$course->name} - {$levelName} - {$typeName} - {$frequencyName} ({$year})",
  195. 'level_name' => $levelName,
  196. 'type_name' => $typeName,
  197. 'frequency_name' => $frequencyName,
  198. 'year' => $year
  199. ];
  200. })->toArray();
  201. return $courses;
  202. }
  203. public function getCourseMonthlyEarnings()
  204. {
  205. $courseId = $this->selectedCourse;
  206. Log::info('Getting earnings for course ID: ' . $courseId);
  207. if (empty($courseId)) {
  208. return [
  209. 'labels' => ['Set', 'Ott', 'Nov', 'Dic', 'Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago'],
  210. 'datasets' => [
  211. [
  212. 'label' => 'Pagamenti Effettuati',
  213. 'backgroundColor' => 'rgba(0, 184, 148, 1)',
  214. 'data' => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  215. 'type' => 'bar',
  216. 'order' => 3
  217. ],
  218. [
  219. 'label' => 'Pagamenti Totali',
  220. 'backgroundColor' => 'transparent',
  221. 'borderColor' => 'rgba(48, 51, 107, 1)',
  222. 'borderWidth' => 3,
  223. 'pointBackgroundColor' => 'rgba(48, 51, 107, 1)',
  224. 'pointRadius' => 5,
  225. 'data' => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  226. 'type' => 'line',
  227. 'tension' => 0.2,
  228. 'order' => 2
  229. ]
  230. ]
  231. ];
  232. }
  233. $monthOrder = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8];
  234. $monthlyData = [];
  235. foreach ($monthOrder as $i) {
  236. $monthlyData[$i] = [
  237. 'earned' => 0,
  238. 'total' => 0,
  239. 'participants' => 0
  240. ];
  241. }
  242. $memberCourses = \App\Models\MemberCourse::where('course_id', $courseId)
  243. ->with('member')
  244. ->get();
  245. foreach ($memberCourses as $memberCourse) {
  246. $price = (float)($memberCourse->price ?? 0);
  247. if ($memberCourse->months) {
  248. $monthsData = json_decode($memberCourse->months, true);
  249. if (is_array($monthsData)) {
  250. foreach ($monthsData as $monthData) {
  251. $month = $monthData['m'] ?? null;
  252. $status = $monthData['status'] ?? '';
  253. if ($month !== null && isset($monthlyData[$month])) {
  254. $monthlyData[$month]['total'] += $price;
  255. if ($status === 1) {
  256. $monthlyData[$month]['earned'] += $price;
  257. }
  258. $monthlyData[$month]['participants']++;
  259. }
  260. }
  261. }
  262. }
  263. }
  264. $monthNames = [
  265. 9 => 'Set',
  266. 10 => 'Ott',
  267. 11 => 'Nov',
  268. 12 => 'Dic',
  269. 1 => 'Gen',
  270. 2 => 'Feb',
  271. 3 => 'Mar',
  272. 4 => 'Apr',
  273. 5 => 'Mag',
  274. 6 => 'Giu',
  275. 7 => 'Lug',
  276. 8 => 'Ago',
  277. ];
  278. $labels = [];
  279. $earnedData = [];
  280. $totalData = [];
  281. $participantData = [];
  282. $missingData = [];
  283. foreach ($monthOrder as $month) {
  284. $labels[] = $monthNames[$month];
  285. $earnedData[] = round($monthlyData[$month]['earned'], 2);
  286. $totalData[] = round($monthlyData[$month]['total'], 2);
  287. $participantData[] = $monthlyData[$month]['participants'];
  288. $missingData[] = round($monthlyData[$month]['total'] - $monthlyData[$month]['earned'], 2);
  289. }
  290. return [
  291. 'labels' => $labels,
  292. 'datasets' => [
  293. [
  294. 'label' => 'Pagamenti Effettuati',
  295. 'backgroundColor' => 'rgba(0, 184, 148, 1)',
  296. 'data' => $earnedData,
  297. 'type' => 'bar',
  298. 'order' => 3
  299. ],
  300. [
  301. 'label' => 'Pagamenti Attesi',
  302. 'backgroundColor' => 'transparent',
  303. 'borderColor' => 'rgba(48, 51, 107, 1)',
  304. 'borderWidth' => 3,
  305. 'pointBackgroundColor' => 'rgba(48, 51, 107, 1)',
  306. 'pointRadius' => 5,
  307. 'data' => $totalData,
  308. 'type' => 'line',
  309. 'tension' => 0.2,
  310. 'order' => 2,
  311. 'participants' => $participantData,
  312. 'missing' => $missingData
  313. ]
  314. ]
  315. ];
  316. }
  317. public static function getMemberCountChartData($endYear = null, $span = 5)
  318. {
  319. if ($endYear === null) {
  320. $endYear = date('Y');
  321. }
  322. $startYear = $endYear - $span + 1;
  323. $memberCards = MemberCard::select('member_id', 'expire_date')
  324. ->whereNotNull('expire_date')
  325. ->whereNotNull('member_id')
  326. ->where('status', '!=', 'cancelled')
  327. ->whereRaw('YEAR(expire_date) >= ?', [$startYear])
  328. ->whereRaw('YEAR(expire_date) <= ?', [$endYear])
  329. ->get();
  330. $yearCounts = [];
  331. for ($year = $startYear; $year <= $endYear; $year++) {
  332. $yearPeriod = ($year - 1) . '-' . $year;
  333. $yearCounts[$yearPeriod] = [];
  334. }
  335. foreach ($memberCards as $card) {
  336. $expireYear = date('Y', strtotime($card->expire_date));
  337. $previousYear = $expireYear - 1;
  338. $yearPeriod = $previousYear . '-' . $expireYear;
  339. if (isset($yearCounts[$yearPeriod])) {
  340. $yearCounts[$yearPeriod][$card->member_id] = true;
  341. }
  342. }
  343. $yearLabels = [];
  344. $memberCountData = [];
  345. foreach ($yearCounts as $yearPeriod => $members) {
  346. $yearLabels[] = $yearPeriod;
  347. $memberCountData[] = count($members);
  348. }
  349. return [
  350. 'labels' => $yearLabels,
  351. 'datasets' => [
  352. [
  353. 'label' => 'Membri Tesserati',
  354. 'data' => $memberCountData,
  355. 'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
  356. 'borderColor' => 'rgba(54, 162, 235, 1)',
  357. 'borderWidth' => 2,
  358. 'pointBackgroundColor' => 'rgba(54, 162, 235, 1)',
  359. 'pointRadius' => 4,
  360. 'tension' => 0.3,
  361. 'fill' => true
  362. ]
  363. ]
  364. ];
  365. }
  366. }