Files
finance_app/app/Http/Controllers/Api/AccountController.php
henry yo 85972f950c feat: 前端分離+功能api化
1. 新增資產管理CRUD、googleOAuth登入
2. 更改原記帳controller
2026-06-25 14:19:07 +08:00

123 lines
4.9 KiB
PHP
Raw Permalink 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\Http\Controllers\Controller;
use App\Models\Account;
use Illuminate\Http\Request;
class AccountController extends Controller
{
public function index(Request $request)
{
// 🟢 1. 預先載入 holdings、transactions 還有全新開荒好的 security 字典表!
$accounts = $request->user()->accounts()
->with(['holdings.transactions', 'holdings.security']) // 👈 確保能直接拿到最新價格
->get()
->map(function ($account) {
$data = $account->toArray();
// 🟢 2. 處理股票與基金的成本與估計現值計算
if (in_array($account->type, ['stock', 'fund'])) {
$totalCost = 0;
$totalValue = 0;
foreach ($account->holdings as $holding) {
// 🟢 修正 1直接拿資料庫存好的庫存股數不用再去 transactions 加減計算
$remainingShares = floatval($holding->shares);
if ($remainingShares > 0) {
// 🟢 修正 2拿到最新價格 (如果沒抓到就先給一個暫時的預設值,例如 10方便你 debug 畫面)
$currentPrice = floatval($holding->security?->latest_price ?? 0);
if ($currentPrice == 0) {
// 💡 Debug 觀測:如果還是 0我們先用 avg_cost 頂替,或者去手動確認 securities 表有沒有真的更新到價格
$currentPrice = floatval($holding->avg_cost ?? 0);
}
// 總現值 = 剩餘單位 * 最新價格
$totalValue += ($remainingShares * $currentPrice);
// 總成本:直接拿你畫面上已經有的 avg_cost * shares 算最快最準!
$avgCost = floatval($holding->avg_cost ?? 0);
$totalCost += ($remainingShares * $avgCost);
}
}
// 🟢 捨入並塞入回傳陣列
$data['total_cost'] = round($totalCost); // 持倉成本
$data['total_value'] = round($totalValue); // 目前市值
// 💡 關鍵補強:計算總損益與總報酬率並塞回陣列,消滅前端的 NaN%
$totalProfit = $totalValue - $totalCost;
$totalRate = $totalCost > 0 ? ($totalProfit / $totalCost) * 100 : 0;
// 🚨 請確認這裡的 Key 名稱(例如 total_profit 或是 unrealized_profit
// 要跟你在 Vue 前端 account.xxx 綁定的欄位名稱一模一樣喔!
$data['total_profit'] = round($totalProfit);
$data['total_rate'] = round($totalRate, 2);
}
// 處理信用卡
if ($account->type === 'credit_card') {
$data['unpaid_amount'] = $account->unpaidAmount();
}
return $data;
});
return response()->json($accounts);
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'type' => 'required|in:cash,bank,stock,fund,wallet,credit_card',
'currency' => 'nullable|string|max:10',
'balance' => 'nullable|numeric',
'note' => 'nullable|string',
'billing_day' => 'nullable|integer|min:1|max:31',
'payment_day' => 'nullable|integer|min:1|max:31',
]);
$account = $request->user()->accounts()->create($validated);
return response()->json($account, 201);
}
public function show(Request $request, string $id)
{
$account = $request->user()->accounts()->findOrFail($id);
return response()->json($account);
}
public function update(Request $request, string $id)
{
$account = $request->user()->accounts()->findOrFail($id);
$validated = $request->validate([
'name' => 'sometimes|string|max:255',
'type' => 'sometimes|in:cash,bank,stock,fund,wallet,credit_card',
'currency' => 'nullable|string|max:10',
'balance' => 'nullable|numeric',
'note' => 'nullable|string',
'billing_day' => 'nullable|integer|min:1|max:31',
'payment_day' => 'nullable|integer|min:1|max:31',
]);
$account->update($validated);
return response()->json($account);
}
public function destroy(Request $request, string $id)
{
$account = $request->user()->accounts()->findOrFail($id);
$account->delete();
return response()->json(null, 204);
}
}