user()->accounts()->findOrFail($accountId); // 🟢 1. 預先載入最新價格字典表關聯,避免 N+1 問題 $holdings = $account->holdings()->with('security')->get(); // 🟢 2. 透過 map 幫每檔持倉加工算出市值與報酬率 $processedHoldings = $holdings->map(function ($holding) { $data = $holding->toArray(); $remainingShares = floatval($holding->shares); $avgCost = floatval($holding->avg_cost); // 預設給 0 或拿平均成本防呆 $currentPrice = floatval($holding->security?->latest_price ?? 0); if ($currentPrice == 0) { $currentPrice = $avgCost; } if ($remainingShares > 0) { // 單一標的總成本 $totalCost = $remainingShares * $avgCost; // 單一標目前市值 $currentValue = $remainingShares * $currentPrice; // 單一標的未實現損益 $profit = $currentValue - $totalCost; // 單一標的報酬率 $profitRate = $totalCost > 0 ? ($profit / $totalCost) * 100 : 0; // 🟢 3. 塞入擴充數據欄位 $data['latest_price'] = $currentPrice; $data['current_value'] = round($currentValue); $data['profit'] = round($profit); $data['profit_rate'] = round($profitRate, 2); } else { // 庫存為 0 的防呆 $data['latest_price'] = $currentPrice; $data['current_value'] = 0; $data['profit'] = 0; $data['profit_rate'] = 0; } return $data; }); // 傳回包含豐富績效數據的持倉清單 return response()->json($processedHoldings); } public function store(Request $request, string $accountId) { $account = $request->user()->accounts()->findOrFail($accountId); $validated = $request->validate([ 'type' => 'required|in:stock,fund', 'symbol' => 'required|string|max:20', 'name' => 'required|string|max:255', ]); $holding = $account->holdings()->create($validated); // 檢查security: 如果之前有資料不create, 如果之前沒有資料create $security = Security::firstOrCreate( ['code' => $validated['symbol']], // 尋找條件 [ 'type' => $validated['type'], 'name' => $validated['name'], 'source' => $request->input('source') ?? 'twse', ] ); // 立即派發更新價格的任務 UpdateAssetPriceJob::dispatch($security->code, $security->type, $security->name); return response()->json($holding, 201); } public function show(Request $request, string $accountId, string $id) { $account = $request->user()->accounts()->findOrFail($accountId); $holding = $account->holdings()->findOrFail($id); return response()->json($holding); } public function update(Request $request, string $accountId, string $id) { $account = $request->user()->accounts()->findOrFail($accountId); $holding = $account->holdings()->findOrFail($id); $validated = $request->validate([ 'name' => 'sometimes|string|max:255', ]); $holding->update($validated); return response()->json($holding); } public function destroy(Request $request, string $accountId, string $id) { $account = $request->user()->accounts()->findOrFail($accountId); $holding = $account->holdings()->findOrFail($id); $holding->delete(); return response()->json(null, 204); } }