make app database

This commit is contained in:
Meghdad
2026-07-22 15:38:08 +03:30
parent 626a1e5373
commit 36b6bc544d
25 changed files with 1059 additions and 9 deletions

View File

@@ -6,7 +6,7 @@ Each loan is assigned an immutable published workflow configuration when it is c
## Project status
The repository is currently in the **blueprint and scaffolding phase**. The Laravel application skeleton and project documentation exist, but the BankFlow models, API routes, workflow engine, PostgreSQL schema, admin configuration endpoints, and Docker image have not yet been implemented.
The repository is currently under implementation. The domain enums, Eloquent models, factories, database schema, and local development seeder are implemented. The API routes, workflow engine, admin configuration endpoints, and Docker image have not yet been implemented.
The approved implementation plan is documented in [Project Blueprint](docs/project-blueprint.md).
@@ -180,12 +180,23 @@ DB_USERNAME=postgres
DB_PASSWORD=
```
After the BankFlow migrations and seeders are implemented, initialize the database with:
Initialize the database and load development sample data with:
```bash
php artisan migrate --seed
```
`DatabaseSeeder` runs `BankFlowDevelopmentSeeder` only in the local environment. It creates:
- an admin user (`admin@bankflow.test` / `password`);
- `PERSONAL` and `BUSINESS` loan types;
- all supported stage definitions;
- one published workflow per loan type with configured rules;
- submitted, approved, rejected, and manual-review loan examples;
- processing histories linked to the exact workflow steps used.
For a clean development rebuild, use `php artisan migrate:fresh --seed`. This command deletes all existing database data.
Build frontend assets when required:
```bash

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Domain\Loan\Enums;
enum LoanStage: string
{
case Validation = 'VALIDATION';
case FraudCheck = 'FRAUD_CHECK';
case GuarantorCheck = 'GUARANTOR_CHECK';
case CreditCheck = 'CREDIT_CHECK';
case ManagerApproval = 'MANAGER_APPROVAL';
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Domain\Loan\Enums;
enum LoanStatus: string
{
case Submitted = 'SUBMITTED';
case InProgress = 'IN_PROGRESS';
case ManualReview = 'MANUAL_REVIEW';
case Approved = 'APPROVED';
case Rejected = 'REJECTED';
public function isTerminal(): bool
{
return match ($this) {
self::ManualReview, self::Approved, self::Rejected => true,
self::Submitted, self::InProgress => false,
};
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Domain\Loan\Enums;
enum StageResultType: string
{
case Pass = 'PASS';
case Fail = 'FAIL';
case ManualReview = 'MANUAL_REVIEW';
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Domain\Loan\Enums;
enum WorkflowConfigurationStatus: string
{
case Draft = 'DRAFT';
case Published = 'PUBLISHED';
case Archived = 'ARCHIVED';
}

73
app/Models/Loan.php Normal file
View File

@@ -0,0 +1,73 @@
<?php
namespace App\Models;
use App\Domain\Loan\Enums\LoanStatus;
use Database\Factories\LoanFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable([
'public_id',
'customer_id',
'loan_type_id',
'workflow_configuration_id',
'current_workflow_configuration_step_id',
'amount',
'phone',
'monthly_income',
'credit_score',
'has_guarantor',
'status',
])]
class Loan extends Model
{
/** @use HasFactory<LoanFactory> */
use HasFactory;
protected $attributes = [
'status' => LoanStatus::Submitted->value,
];
public function getRouteKeyName(): string
{
return 'public_id';
}
public function loanType(): BelongsTo
{
return $this->belongsTo(LoanType::class);
}
public function workflowConfiguration(): BelongsTo
{
return $this->belongsTo(WorkflowConfiguration::class);
}
public function currentStep(): BelongsTo
{
return $this->belongsTo(WorkflowConfigurationStep::class, 'current_workflow_configuration_step_id');
}
public function histories(): HasMany
{
return $this->hasMany(LoanHistory::class);
}
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'amount' => 'integer',
'monthly_income' => 'integer',
'credit_score' => 'integer',
'has_guarantor' => 'boolean',
'status' => LoanStatus::class,
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Models;
use App\Domain\Loan\Enums\LoanStage;
use App\Domain\Loan\Enums\StageResultType;
use Database\Factories\LoanHistoryFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
#[Fillable([
'loan_id',
'workflow_configuration_step_id',
'stage_code',
'rules_snapshot',
'result',
'reason',
'executed_at',
])]
class LoanHistory extends Model
{
/** @use HasFactory<LoanHistoryFactory> */
use HasFactory;
public function loan(): BelongsTo
{
return $this->belongsTo(Loan::class);
}
public function workflowConfigurationStep(): BelongsTo
{
return $this->belongsTo(WorkflowConfigurationStep::class);
}
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'stage_code' => LoanStage::class,
'rules_snapshot' => 'array',
'result' => StageResultType::class,
'executed_at' => 'datetime',
];
}
}

