mirror of
https://github.com/henry4682/finance_app.git
synced 2026-07-16 08:20:00 +00:00
99 lines
3.3 KiB
PHP
99 lines
3.3 KiB
PHP
<?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,
|
|
]);
|
|
}
|
|
}
|