feat: 前端分離+功能api化

1. 新增資產管理CRUD、googleOAuth登入
2. 更改原記帳controller
This commit is contained in:
2026-06-25 14:19:07 +08:00
parent 7bb931c97b
commit 85972f950c
70 changed files with 4754 additions and 1520 deletions

View File

@@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\InvestmentHolding;
use Illuminate\Http\Request;
class InvestmentTransactionController extends Controller
{
public function index(Request $request, string $holdingId)
{
$holding = InvestmentHolding::whereHas('account', function ($q) use ($request) {
$q->where('user_id', $request->user()->id);
})->findOrFail($holdingId);
return response()->json($holding->transactions()->orderBy('traded_at', 'desc')->get());
}
public function store(Request $request, string $holdingId)
{
$holding = InvestmentHolding::whereHas('account', function ($q) use ($request) {
$q->where('user_id', $request->user()->id);
})->findOrFail($holdingId);
$validated = $request->validate([
'action' => 'required|in:buy,sell',
'shares' => 'nullable|numeric|min:0',
'price' => 'nullable|numeric|min:0',
'fee' => 'nullable|numeric|min:0',
'tax' => 'nullable|numeric|min:0',
'traded_at' => 'required|date',
'note' => 'nullable|string',
'amount' => 'nullable|numeric|min:0', // 實際投入金額(基金用)
'nav' => 'nullable|numeric|min:0', // 淨值(基金用)
]);
$transaction = $holding->transactions()->create($validated);
// 更新持倉的 shares 和 avg_cost
$this->recalculateHolding($holding);
return response()->json($transaction, 201);
}
public function destroy(Request $request, string $holdingId, string $id)
{
$holding = InvestmentHolding::whereHas('account', function ($q) use ($request) {
$q->where('user_id', $request->user()->id);
})->findOrFail($holdingId);
$transaction = $holding->transactions()->findOrFail($id);
$transaction->delete();
$this->recalculateHolding($holding);
return response()->json(null, 204);
}
// 每次買賣後重新計算平均成本和持有股數
private function recalculateHolding(InvestmentHolding $holding): void
{
$transactions = $holding->transactions()->orderBy('traded_at')->get();
$totalUnits = 0;
$totalCost = 0;
$presentUnits = 0;
foreach ($transactions as $tx) {
if ($tx->action === 'buy') {
if ($holding->type === 'fund') {
$units = $tx->nav > 0 ? $tx->amount / $tx->nav : 0;
$totalCost += $tx->amount + $tx->fee;
$presentUnits += $units;
$totalUnits += $units;
} else {
$totalCost += $tx->shares * $tx->price + $tx->fee;
$presentUnits += $tx->shares;
$totalUnits += $tx->shares;
}
} else {
if ($holding->type === 'fund') {
$units = $tx->nav > 0 ? $tx->amount / $tx->nav : 0;
$totalUnits -= $units;
} else {
$totalUnits -= $tx->shares;
}
}
}
$holding->update([
'shares' => max(0, $totalUnits), // 總持有股數
'avg_cost' => $presentUnits > 0 ? $totalCost / $presentUnits : 0,
]);
}
}