36
app/Models/LoanType.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Database\Factories\LoanTypeFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['code', 'name', 'is_active'])]
class LoanType extends Model
{
/** @use HasFactory<LoanTypeFactory> */
use HasFactory;
public function workflowConfigurations(): HasMany
{
return $this->hasMany(WorkflowConfiguration::class);
}
public function loans(): HasMany
{
return $this->hasMany(Loan::class);
}
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_active' => 'boolean',
];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Database\Factories\StageDefinitionFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['code', 'name', 'is_active'])]
class StageDefinition extends Model
{
/** @use HasFactory<StageDefinitionFactory> */
use HasFactory;
public function workflowConfigurationSteps(): HasMany
{
return $this->hasMany(WorkflowConfigurationStep::class);
}
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_active' => 'boolean',
];
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Models;
use App\Domain\Loan\Enums\WorkflowConfigurationStatus;
use Database\Factories\WorkflowConfigurationFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['loan_type_id', 'name', 'version', 'status', 'published_at', 'created_by'])]
class WorkflowConfiguration extends Model
{
/** @use HasFactory<WorkflowConfigurationFactory> */
use HasFactory;
protected $attributes = [
'status' => WorkflowConfigurationStatus::Draft->value,
];
public function loanType(): BelongsTo
{
return $this->belongsTo(LoanType::class);
}
public function steps(): HasMany
{
return $this->hasMany(WorkflowConfigurationStep::class)->orderBy('position');
}
public function loans(): HasMany
{
return $this->hasMany(Loan::class);
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function isEditable(): bool
{
return $this->status === WorkflowConfigurationStatus::Draft;
}
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'status' => WorkflowConfigurationStatus::class,
'published_at' => 'datetime',
];
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Database\Factories\WorkflowConfigurationStepFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
#[Fillable(['workflow_configuration_id', 'stage_definition_id', 'position', 'rules', 'is_enabled'])]
class WorkflowConfigurationStep extends Model
{
/** @use HasFactory<WorkflowConfigurationStepFactory> */
use HasFactory;
public function workflowConfiguration(): BelongsTo
{
return $this->belongsTo(WorkflowConfiguration::class);
}
public function stageDefinition(): BelongsTo
{
return $this->belongsTo(StageDefinition::class);
}
public function loanHistories(): HasMany
{
return $this->hasMany(LoanHistory::class);
}
/**
* @return array<string, string>
*/
protected function casts(): array
{
return [
'rules' => 'array',
'is_enabled' => 'boolean',
];
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Database\Factories;
use App\Domain\Loan\Enums\LoanStatus;
use App\Domain\Loan\Enums\StageResultType;
use App\Models\Loan;
use App\Models\LoanHistory;
use App\Models\WorkflowConfiguration;
use App\Models\WorkflowConfigurationStep;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Loan>
*/
class LoanFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'public_id' => 'L-'.fake()->unique()->numerify('#####'),
'customer_id' => 'C-'.fake()->unique()->numerify('####'),
'workflow_configuration_id' => WorkflowConfiguration::factory()->published(),
'loan_type_id' => static fn (array $attributes): int => WorkflowConfiguration::query()
->findOrFail($attributes['workflow_configuration_id'])
->loan_type_id,
'current_workflow_configuration_step_id' => null,
'amount' => fake()->numberBetween(1_000_000, 1_000_000_000),
'phone' => '09'.fake()->numerify('#########'),
'monthly_income' => fake()->numberBetween(1_000_000, 100_000_000),
'credit_score' => fake()->numberBetween(0, 1000),
'has_guarantor' => fake()->boolean(),
'status' => LoanStatus::Submitted,
];
}
public function forWorkflow(WorkflowConfiguration $workflowConfiguration): static
{
return $this
->for($workflowConfiguration->loanType, 'loanType')
->for($workflowConfiguration, 'workflowConfiguration');
}
public function atStep(WorkflowConfigurationStep $step): static
{
return $this->forWorkflow($step->workflowConfiguration)->state([
'current_workflow_configuration_step_id' => $step->getKey(),
]);
}
public function withHistory(
WorkflowConfigurationStep $step,
StageResultType $result = StageResultType::Pass,
string $reason = 'SUCCESS',
): static {
return $this->has(
LoanHistory::factory()
->for($step, 'workflowConfigurationStep')
->state([
'stage_code' => $step->stageDefinition->code,
'rules_snapshot' => $step->rules,
'result' => $result,
'reason' => $reason,
]),
'histories',
);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Database\Factories;
use App\Domain\Loan\Enums\StageResultType;
use App\Models\Loan;
use App\Models\LoanHistory;
use App\Models\WorkflowConfigurationStep;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<LoanHistory>
*/
class LoanHistoryFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'loan_id' => Loan::factory(),
'workflow_configuration_step_id' => static function (array $attributes): int {
$loan = Loan::query()->findOrFail($attributes['loan_id']);
return WorkflowConfigurationStep::factory()
->for($loan->workflowConfiguration)
->create()
->getKey();
},
'stage_code' => static fn (array $attributes): string => WorkflowConfigurationStep::query()
->findOrFail($attributes['workflow_configuration_step_id'])
->stageDefinition
->code,
'rules_snapshot' => static fn (array $attributes): array => WorkflowConfigurationStep::query()
->findOrFail($attributes['workflow_configuration_step_id'])
->rules,
'result' => StageResultType::Pass,
'reason' => 'SUCCESS',
'executed_at' => now(),
];
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Database\Factories;
use App\Models\LoanType;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<LoanType>
*/
class LoanTypeFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'code' => fake()->unique()->bothify('TYPE_????'),
'name' => fake()->words(2, true),
'is_active' => true,
];
}
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Database\Factories;
use App\Domain\Loan\Enums\LoanStage;
use App\Models\StageDefinition;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<StageDefinition>
*/
class StageDefinitionFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$stage = fake()->randomElement(LoanStage::cases());
return [
'code' => $stage->value,
'name' => str($stage->name)->headline()->toString(),
'is_active' => true,
];
}
public function forStage(LoanStage $stage): static
{
return $this->state(fn (array $attributes) => [
'code' => $stage->value,
'name' => str($stage->name)->headline()->toString(),
]);
}
public function inactive(): static
{
return $this->state(fn (array $attributes) => [
'is_active' => false,
]);
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Database\Factories;
use App\Domain\Loan\Enums\WorkflowConfigurationStatus;
use App\Models\LoanType;
use App\Models\StageDefinition;
use App\Models\WorkflowConfiguration;
use App\Models\WorkflowConfigurationStep;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<WorkflowConfiguration>
*/
class WorkflowConfigurationFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'loan_type_id' => LoanType::factory(),
'name' => fake()->words(3, true),
'version' => 1,
'status' => WorkflowConfigurationStatus::Draft,
'published_at' => null,
'created_by' => null,
];
}
public function published(): static
{
return $this->state(fn (array $attributes) => [
'status' => WorkflowConfigurationStatus::Published,
'published_at' => now(),
]);
}
public function archived(): static
{
return $this->state(fn (array $attributes) => [
'status' => WorkflowConfigurationStatus::Archived,
'published_at' => fake()->dateTimeBetween('-1 year', '-1 day'),
]);
}
/**
* @param array<string, mixed> $rules
*/
public function withStep(StageDefinition $stageDefinition, int $position, array $rules = []): static
{
return $this->has(
WorkflowConfigurationStep::factory()
->for($stageDefinition, 'stageDefinition')
->state([
'position' => $position,
'rules' => $rules,
]),
'steps',
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Database\Factories;
use App\Models\StageDefinition;
use App\Models\WorkflowConfiguration;
use App\Models\WorkflowConfigurationStep;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<WorkflowConfigurationStep>
*/
class WorkflowConfigurationStepFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'workflow_configuration_id' => WorkflowConfiguration::factory(),
'stage_definition_id' => StageDefinition::factory(),
'position' => fake()->unique()->numberBetween(1, 1000),
'rules' => [],
'is_enabled' => true,
];
}
public function disabled(): static
{
return $this->state(fn (array $attributes) => [
'is_enabled' => false,
]);
}
}

