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

54
app/Models/Account.php Normal file
View File

@@ -0,0 +1,54 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Account extends Model
{
protected $fillable = [
'user_id',
'name',
'type',
'currency',
'balance',
'note',
'billing_day',
'payment_day',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function holdings()
{
return $this->hasMany(InvestmentHolding::class);
}
public function expenses()
{
return $this->hasMany(Expense::class);
}
public function unpaidAmount()
{
$billingDay = $this->billing_day ?? 1;
$now = now();
// 如果今天還沒到結帳日,帳單週期是上個月結帳日到這個月結帳日
if ($now->day < $billingDay) {
$start = $now->copy()->subMonth()->setDay($billingDay);
$end = $now->copy()->setDay($billingDay)->subDay();
} else {
$start = $now->copy()->setDay($billingDay);
$end = $now->copy()->addMonth()->setDay($billingDay)->subDay();
}
return $this->expenses()
->whereBetween('date', [$start->toDateString(), $end->toDateString()])
->sum('amount');
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AccountRule extends Model
{
//
}

View File

@@ -7,18 +7,18 @@ use Illuminate\Database\Eloquent\Model;
class Expense extends Model
{
protected $fillable = [
'user_id',
'category_id',
'subcategory_id',
'type',
'amount',
'note',
'date',
'invoice_number',
'seller_name',
'item_name',
'external_id',
];
'user_id',
'amount',
'category_id',
'item_name',
'date',
'note',
'seller_name',
'account_id',
'is_amortized',
'period_start',
'period_end',
];
protected $casts = [
'date' => 'date:Y-m-d',
@@ -39,4 +39,9 @@ class Expense extends Model
{
return $this->belongsTo(Category::class, 'subcategory_id');
}
public function account()
{
return $this->belongsTo(Account::class);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvestmentHolding extends Model
{
protected $fillable = [
'account_id',
'type',
'symbol',
'name',
'shares',
'avg_cost',
];
public function account()
{
return $this->belongsTo(Account::class);
}
public function transactions()
{
return $this->hasMany(InvestmentTransaction::class, 'holding_id');
}
public function security()
{
return $this->hasOne(Security::class, 'code', 'symbol');
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class InvestmentTransaction extends Model
{
protected $fillable = [
'holding_id',
'action',
'shares',
'price',
'fee',
'tax',
'traded_at',
'note',
'amount', // 實際投入金額(基金用)
'nav', // 淨值(基金用)
];
protected $casts = [
'traded_at' => 'date',
];
public function holding()
{
return $this->belongsTo(InvestmentHolding::class, 'holding_id');
}
}

33
app/Models/Security.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Security extends Model
{
protected $fillable = [
'code',
'type',
'source',
'name',
'latest_price',
'latest_price_at',
'change_amount',
'change_rate',
'meta',
];
protected $casts = [
'latest_price_at' => 'datetime',
'latest_price' => 'decimal:4',
'change_amount' => 'decimal:4',
'change_rate' => 'decimal:4',
'meta' => 'array',
];
public function historicPrices()
{
return $this->hasMany(SecurityPriceHistories::class, 'security_id', 'id');
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SecurityPriceHistories extends Model
{
protected $fillable = [
'id',
'security_id',
'price_date',
'price',
'change_amount',
'change_rate',
];
protected $casts = [
'price' => 'decimal:4',
'price_date' => 'datetime',
'change_amount' => 'decimal:4',
'change_rate' => 'decimal:4',
];
public function security()
{
return $this->belongsTo(Security::class, 'security_id', 'id');
}
}

View File

@@ -5,12 +5,14 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Models\Account;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable;
use HasFactory, Notifiable, HasApiTokens;
/**
* The attributes that are mass assignable.
@@ -21,6 +23,11 @@ class User extends Authenticatable
'name',
'email',
'password',
'google_id',
'avatar',
'google_access_token',
'google_refresh_token',
'google_token_expires_at',
];
/**
@@ -45,4 +52,9 @@ class User extends Authenticatable
'password' => 'hashed',
];
}
public function accounts()
{
return $this->hasMany(Account::class);
}
}