wip
improve import / export flow and clean up relationships
This commit is contained in:
@@ -13,10 +13,10 @@ class DailyChangesSheet implements FromCollection, WithHeadings, WithTitle
|
||||
{
|
||||
return [
|
||||
'Date',
|
||||
'Portfolio',
|
||||
'Portfolio ID',
|
||||
'Total Market Value',
|
||||
'Total Cost Basis',
|
||||
'Total Gain Loss',
|
||||
'Total Gain',
|
||||
'Total Dividends',
|
||||
'Realized Gains',
|
||||
'Annotation'
|
||||
@@ -28,7 +28,7 @@ class DailyChangesSheet implements FromCollection, WithHeadings, WithTitle
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return auth()->user()->daily_changes;
|
||||
return DailyChange::myDailyChanges()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ class PortfoliosSheet implements FromCollection, WithHeadings, WithTitle
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return auth()->user()->portfolios;
|
||||
return Portfolio::myPortfolios()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
namespace App\Exports\Sheets;
|
||||
|
||||
use App\Models\Portfolio;
|
||||
use App\Models\Transaction;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
|
||||
class TransactionsSheet implements FromCollection, WithHeadings, WithTitle
|
||||
{
|
||||
@@ -22,14 +23,7 @@ class TransactionsSheet implements FromCollection, WithHeadings, WithTitle
|
||||
'Split',
|
||||
'Date',
|
||||
'Created',
|
||||
'Updated',
|
||||
'Company Name',
|
||||
'Portfolio Title',
|
||||
'Market Value',
|
||||
'52 Week Low',
|
||||
'52 Week High',
|
||||
'Market Data Refresh Date',
|
||||
'Gain/Loss Dollars'
|
||||
'Updated'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -38,7 +32,7 @@ class TransactionsSheet implements FromCollection, WithHeadings, WithTitle
|
||||
*/
|
||||
public function collection()
|
||||
{
|
||||
return auth()->user()->transactions;
|
||||
return Transaction::myTransactions()->get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Holding;
|
||||
use App\Models\Portfolio;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -15,14 +14,12 @@ class HoldingController extends Controller
|
||||
public function show(Request $request, Portfolio $portfolio, String $symbol)
|
||||
{
|
||||
|
||||
|
||||
$holding = Holding::query()
|
||||
->portfolio($portfolio->id)
|
||||
$holding = $portfolio->holdings()
|
||||
->with(['market_data'])
|
||||
->symbol($symbol)
|
||||
->portfolio($portfolio->id)
|
||||
->first();
|
||||
|
||||
$market_data = $holding->market_data;
|
||||
|
||||
return view('holding.show', compact(['portfolio', 'holding', 'market_data']));
|
||||
return view('holding.show', compact(['portfolio', 'holding']));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,14 @@ class PortfolioController extends Controller
|
||||
*/
|
||||
public function show(Portfolio $portfolio)
|
||||
{
|
||||
$portfolio->load(['transactions', 'holdings']);
|
||||
|
||||
// get portfolio metrics
|
||||
$metrics = cache()->tags(['metrics', 'portfolio', auth()->user()->id, $portfolio->id])->remember(
|
||||
'portfolio-metrics-' . $portfolio->id,
|
||||
60,
|
||||
function () use ($portfolio) {
|
||||
return
|
||||
Holding::query()
|
||||
return Holding::query()
|
||||
->portfolio($portfolio->id)
|
||||
->getPortfolioMetrics()
|
||||
->first();
|
||||
|
||||
@@ -20,8 +20,10 @@ class BackupImport implements WithMultipleSheets
|
||||
{
|
||||
return [
|
||||
'Portfolios' => new PortfoliosSheet,
|
||||
// 'Transactions' => new TransactionsSheet,
|
||||
'Transactions' => new TransactionsSheet,
|
||||
// 'Daily Changes' => new DailyChangesSheet,
|
||||
];
|
||||
|
||||
// Can listen for AfterSheet to run clean up afterwards
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,23 +15,25 @@ class DailyChangesSheet implements ToCollection, WithHeadingRow, SkipsEmptyRows
|
||||
|
||||
public function collection(Collection $dailyChanges)
|
||||
{
|
||||
foreach ($dailyChanges->sortBy('date') as $row) {
|
||||
if ($row['user'] != auth()->user()->id) {
|
||||
foreach ($dailyChanges as $dailyChange) {
|
||||
|
||||
if ($dailyChange['user'] != auth()->user()->id) {
|
||||
|
||||
throw new Exception('Can\'t do that.');
|
||||
}
|
||||
|
||||
DailyChange::updateOrCreate([
|
||||
'date' => $row['date'],
|
||||
'user_id' => $row['user'],
|
||||
'date' => $dailyChange['date'],
|
||||
'portfolio_id' => $dailyChange['portfolio_id'],
|
||||
],[
|
||||
'user_id' => $row['user'],
|
||||
'date' => $row['date'],
|
||||
'total_market_value' => $row['total_market_value'],
|
||||
'total_cost_basis' => $row['total_cost_basis'],
|
||||
'total_gain' => $row['total_gain'],
|
||||
'total_dividends_earned' => $row['total_dividends_earned'],
|
||||
'realized_gains' => $row['realized_gains'],
|
||||
'notes' => $row['notes'],
|
||||
'portfolio_id' => $dailyChange['portfolio_id'],
|
||||
'date' => $dailyChange['date'],
|
||||
'total_market_value' => $dailyChange['total_market_value'],
|
||||
'total_cost_basis' => $dailyChange['total_cost_basis'],
|
||||
'total_gain' => $dailyChange['total_gain'],
|
||||
'total_dividends' => $dailyChange['total_dividends'],
|
||||
'realized_gains' => $dailyChange['realized_gains'],
|
||||
'annotation' => $dailyChange['annotation'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class PortfoliosSheet implements ToCollection, WithHeadingRow, SkipsEmptyRows
|
||||
|
||||
public function collection(Collection $portfolios)
|
||||
{
|
||||
foreach ($portfolios->sortBy('date') as $portfolio) {
|
||||
foreach ($portfolios as $portfolio) {
|
||||
|
||||
auth()->user()->portfolios()
|
||||
->where(['id' => $portfolio['id']])
|
||||
|
||||
@@ -7,28 +7,36 @@ use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||
use Maatwebsite\Excel\Concerns\WithChunkReading;
|
||||
|
||||
class TransactionsSheet implements ToCollection, WithHeadingRow, SkipsEmptyRows
|
||||
class TransactionsSheet implements ToCollection, WithHeadingRow, SkipsEmptyRows, WithChunkReading
|
||||
{
|
||||
// use Importable;
|
||||
|
||||
public function collection(Collection $transactions)
|
||||
{
|
||||
foreach ($transactions->sortBy('date') as $row) {
|
||||
foreach ($transactions as $transaction) {
|
||||
|
||||
Transaction::updateOrCreate([
|
||||
'id' => $row['id'],
|
||||
],[
|
||||
'id' => $row['id'],
|
||||
'symbol' => $row['symbol'],
|
||||
'portfolio_id' => $row['portfolio'],
|
||||
'transaction_type' => $row['transaction'],
|
||||
'quantity' => $row['quantity'],
|
||||
'cost_basis' => $row['cost_basis'] ?? 0,
|
||||
'sale_price' => $row['sale_price'],
|
||||
'split' => $row['split'] ?? null,
|
||||
'date' => $row['date'],
|
||||
]);
|
||||
Transaction::where('id', $transaction['transaction_id'])
|
||||
->firstOr(function () use ($transaction) {
|
||||
|
||||
return Transaction::make()->forceFill([
|
||||
'id' => $transaction['transaction_id'],
|
||||
'symbol' => $transaction['symbol'],
|
||||
'portfolio_id' => $transaction['portfolio_id'],
|
||||
'transaction_type' => $transaction['transaction_type'],
|
||||
'quantity' => $transaction['quantity'],
|
||||
'cost_basis' => $transaction['cost_basis'] ?? 0,
|
||||
'sale_price' => $transaction['sale_price'],
|
||||
'split' => $transaction['split'] ?? null,
|
||||
'date' => $transaction['date'],
|
||||
])->save();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function chunkSize(): int
|
||||
{
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,15 @@ class DailyChange extends Model
|
||||
return $query->where('portfolio_id', $portfolio);
|
||||
}
|
||||
|
||||
public function scopeMyDailyChanges()
|
||||
{
|
||||
return $this->whereHas('portfolio', function ($query) {
|
||||
$query->whereHas('users', function ($query) {
|
||||
$query->where('id', auth()->id());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function portfolio()
|
||||
{
|
||||
return $this->belongsTo(Portfolio::class);
|
||||
|
||||
@@ -56,7 +56,8 @@ class Holding extends Model
|
||||
public function transactions()
|
||||
{
|
||||
return $this->hasMany(Transaction::class, 'symbol', 'symbol')
|
||||
->where('transactions.portfolio_id', $this->portfolio_id);
|
||||
->where('portfolio_id', $this->portfolio_id)
|
||||
->withAggregate('portfolio', 'title');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +142,7 @@ class Holding extends Model
|
||||
|
||||
public function scopeSymbol($query, $symbol)
|
||||
{
|
||||
return $query->where('symbol', $symbol);
|
||||
return $query->where('holdings.symbol', $symbol);
|
||||
}
|
||||
|
||||
public function scopeWithoutWishlists($query) {
|
||||
|
||||
@@ -62,6 +62,13 @@ class Portfolio extends Model
|
||||
return $this->hasMany(DailyChange::class);
|
||||
}
|
||||
|
||||
public function scopeMyPortfolios()
|
||||
{
|
||||
return $this->whereHas('users', function ($query) {
|
||||
$query->where('user_id', auth()->user()->id);
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeWithoutWishlists()
|
||||
{
|
||||
return $this->where(['wishlist' => false]);
|
||||
|
||||
@@ -90,7 +90,6 @@ class Transaction extends Model
|
||||
public function scopeWithMarketData($query)
|
||||
{
|
||||
$query->withAggregate('market_data', 'name')
|
||||
->withAggregate('portfolio', 'title')
|
||||
->withAggregate('market_data', 'market_value')
|
||||
->withAggregate('market_data', 'fifty_two_week_low')
|
||||
->withAggregate('market_data', 'fifty_two_week_high')
|
||||
@@ -108,6 +107,15 @@ class Transaction extends Model
|
||||
return $query->where('symbol', $symbol);
|
||||
}
|
||||
|
||||
public function scopeMyTransactions()
|
||||
{
|
||||
return $this->whereHas('portfolio', function ($query) {
|
||||
$query->whereHas('users', function ($query) {
|
||||
$query->where('id', auth()->id());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function refreshMarketData()
|
||||
{
|
||||
return MarketData::getMarketData($this->attributes['symbol']);
|
||||
|
||||
@@ -72,6 +72,7 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasManyDeep(Transaction::class, ['portfolio_user', Portfolio::class])
|
||||
->withMarketData()
|
||||
->withAggregate('portfolio', 'title')
|
||||
->selectRaw('
|
||||
CASE
|
||||
WHEN transaction_type = \'SELL\'
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
@livewire('manage-transaction-form', [
|
||||
'portfolio' => $portfolio,
|
||||
'symbol' => $market_data->symbol,
|
||||
'symbol' => $holding->market_data->symbol,
|
||||
])
|
||||
|
||||
</x-ib-modal>
|
||||
@@ -16,7 +16,7 @@
|
||||
<x-slot:title>
|
||||
<a href="{{ route('portfolio.show', ['portfolio' => $portfolio->id]) }}" title="{{ __('Portfolio') }}">
|
||||
{{ $portfolio->title }}
|
||||
</a> » <span title="{{ __('Holding') }}">{{ $market_data->symbol }}</span>
|
||||
</a> » <span title="{{ __('Holding') }}">{{ $holding->market_data->symbol }}</span>
|
||||
</x-slot:title>
|
||||
|
||||
<x-ib-flex-spacer />
|
||||
@@ -35,16 +35,16 @@
|
||||
<x-ib-card class="md:col-span-5">
|
||||
<x-slot:title class="pb-2">
|
||||
|
||||
{{ $market_data->symbol }}
|
||||
<span class="text-sm"> {{ $market_data->name }} </span>
|
||||
{{ $holding->market_data->symbol }}
|
||||
<span class="text-sm"> {{ $holding->market_data->name }} </span>
|
||||
</x-slot:title>
|
||||
|
||||
<div class="font-bold text-2xl py-1 flex items-center">
|
||||
{{ Number::currency($market_data->market_value ?? 0) }}
|
||||
{{ Number::currency($holding->market_data->market_value ?? 0) }}
|
||||
|
||||
<x-gain-loss-arrow-badge
|
||||
:cost-basis="$holding->average_cost_basis"
|
||||
:market-value="$market_data->market_value"
|
||||
:market-value="$holding->market_data->market_value"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
<p class="pt-2 text-sm">
|
||||
{{ __('Market Data Age') }}:
|
||||
{{ \Carbon\Carbon::parse($market_data->updated_at)->diffForHumans() }}
|
||||
{{ \Carbon\Carbon::parse($holding->market_data->updated_at)->diffForHumans() }}
|
||||
</p>
|
||||
|
||||
</x-ib-card>
|
||||
@@ -84,26 +84,26 @@
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Forward PE') }}: </span>
|
||||
{{ $market_data->forward_pe }}
|
||||
{{ $holding->market_data->forward_pe }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Trailing PE') }}: </span>
|
||||
{{ $market_data->trailing_pe }}
|
||||
{{ $holding->market_data->trailing_pe }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('Market Cap') }}: </span>
|
||||
${{ Number::forHumans($market_data->market_cap ?? 0) }}
|
||||
${{ Number::forHumans($holding->market_data->market_cap ?? 0) }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-bold">{{ __('52 week') }}: </span>
|
||||
|
||||
<x-fifty-two-week-range
|
||||
:low="$market_data->fifty_two_week_low"
|
||||
:high="$market_data->fifty_two_week_high"
|
||||
:current="$market_data->market_value"
|
||||
:low="$holding->market_data->fifty_two_week_low"
|
||||
:high="$holding->market_data->fifty_two_week_high"
|
||||
:current="$holding->market_data->market_value"
|
||||
/>
|
||||
|
||||
</p>
|
||||
|
||||
Reference in New Issue
Block a user