Reports.php 14 KB

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