mirror of
https://github.com/henry4682/finance_app.git
synced 2026-07-16 00:10:00 +00:00
115 lines
4.7 KiB
PHP
115 lines
4.7 KiB
PHP
<?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();
|
||
|
||
// 1. 本月基礎統計 (用一個查詢同時撈出「總金額」與「總筆數」)
|
||
$monthStats = Expense::where('user_id', $userId)
|
||
->whereYear('date', $now->year)
|
||
->whereMonth('date', $now->month)
|
||
->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', $now->year)
|
||
->whereMonth('date', $now->month)
|
||
->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)) {
|
||
$monthlyTrendData[$monthStr] += $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,
|
||
]);
|
||
}
|
||
}
|