Reports.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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. $type = null;
  188. if (!empty($course->course_type_id)) {
  189. $type = \App\Models\CourseType::find($course->course_type_id);
  190. if ($type) {
  191. $typeName = $type->name;
  192. }
  193. }
  194. $levelName = is_object($course->level) ? $course->level->name : 'No Level';
  195. $typeName = is_object($type) ? $type->name : 'No Type';
  196. $frequencyName = is_object($course->frequency) ? $course->frequency->name : 'No Frequency';
  197. $year = $course->year ?? '';
  198. return [
  199. 'id' => $course->id,
  200. 'name' => $course->name,
  201. 'full_name' => "{$course->name} - {$levelName} - {$typeName} - {$frequencyName} ({$year})",
  202. 'level_name' => $levelName,
  203. 'type_name' => $typeName,
  204. 'frequency_name' => $frequencyName,
  205. 'year' => $year
  206. ];
  207. })->toArray();
  208. return $courses;
  209. }
  210. public function getCourseMonthlyEarnings()
  211. {
  212. $courseId = $this->selectedCourse;
  213. Log::info('Getting earnings for course ID: ' . $courseId);
  214. if (empty($courseId)) {
  215. return [
  216. 'labels' => ['Set', 'Ott', 'Nov', 'Dic', 'Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago'],
  217. 'datasets' => [
  218. [
  219. 'label' => 'Pagamenti Effettuati',
  220. 'backgroundColor' => 'rgba(0, 184, 148, 1)',
  221. 'data' => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  222. 'type' => 'bar',
  223. 'order' => 3
  224. ],
  225. [
  226. 'label' => 'Pagamenti Totali',
  227. 'backgroundColor' => 'transparent',
  228. 'borderColor' => 'rgba(48, 51, 107, 1)',
  229. 'borderWidth' => 3,
  230. 'pointBackgroundColor' => 'rgba(48, 51, 107, 1)',
  231. 'pointRadius' => 5,
  232. 'data' => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
  233. 'type' => 'line',
  234. 'tension' => 0.2,
  235. 'order' => 2
  236. ]
  237. ]
  238. ];
  239. }
  240. $monthOrder = [9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8];
  241. $monthlyData = [];
  242. foreach ($monthOrder as $i) {
  243. $monthlyData[$i] = [
  244. 'earned' => 0,
  245. 'total' => 0,
  246. 'participants' => 0
  247. ];
  248. }
  249. $memberCourses = \App\Models\MemberCourse::where('course_id', $courseId)
  250. ->with('member')
  251. ->get();
  252. foreach ($memberCourses as $memberCourse) {
  253. $price = (float)($memberCourse->price ?? 0);
  254. if ($memberCourse->months) {
  255. $monthsData = json_decode($memberCourse->months, true);
  256. if (is_array($monthsData)) {
  257. foreach ($monthsData as $monthData) {
  258. $month = $monthData['m'] ?? null;
  259. $status = $monthData['status'] ?? '';
  260. if ($month !== null && isset($monthlyData[$month])) {
  261. $monthlyData[$month]['total'] += $price;
  262. if ($status === 1) {
  263. $monthlyData[$month]['earned'] += $price;
  264. }
  265. $monthlyData[$month]['participants']++;
  266. }
  267. }
  268. }
  269. }
  270. }
  271. $monthNames = [
  272. 9 => 'Set',
  273. 10 => 'Ott',
  274. 11 => 'Nov',
  275. 12 => 'Dic',
  276. 1 => 'Gen',
  277. 2 => 'Feb',
  278. 3 => 'Mar',
  279. 4 => 'Apr',
  280. 5 => 'Mag',
  281. 6 => 'Giu',
  282. 7 => 'Lug',
  283. 8 => 'Ago',
  284. ];
  285. $labels = [];
  286. $earnedData = [];
  287. $totalData = [];
  288. $participantData = [];
  289. $missingData = [];
  290. foreach ($monthOrder as $month) {
  291. $labels[] = $monthNames[$month];
  292. $earnedData[] = round($monthlyData[$month]['earned'], 2);
  293. $totalData[] = round($monthlyData[$month]['total'], 2);
  294. $participantData[] = $monthlyData[$month]['participants'];
  295. $missingData[] = round($monthlyData[$month]['total'] - $monthlyData[$month]['earned'], 2);
  296. }
  297. return [
  298. 'labels' => $labels,
  299. 'datasets' => [
  300. [
  301. 'label' => 'Pagamenti Effettuati',
  302. 'backgroundColor' => 'rgba(0, 184, 148, 1)',
  303. 'data' => $earnedData,
  304. 'type' => 'bar',
  305. 'order' => 3
  306. ],
  307. [
  308. 'label' => 'Pagamenti Attesi',
  309. 'backgroundColor' => 'transparent',
  310. 'borderColor' => 'rgba(48, 51, 107, 1)',
  311. 'borderWidth' => 3,
  312. 'pointBackgroundColor' => 'rgba(48, 51, 107, 1)',
  313. 'pointRadius' => 5,
  314. 'data' => $totalData,
  315. 'type' => 'line',
  316. 'tension' => 0.2,
  317. 'order' => 2,
  318. 'participants' => $participantData,
  319. 'missing' => $missingData
  320. ]
  321. ]
  322. ];
  323. }
  324. public static function getMemberCountChartData($endYear = null, $span = 5)
  325. {
  326. if ($endYear === null) {
  327. $endYear = date('Y');
  328. }
  329. $startYear = $endYear - $span + 1;
  330. $memberCards = MemberCard::select('member_id', 'expire_date')
  331. ->whereNotNull('expire_date')
  332. ->whereNotNull('member_id')
  333. ->where('status', '!=', 'cancelled')
  334. ->whereRaw('YEAR(expire_date) >= ?', [$startYear])
  335. ->whereRaw('YEAR(expire_date) <= ?', [$endYear])
  336. ->get();
  337. $yearCounts = [];
  338. for ($year = $startYear; $year <= $endYear; $year++) {
  339. $yearPeriod = ($year - 1) . '-' . $year;
  340. $yearCounts[$yearPeriod] = [];
  341. }
  342. foreach ($memberCards as $card) {
  343. $expireYear = date('Y', strtotime($card->expire_date));
  344. $previousYear = $expireYear - 1;
  345. $yearPeriod = $previousYear . '-' . $expireYear;
  346. if (isset($yearCounts[$yearPeriod])) {
  347. $yearCounts[$yearPeriod][$card->member_id] = true;
  348. }
  349. }
  350. $yearLabels = [];
  351. $memberCountData = [];
  352. foreach ($yearCounts as $yearPeriod => $members) {
  353. $yearLabels[] = $yearPeriod;
  354. $memberCountData[] = count($members);
  355. }
  356. return [
  357. 'labels' => $yearLabels,
  358. 'datasets' => [
  359. [
  360. 'label' => 'Membri Tesserati',
  361. 'data' => $memberCountData,
  362. 'backgroundColor' => 'rgba(54, 162, 235, 0.2)',
  363. 'borderColor' => 'rgba(54, 162, 235, 1)',
  364. 'borderWidth' => 2,
  365. 'pointBackgroundColor' => 'rgba(54, 162, 235, 1)',
  366. 'pointRadius' => 4,
  367. 'tension' => 0.3,
  368. 'fill' => true
  369. ]
  370. ]
  371. ];
  372. }
  373. }