From 75fbd60a545208b9387d453ae0b334ae8388f048 Mon Sep 17 00:00:00 2001 From: hackerESQ Date: Mon, 9 Sep 2024 19:39:38 -0500 Subject: [PATCH] fix splits command and add splits test --- app/Models/Split.php | 46 +++++++++++-------- app/Models/Transaction.php | 4 +- database/factories/TransactionFactory.php | 7 +++ .../livewire/transactions-table.blade.php | 4 +- tests/DashboardTest.php | 12 ++--- tests/SplitsTest.php | 39 +++++++++++++++- 6 files changed, 80 insertions(+), 32 deletions(-) diff --git a/app/Models/Split.php b/app/Models/Split.php index 27f64d4..092cf07 100644 --- a/app/Models/Split.php +++ b/app/Models/Split.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Models\Transaction; +use Illuminate\Support\Str; use Illuminate\Support\Facades\DB; use Illuminate\Database\Eloquent\Model; use App\Interfaces\MarketData\MarketDataInterface; @@ -68,8 +69,12 @@ class Split extends Model } if ($split_data->isNotEmpty()) { + // insert records - (new self)->insert($split_data->toArray()); + (new self)->insert($split_data->map(function($split) { + + return [...$split, ...['id' => Str::uuid()->toString()]]; + })->toArray()); } // sync to transactions @@ -84,31 +89,34 @@ class Split extends Model */ public static function syncToTransactions($symbol) { - // get relevant split data - $splits = self::where([ - 'splits.symbol' => $symbol, - ]) - ->whereDate('transactions.date', '>', DB::raw('IFNULL(holdings.splits_synced_at, "0000-00-00")')) - ->select([ - 'splits.date', - 'splits.symbol', - 'splits.split_amount', - 'transactions.portfolio_id' - ]) - ->join('transactions', 'transactions.symbol', 'splits.symbol') - ->join('holdings', 'transactions.symbol', 'holdings.symbol') - ->orderBy('splits.date', 'ASC') - ->get(); + // get splits joined with matching holdings + $splits = self::select([ + 'splits.date', + 'splits.symbol', + 'splits.split_amount', + 'holdings.portfolio_id' + ]) + ->where([ + 'splits.symbol' => $symbol, + ]) + ->whereDate('splits.date', '>', DB::raw('IFNULL(holdings.splits_synced_at, "0000-00-00")')) + ->where('holdings.quantity', '>', 0) + ->join('holdings', 'splits.symbol', 'holdings.symbol') + ->orderBy('splits.date', 'ASC') + ->get(); foreach($splits as $split) { + // get qty owned when split was issued $qty_owned = Transaction::where([ 'symbol' => $split->symbol, 'portfolio_id' => $split->portfolio_id ]) ->whereDate('transactions.date', '<', $split->date->format('Y-m-d')) - ->sum('quantity'); - + ->selectRaw('SUM(CASE WHEN transaction_type = "BUY" THEN quantity ELSE 0 END) - + SUM(CASE WHEN transaction_type = "SELL" THEN quantity ELSE 0 END) AS qty_owned') + ->value('qty_owned'); + if ($qty_owned > 0) { Transaction::create([ @@ -132,6 +140,4 @@ class Split extends Model } } } - - } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 77af6cc..d03918f 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -21,7 +21,8 @@ class Transaction extends Model 'transaction_type', 'quantity', 'cost_basis', - 'sale_price' + 'sale_price', + 'split', ]; protected $hidden = []; @@ -185,6 +186,7 @@ class Transaction extends Model 'quantity' => $this->quantity, 'average_cost_basis' => $this->cost_basis, 'total_cost_basis' => $this->quantity * $this->cost_basis, + 'splits_synced_at' => now(), ])->syncTransactionsAndDividends(); } } \ No newline at end of file diff --git a/database/factories/TransactionFactory.php b/database/factories/TransactionFactory.php index 27c8b2f..da972c0 100644 --- a/database/factories/TransactionFactory.php +++ b/database/factories/TransactionFactory.php @@ -36,6 +36,13 @@ class TransactionFactory extends Factory ]; } + public function yearsAgo(): static + { + return $this->state(fn (array $attributes) => [ + 'date' => $this->faker->date('Y-m-d', '-3 years'), + ]); + } + public function symbol($symbol): static { return $this->state(fn (array $attributes) => [ diff --git a/resources/views/livewire/transactions-table.blade.php b/resources/views/livewire/transactions-table.blade.php index 31da1e4..1862f46 100644 --- a/resources/views/livewire/transactions-table.blade.php +++ b/resources/views/livewire/transactions-table.blade.php @@ -92,7 +92,9 @@ new class extends Component { @endscope @scope('cell_transaction_type', $row) create(); - $this->actingAs($user); + $this->actingAs($user = User::factory()->create()); Portfolio::factory(5)->create(); @@ -29,8 +28,7 @@ class DashboardTest extends TestCase */ public function test_user_has_transactions(): void { - $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user = User::factory()->create()); Transaction::factory(10)->create(); @@ -41,8 +39,7 @@ class DashboardTest extends TestCase */ public function test_user_has_holdings(): void { - $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user = User::factory()->create()); $portfolio = Portfolio::factory()->create(); @@ -55,8 +52,7 @@ class DashboardTest extends TestCase */ public function test_user_has_dashboard_metrics(): void { - $user = User::factory()->create(); - $this->actingAs($user); + $this->actingAs($user = User::factory()->create()); $portfolio = Portfolio::factory()->create(); diff --git a/tests/SplitsTest.php b/tests/SplitsTest.php index d46732f..ba1b5eb 100644 --- a/tests/SplitsTest.php +++ b/tests/SplitsTest.php @@ -3,6 +3,11 @@ namespace Tests; use Tests\TestCase; +use App\Models\User; +use App\Models\Split; +use App\Models\Holding; +use App\Models\Portfolio; +use App\Models\Transaction; use Illuminate\Foundation\Testing\RefreshDatabase; class SplitsTest extends TestCase @@ -11,8 +16,38 @@ class SplitsTest extends TestCase /** */ - public function test_user_has_dashboard_metrics(): void + public function test_splits_create_new_transaction(): void { - // + $this->actingAs($user = User::factory()->create()); + + $portfolio = Portfolio::factory()->create(); + Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create(); + + // manually reset the split last sync date (which is set when the holding is created) + Holding::query()->portfolio($portfolio->id)->symbol('ACME')->update([ + 'splits_synced_at' => null + ]); + + Split::refreshSplitData('ACME'); + + $transactions = Transaction::query()->symbol('ACME')->portfolio($portfolio->id)->get(); + + $this->assertCount(2, $transactions); + } + + /** + */ + public function test_splits_do_not_create_new_transaction_if_already_synced(): void + { + $this->actingAs($user = User::factory()->create()); + + $portfolio = Portfolio::factory()->create(); + Transaction::factory()->buy()->yearsAgo()->portfolio($portfolio->id)->symbol('ACME')->create(); + + Split::refreshSplitData('ACME'); + + $transactions = Transaction::query()->symbol('ACME')->portfolio($portfolio->id)->get(); + + $this->assertCount(1, $transactions); } }