View File

@@ -0,0 +1,30 @@
<?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('loan_types', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->boolean('is_active')->default(true)->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('loan_types');
}
};

View File

@@ -0,0 +1,30 @@
<?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('stage_definitions', function (Blueprint $table) {
$table->id();
$table->string('code')->unique();
$table->string('name');
$table->boolean('is_active')->default(true)->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('stage_definitions');
}
};

View File

@@ -0,0 +1,42 @@
<?php
use App\Domain\Loan\Enums\WorkflowConfigurationStatus;
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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('workflow_configurations', function (Blueprint $table) {
$table->id();
$table->foreignId('loan_type_id')->constrained()->restrictOnDelete();
$table->string('name');
$table->unsignedInteger('version');
$table->string('status')->default(WorkflowConfigurationStatus::Draft->value)->index();
$table->timestamp('published_at')->nullable()->index();
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
$table->timestamps();
$table->unique(['loan_type_id', 'version']);
$table->unique(['id', 'loan_type_id']);
});
if (DB::getDriverName() === 'pgsql') {
DB::statement("CREATE UNIQUE INDEX workflow_configurations_one_published_per_loan_type ON workflow_configurations (loan_type_id) WHERE status = 'PUBLISHED'");
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('workflow_configurations');
}
};

View File

@@ -0,0 +1,36 @@
<?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('workflow_configuration_steps', function (Blueprint $table) {
$table->id();
$table->foreignId('workflow_configuration_id')->constrained()->restrictOnDelete();
$table->foreignId('stage_definition_id')->constrained()->restrictOnDelete();
$table->unsignedInteger('position');
$table->json('rules');
$table->boolean('is_enabled')->default(true);
$table->timestamps();
$table->unique(['workflow_configuration_id', 'position']);
$table->unique(['workflow_configuration_id', 'stage_definition_id']);
$table->unique(['id', 'workflow_configuration_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('workflow_configuration_steps');
}
};

View File

@@ -0,0 +1,49 @@
<?php
use App\Domain\Loan\Enums\LoanStatus;
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('loans', function (Blueprint $table) {
$table->id();
$table->string('public_id')->unique();
$table->string('customer_id')->index();
$table->foreignId('loan_type_id');
$table->foreignId('workflow_configuration_id');
$table->foreignId('current_workflow_configuration_step_id')->nullable();
$table->unsignedBigInteger('amount');
$table->string('phone', 11);
$table->unsignedBigInteger('monthly_income');
$table->unsignedInteger('credit_score');
$table->boolean('has_guarantor');
$table->string('status')->default(LoanStatus::Submitted->value)->index();
$table->timestamps();
$table->foreign(['workflow_configuration_id', 'loan_type_id'])
->references(['id', 'loan_type_id'])
->on('workflow_configurations')
->restrictOnDelete();
$table->foreign(['current_workflow_configuration_step_id', 'workflow_configuration_id'])
->references(['id', 'workflow_configuration_id'])
->on('workflow_configuration_steps')
->restrictOnDelete();
$table->index('current_workflow_configuration_step_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('loans');
}
};

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('loan_histories', function (Blueprint $table) {
$table->id();
$table->foreignId('loan_id')->constrained()->cascadeOnDelete();
$table->foreignId('workflow_configuration_step_id')->constrained()->restrictOnDelete();
$table->string('stage_code');
$table->json('rules_snapshot');
$table->string('result');
$table->string('reason');
$table->timestamp('executed_at')->index();
$table->timestamps();
$table->unique(['loan_id', 'workflow_configuration_step_id']);
$table->index(['loan_id', 'executed_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('loan_histories');
}
};

View File

@@ -0,0 +1,180 @@
<?php
namespace Database\Seeders;
use App\Domain\Loan\Enums\LoanStage;
use App\Domain\Loan\Enums\LoanStatus;
use App\Domain\Loan\Enums\StageResultType;
use App\Models\Loan;
use App\Models\LoanType;
use App\Models\StageDefinition;
use App\Models\User;
use App\Models\WorkflowConfiguration;
use App\Models\WorkflowConfigurationStep;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Seeder;
class BankFlowDevelopmentSeeder extends Seeder
{
use WithoutModelEvents;
public function run(): void
{
if (! app()->environment('local')) {
return;
}
$admin = User::factory()->create([
'name' => 'BankFlow Admin',
'email' => 'admin@bankflow.test',
]);
$personalLoanType = LoanType::factory()->create([
'code' => 'PERSONAL',
'name' => 'Personal Loan',
]);
$businessLoanType = LoanType::factory()->create([
'code' => 'BUSINESS',
'name' => 'Business Loan',
]);
/** @var Collection<int, StageDefinition> $stageDefinitions */
$stageDefinitions = collect(LoanStage::cases())
->mapWithKeys(fn (LoanStage $stage): array => [
$stage->value => StageDefinition::factory()->forStage($stage)->create(),
]);
$personalWorkflow = WorkflowConfiguration::factory()
->for($personalLoanType, 'loanType')
->for($admin, 'creator')
->published()
->withStep($stageDefinitions[LoanStage::Validation->value], 1)
->withStep($stageDefinitions[LoanStage::FraudCheck->value], 2, [
'fraudPrefix' => 'FRAUD',
'manualReviewPrefix' => 'REVIEW',
])
->withStep($stageDefinitions[LoanStage::CreditCheck->value], 3, [
'rejectBelow' => 500,
'manualReviewMin' => 500,
'manualReviewMax' => 649,
'approveFrom' => 650,
])
->withStep($stageDefinitions[LoanStage::ManagerApproval->value], 4, [
'activationThreshold' => 500_000_000,
'incomeMultiplier' => 20,
])
->create([
'name' => 'Personal Loan Workflow',
'version' => 1,
]);
$businessWorkflow = WorkflowConfiguration::factory()
->for($businessLoanType, 'loanType')
->for($admin, 'creator')
->published()
->withStep($stageDefinitions[LoanStage::Validation->value], 1)
->withStep($stageDefinitions[LoanStage::FraudCheck->value], 2, [
'fraudPrefix' => 'FRAUD',
'manualReviewPrefix' => 'REVIEW',
])
->withStep($stageDefinitions[LoanStage::GuarantorCheck->value], 3, [
'guarantorRequired' => true,
])
->withStep($stageDefinitions[LoanStage::CreditCheck->value], 4, [
'rejectBelow' => 500,
'manualReviewMin' => 500,
'manualReviewMax' => 649,
'approveFrom' => 650,
])
->withStep($stageDefinitions[LoanStage::ManagerApproval->value], 5, [
'activationThreshold' => 500_000_000,
'incomeMultiplier' => 20,
])
->create([
'name' => 'Business Loan Workflow',
'version' => 1,
]);
$personalSteps = $this->stepsByCode($personalWorkflow);
$businessSteps = $this->stepsByCode($businessWorkflow);
Loan::factory()
->count(8)
->atStep($personalSteps[LoanStage::Validation->value])
->create();
Loan::factory()
->count(8)
->atStep($businessSteps[LoanStage::Validation->value])
->create();
Loan::factory()
->forWorkflow($personalWorkflow)
->withHistory($personalSteps[LoanStage::Validation->value])
->withHistory($personalSteps[LoanStage::FraudCheck->value])
->withHistory($personalSteps[LoanStage::CreditCheck->value])
->create([
'status' => LoanStatus::Approved,
'current_workflow_configuration_step_id' => null,
'amount' => 400_000_000,
'credit_score' => 720,
]);
Loan::factory()
->forWorkflow($personalWorkflow)
->withHistory($personalSteps[LoanStage::Validation->value])
->withHistory(
$personalSteps[LoanStage::FraudCheck->value],
StageResultType::ManualReview,
'CUSTOMER_REQUIRES_REVIEW',
)
->create([
'customer_id' => 'REVIEW-CUSTOMER',
'status' => LoanStatus::ManualReview,
'current_workflow_configuration_step_id' => null,
]);
Loan::factory()
->forWorkflow($businessWorkflow)
->withHistory($businessSteps[LoanStage::Validation->value])
->withHistory($businessSteps[LoanStage::FraudCheck->value])
->withHistory(
$businessSteps[LoanStage::GuarantorCheck->value],
StageResultType::Fail,
'GUARANTOR_REQUIRED',
)
->create([
'has_guarantor' => false,
'status' => LoanStatus::Rejected,
'current_workflow_configuration_step_id' => null,
]);
Loan::factory()
->forWorkflow($personalWorkflow)
->withHistory($personalSteps[LoanStage::Validation->value])
->withHistory($personalSteps[LoanStage::FraudCheck->value])
->withHistory($personalSteps[LoanStage::CreditCheck->value])
->withHistory($personalSteps[LoanStage::ManagerApproval->value])
->create([
'amount' => 600_000_000,
'monthly_income' => 50_000_000,
'credit_score' => 750,
'status' => LoanStatus::Approved,
'current_workflow_configuration_step_id' => null,
]);
}
/**
* @return Collection<string, WorkflowConfigurationStep>
*/
private function stepsByCode(WorkflowConfiguration $workflowConfiguration): Collection
{
return $workflowConfiguration
->steps()
->with('stageDefinition')
->get()
->keyBy(fn (WorkflowConfigurationStep $step): string => $step->stageDefinition->code);
}
}

View File

@@ -2,7 +2,6 @@
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
@@ -15,11 +14,8 @@ class DatabaseSeeder extends Seeder
*/
public function run(): void
{
// User::factory(10)->create();
User::factory()->create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
if (app()->environment('local')) {
$this->call(BankFlowDevelopmentSeeder::class);
}
}
}