31 - Schema: create trackers, rename purchases to entries, add tracker_id to milestones
This commit is contained in:
parent
2f7a248e9f
commit
b66e018b3a
6 changed files with 193 additions and 7 deletions
|
|
@ -11,6 +11,7 @@
|
|||
class Milestone extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'tracker_id',
|
||||
'target',
|
||||
'description',
|
||||
];
|
||||
|
|
|
|||
25
app/Models/Tracker.php
Normal file
25
app/Models/Tracker.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Tracker extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'asset_id',
|
||||
'label',
|
||||
'unit',
|
||||
'price_tracking_enabled',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'price_tracking_enabled' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('trackers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('asset_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('label');
|
||||
$table->string('unit');
|
||||
$table->boolean('price_tracking_enabled')->default(false);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
// Migrate existing users: create one tracker per user from their current asset_id + price_tracking_enabled
|
||||
DB::table('users')->orderBy('id')->each(function (object $user) {
|
||||
DB::table('trackers')->insert([
|
||||
'user_id' => $user->id,
|
||||
'asset_id' => $user->asset_id,
|
||||
'label' => 'Portfolio',
|
||||
'unit' => 'shares',
|
||||
'price_tracking_enabled' => $user->price_tracking_enabled ?? false,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
// Restore asset_id and price_tracking_enabled back onto users before dropping trackers
|
||||
DB::table('trackers')->orderBy('id')->each(function (object $tracker) {
|
||||
DB::table('users')
|
||||
->where('id', $tracker->user_id)
|
||||
->update([
|
||||
'asset_id' => $tracker->asset_id,
|
||||
'price_tracking_enabled' => $tracker->price_tracking_enabled,
|
||||
]);
|
||||
});
|
||||
|
||||
Schema::dropIfExists('trackers');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
// Rename table
|
||||
Schema::rename('purchases', 'entries');
|
||||
|
||||
Schema::table('entries', function (Blueprint $table) {
|
||||
// Rename columns
|
||||
$table->renameColumn('shares', 'quantity');
|
||||
$table->renameColumn('price_per_share', 'unit_price');
|
||||
|
||||
// Add tracker_id FK (nullable first so we can backfill)
|
||||
$table->foreignId('tracker_id')->nullable()->after('id')->constrained()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
// Backfill tracker_id: assign all entries to the first tracker (single-user app)
|
||||
$trackerId = DB::table('trackers')->value('id');
|
||||
if ($trackerId) {
|
||||
DB::table('entries')->update(['tracker_id' => $trackerId]);
|
||||
}
|
||||
|
||||
// Make tracker_id non-nullable now that it's backfilled
|
||||
Schema::table('entries', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('tracker_id')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('entries', function (Blueprint $table) {
|
||||
$table->dropForeign(['tracker_id']);
|
||||
$table->dropColumn('tracker_id');
|
||||
});
|
||||
|
||||
Schema::table('entries', function (Blueprint $table) {
|
||||
$table->renameColumn('unit_price', 'price_per_share');
|
||||
$table->renameColumn('quantity', 'shares');
|
||||
});
|
||||
|
||||
Schema::rename('entries', 'purchases');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('milestones', function (Blueprint $table) {
|
||||
$table->foreignId('tracker_id')->nullable()->after('id')->constrained()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
// Backfill tracker_id on milestones
|
||||
$trackerId = DB::table('trackers')->value('id');
|
||||
if ($trackerId) {
|
||||
DB::table('milestones')->update(['tracker_id' => $trackerId]);
|
||||
}
|
||||
|
||||
Schema::table('milestones', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('tracker_id')->nullable(false)->change();
|
||||
});
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropForeign(['asset_id']);
|
||||
$table->dropColumn(['asset_id', 'price_tracking_enabled']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->foreignId('asset_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->boolean('price_tracking_enabled')->default(false);
|
||||
});
|
||||
|
||||
Schema::table('milestones', function (Blueprint $table) {
|
||||
$table->dropForeign(['tracker_id']);
|
||||
$table->dropColumn('tracker_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Milestone;
|
||||
use App\Models\Tracker;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
|
@ -10,9 +12,23 @@ class MilestoneTest extends TestCase
|
|||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function tracker(): Tracker
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
|
||||
return Tracker::create([
|
||||
'user_id' => $user->id,
|
||||
'label' => 'Test',
|
||||
'unit' => 'units',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_can_create_milestone(): void
|
||||
{
|
||||
$tracker = $this->tracker();
|
||||
|
||||
$milestone = Milestone::create([
|
||||
'tracker_id' => $tracker->id,
|
||||
'target' => 1500,
|
||||
'description' => 'First milestone',
|
||||
]);
|
||||
|
|
@ -28,9 +44,9 @@ public function test_can_create_milestone(): void
|
|||
|
||||
public function test_can_fetch_milestones_via_api(): void
|
||||
{
|
||||
// Create test milestones
|
||||
Milestone::create(['target' => 1500, 'description' => 'First milestone']);
|
||||
Milestone::create(['target' => 3000, 'description' => 'Second milestone']);
|
||||
$tracker = $this->tracker();
|
||||
Milestone::create(['tracker_id' => $tracker->id, 'target' => 1500, 'description' => 'First milestone']);
|
||||
Milestone::create(['tracker_id' => $tracker->id, 'target' => 3000, 'description' => 'Second milestone']);
|
||||
|
||||
$response = $this->get('/milestones');
|
||||
|
||||
|
|
@ -44,10 +60,10 @@ public function test_can_fetch_milestones_via_api(): void
|
|||
|
||||
public function test_milestones_ordered_by_target(): void
|
||||
{
|
||||
// Create milestones in reverse order
|
||||
Milestone::create(['target' => 3000, 'description' => 'Third']);
|
||||
Milestone::create(['target' => 1000, 'description' => 'First']);
|
||||
Milestone::create(['target' => 2000, 'description' => 'Second']);
|
||||
$tracker = $this->tracker();
|
||||
Milestone::create(['tracker_id' => $tracker->id, 'target' => 3000, 'description' => 'Third']);
|
||||
Milestone::create(['tracker_id' => $tracker->id, 'target' => 1000, 'description' => 'First']);
|
||||
Milestone::create(['tracker_id' => $tracker->id, 'target' => 2000, 'description' => 'Second']);
|
||||
|
||||
$response = $this->get('/milestones');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue