Files
finance_app/app/Http/Controllers/Api/DashboardController.php
henry yo 1d9ae792fa
All checks were successful
Laravel-Oracle-Deploy / redeploy (push) Successful in 1m10s
fix: add md & fix dateformat & add note
2026-07-03 02:00:01 +08:00

136 lines
5.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Api;
use App\Models\Expense;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\DB;
class DashboardController extends Controller
{
public function index()
{
$userId = Auth::id();
$now = Carbon::now();
$latestMonth = $now->copy();
$latestYear = $latestMonth->year;
// 1. 本月基礎統計 (用一個查詢同時撈出「總金額」與「總筆數」)
$monthStats = Expense::where('user_id', $userId)
->whereYear('date', $latestYear)
->whereMonth('date', $latestMonth)
->selectRaw('SUM(amount) as total_amount, COUNT(id) as total_count')
->first();
if (!$monthStats || $monthStats->total_amount == 0) {
// 改用 first() 確保能抓到單筆資料
$latestExpense = Expense::where('user_id', $userId)->latest('date')->first();
if ($latestExpense) {
$targetDate = Carbon::parse($latestExpense->date);
$latestYear = $targetDate->year;
$latestMonth = $targetDate->month;
// 重新撈取該月份的統計資料
$monthStats = Expense::where('user_id', $userId)
->whereYear('date', $latestYear)
->whereMonth('date', $latestMonth)
->selectRaw('SUM(amount) as total_amount, COUNT(id) as total_count')
->first();
}
}
$monthlyTotal = (float) ($monthStats->total_amount ?? 0);
$monthlyCount = (int) ($monthStats->total_count ?? 0);
// 2. 本月分類統計(圓餅圖)- 保持集合操作即可
$categoryStats = Expense::with('category')
->where('user_id', $userId)
->whereYear('date', $latestYear)
->whereMonth('date', $latestMonth)
->get()
->groupBy(fn($e) => $e->category?->name ?? '未分類')
->map(fn($group, $name) => [
'name' => $name,
'total' => (float) $group->sum('amount'),
'color' => $group->first()->category?->color ?? '#6B7280',
])
->values();
// 3. 近 6 個月趨勢優化:一次性撈出半年內的所有資料,避免 6 次 SQL 查詢
$sixMonthsAgo = $now->copy()->subMonths(5)->startOfMonth();
$endOfCurrentMonth = $now->copy()->endOfMonth();
// 建立近 6 個月的初始月份清單(例如 ['2026/01' => 0.0, '2026/02' => 0.0 ... ]
$monthlyTrendData = collect(range(5, 0))->mapWithKeys(function ($i) use ($now) {
$monthStr = $now->copy()->subMonths($i)->format('Y/m');
return [$monthStr => 0.0];
})->toArray();
// ➔ (A) 撈取一般消費 (未開啟分攤)
$isPostgres = DB::connection()->getDriverName() === 'pgsql';
$dateGroupRaw = $isPostgres ? "to_char(date, 'YYYY/MM')" : "DATE_FORMAT(date, '%Y/%m')";
$normalExpenses = Expense::where('user_id', $userId)
->where('is_amortized', false)
->where('date', '>=', $sixMonthsAgo)
->selectRaw("{$dateGroupRaw} as month, SUM(amount) as total")
->groupBy('month')
->get();
foreach ($normalExpenses as $expense) {
if (array_key_exists($expense->month, $monthlyTrendData)) {
$monthlyTrendData[$expense->month] += (float) $expense->total;
}
}
// ➔ (B) 智慧分攤消費 (處理跨月、跨年、半年繳大魔王 🎯)
// 撈出所有歸屬期間有交集到這 6 個月範圍內的分攤費用
$amortizedExpenses = Expense::where('user_id', $userId)
->where('is_amortized', true)
->where(function ($query) use ($sixMonthsAgo, $endOfCurrentMonth) {
$query->where('period_start', '<=', $endOfCurrentMonth)
->where('period_end', '>=', $sixMonthsAgo);
})
->get();
foreach ($amortizedExpenses as $expense) {
$start = Carbon::parse($expense->period_start)->startOfMonth();
$end = Carbon::parse($expense->period_end)->endOfMonth();
// 計算這筆費用的總跨月份數 (至少為 1 個月)
$totalMonths = max(1, $start->diffInMonths($end) + 1);
// 算出每月平均攤提金額
$monthlyShare = (float) $expense->amount / $totalMonths;
// 跑這 6 個月的迴圈,看有沒有落在這筆費用的歸屬區間內
foreach ($monthlyTrendData as $monthStr => &$currentTotal) {
$currentMonthObj = Carbon::createFromFormat('Y/m', $monthStr)->startOfMonth();
// 如果圖表上的這個月份,剛好在費用的歸屬起訖之內,就把每月平均金額灌進去!
if ($currentMonthObj->betweenIncluded($start, $end)) {
$currentTotal += $monthlyShare;
}
}
}
// 轉回前端圖表要的格式
$monthlyTrend = collect($monthlyTrendData)->map(function ($total, $monthStr) {
return [
'month' => $monthStr,
'total' => round($total, 2), // 四捨五入到小數點兩位
];
})->values();
return response()->json([
'monthlyTotal' => $monthlyTotal,
'monthlyCount' => $monthlyCount,
'categoryStats' => $categoryStats,
'monthlyTrend' => $monthlyTrend,
]);
}
}