feat: Add financial models and seeders

This commit is contained in:
Víctor Falcón 2025-11-07 15:45:28 +00:00
parent 04230e5d1b
commit 635cde021b
19 changed files with 608 additions and 0 deletions

12
app/AccountType.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Enums;
enum AccountType: string
{
case Checking = 'checking';
case CreditCard = 'credit_card';
case Loan = 'loan';
case Savings = 'savings';
case Others = 'others';
}

12
app/Enums/AccountType.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Enums;
enum AccountType: string
{
case Checking = 'checking';
case CreditCard = 'credit_card';
case Loan = 'loan';
case Savings = 'savings';
case Others = 'others';
}

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

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use App\Enums\AccountType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Account extends Model
{
/** @use HasFactory<\Database\Factories\AccountFactory> */
use HasFactory, SoftDeletes;
protected $fillable = [
'user_id',
'name',
'name_iv',
'bank_id',
'currency_code',
'type',
];
protected function casts(): array
{
return [
'type' => AccountType::class,
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function bank(): BelongsTo
{
return $this->belongsTo(Bank::class);
}
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
}

25
app/Models/Bank.php Normal file
View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Bank extends Model
{
/** @use HasFactory<\Database\Factories\BankFactory> */
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'name_iv',
'logo',
];
public function accounts(): HasMany
{
return $this->hasMany(Account::class);
}
}

32
app/Models/Category.php Normal file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Category extends Model
{
/** @use HasFactory<\Database\Factories\CategoryFactory> */
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'icon',
'color',
'user_id',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Transaction extends Model
{
/** @use HasFactory<\Database\Factories\TransactionFactory> */
use HasFactory;
protected $fillable = [
'user_id',
'account_id',
'category_id',
'description',
'description_iv',
'transaction_date',
'amount',
'currency_code',
'notes',
'notes_iv',
];
protected function casts(): array
{
return [
'transaction_date' => 'date',
'amount' => 'decimal:2',
];
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function account(): BelongsTo
{
return $this->belongsTo(Account::class);
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}

View File

@ -4,6 +4,7 @@ namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@ -56,4 +57,19 @@ class User extends Authenticatable
{
return $this->hasOne(EncryptedMessage::class);
}
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
public function accounts(): HasMany
{
return $this->hasMany(Account::class);
}
public function categories(): HasMany
{
return $this->hasMany(Category::class);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Factories;
use App\Enums\AccountType;
use App\Models\Bank;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Account>
*/
class AccountFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'name' => fake()->words(2, true).' Account',
'name_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'bank_id' => Bank::factory(),
'currency_code' => fake()->randomElement(['USD', 'EUR', 'GBP', 'JPY']),
'type' => fake()->randomElement(AccountType::cases()),
];
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Bank>
*/
class BankFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->company(),
'name_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'logo' => fake()->imageUrl(200, 200, 'business'),
];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Category>
*/
class CategoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->randomElement(['Food', 'Transport', 'Entertainment', 'Shopping', 'Healthcare', 'Utilities', 'Travel', 'Education']),
'icon' => fake()->randomElement(['🍔', '🚗', '🎮', '🛒', '💊', '💡', '✈️', '📚']),
'color' => fake()->hexColor(),
'user_id' => User::factory(),
];
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Database\Factories;
use App\Models\Account;
use App\Models\Category;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Transaction>
*/
class TransactionFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'account_id' => Account::factory(),
'category_id' => Category::factory(),
'description' => fake()->sentence(),
'description_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'transaction_date' => fake()->dateTimeBetween('-1 year', 'now'),
'amount' => fake()->randomFloat(2, -1000, 1000),
'currency_code' => fake()->randomElement(['USD', 'EUR', 'GBP', 'JPY']),
'notes' => fake()->optional()->paragraph(),
'notes_iv' => fake()->optional()->regexify('[A-Za-z0-9]{16}'),
];
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('banks', function (Blueprint $table) {
$table->id();
$table->text('name');
$table->string('name_iv', 16);
$table->string('logo')->nullable();
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('banks');
}
};

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('accounts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->text('name');
$table->string('name_iv', 16);
$table->foreignId('bank_id')->constrained()->onDelete('cascade');
$table->string('currency_code', 3);
$table->string('type');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('accounts');
}
};

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('icon')->nullable();
$table->string('color')->nullable();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('categories');
}
};

View File

@ -0,0 +1,37 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('transactions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('account_id')->constrained()->onDelete('cascade');
$table->foreignId('category_id')->constrained()->onDelete('cascade');
$table->text('description');
$table->string('description_iv', 16);
$table->date('transaction_date');
$table->decimal('amount', 15, 2);
$table->string('currency_code', 3);
$table->text('notes')->nullable();
$table->string('notes_iv', 16)->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('transactions');
}
};

View File

@ -0,0 +1,45 @@
<?php
namespace Database\Seeders;
use App\Enums\AccountType;
use App\Models\Account;
use App\Models\Bank;
use App\Models\User;
use Illuminate\Database\Seeder;
class AccountSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$users = User::all();
$banks = Bank::all();
if ($users->isEmpty() || $banks->isEmpty()) {
return;
}
foreach ($users as $user) {
Account::create([
'user_id' => $user->id,
'name' => 'Main Checking',
'name_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'bank_id' => $banks->random()->id,
'currency_code' => 'USD',
'type' => AccountType::Checking,
]);
Account::create([
'user_id' => $user->id,
'name' => 'Savings Account',
'name_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'bank_id' => $banks->random()->id,
'currency_code' => 'USD',
'type' => AccountType::Savings,
]);
}
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Database\Seeders;
use App\Models\Bank;
use Illuminate\Database\Seeder;
class BankSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$banks = [
['name' => 'Chase', 'logo' => null],
['name' => 'Bank of America', 'logo' => null],
['name' => 'Wells Fargo', 'logo' => null],
['name' => 'Citibank', 'logo' => null],
['name' => 'US Bank', 'logo' => null],
];
foreach ($banks as $bank) {
Bank::create([
'name' => $bank['name'],
'name_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'logo' => $bank['logo'],
]);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Database\Seeders;
use App\Models\Category;
use App\Models\User;
use Illuminate\Database\Seeder;
class CategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$users = User::all();
if ($users->isEmpty()) {
return;
}
$categories = [
['name' => 'Food & Dining', 'icon' => '🍔', 'color' => '#FF6B6B'],
['name' => 'Transportation', 'icon' => '🚗', 'color' => '#4ECDC4'],
['name' => 'Entertainment', 'icon' => '🎮', 'color' => '#95E1D3'],
['name' => 'Shopping', 'icon' => '🛒', 'color' => '#F38181'],
['name' => 'Healthcare', 'icon' => '💊', 'color' => '#AA96DA'],
['name' => 'Utilities', 'icon' => '💡', 'color' => '#FCBAD3'],
['name' => 'Travel', 'icon' => '✈️', 'color' => '#FFFFD2'],
['name' => 'Education', 'icon' => '📚', 'color' => '#A8D8EA'],
];
foreach ($users as $user) {
foreach ($categories as $category) {
Category::create([
'name' => $category['name'],
'icon' => $category['icon'],
'color' => $category['color'],
'user_id' => $user->id,
]);
}
}
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Database\Seeders;
use App\Models\Account;
use App\Models\Category;
use App\Models\Transaction;
use App\Models\User;
use Illuminate\Database\Seeder;
class TransactionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$users = User::all();
$accounts = Account::all();
$categories = Category::all();
if ($users->isEmpty() || $accounts->isEmpty() || $categories->isEmpty()) {
return;
}
foreach ($accounts->take(5) as $account) {
for ($i = 0; $i < 10; $i++) {
Transaction::create([
'user_id' => $users->random()->id,
'account_id' => $account->id,
'category_id' => $categories->random()->id,
'description' => fake()->sentence(),
'description_iv' => fake()->regexify('[A-Za-z0-9]{16}'),
'transaction_date' => fake()->dateTimeBetween('-3 months', 'now'),
'amount' => fake()->randomFloat(2, -500, 500),
'currency_code' => 'USD',
'notes' => fake()->optional(0.3)->paragraph(),
'notes_iv' => fake()->optional(0.3)->regexify('[A-Za-z0-9]{16}'),
]);
}
}
}
}