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