mirror of
https://github.com/henry4682/finance_app.git
synced 2026-07-16 00:10:00 +00:00
123 lines
4.9 KiB
PHP
123 lines
4.9 KiB
PHP
<?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);
|
||
}
|
||
}
|