From 36b6bc544de3909e55a620970e11b825b7463a4e Mon Sep 17 00:00:00 2001 From: Meghdad <61308304+MeghdadFadaee@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:38:08 +0330 Subject: [PATCH] make app database --- README.md | 15 +- app/Domain/Loan/Enums/LoanStage.php | 12 ++ app/Domain/Loan/Enums/LoanStatus.php | 20 ++ app/Domain/Loan/Enums/StageResultType.php | 10 + .../Enums/WorkflowConfigurationStatus.php | 10 + app/Models/Loan.php | 73 +++++++ app/Models/LoanHistory.php | 49 +++++ app/Models/LoanType.php | 36 ++++ app/Models/StageDefinition.php | 31 +++ app/Models/WorkflowConfiguration.php | 58 ++++++ app/Models/WorkflowConfigurationStep.php | 43 +++++ database/factories/LoanFactory.php | 73 +++++++ database/factories/LoanHistoryFactory.php | 45 +++++ database/factories/LoanTypeFactory.php | 33 ++++ database/factories/StageDefinitionFactory.php | 44 +++++ .../WorkflowConfigurationFactory.php | 65 +++++++ .../WorkflowConfigurationStepFactory.php | 37 ++++ ...6_07_22_114854_create_loan_types_table.php | 30 +++ ..._114858_create_stage_definitions_table.php | 30 +++ ...1_create_workflow_configurations_table.php | 42 ++++ ...ate_workflow_configuration_steps_table.php | 36 ++++ .../2026_07_22_114910_create_loans_table.php | 49 +++++ ..._22_114915_create_loan_histories_table.php | 37 ++++ .../seeders/BankFlowDevelopmentSeeder.php | 180 ++++++++++++++++++ database/seeders/DatabaseSeeder.php | 10 +- 25 files changed, 1059 insertions(+), 9 deletions(-) create mode 100644 app/Domain/Loan/Enums/LoanStage.php create mode 100644 app/Domain/Loan/Enums/LoanStatus.php create mode 100644 app/Domain/Loan/Enums/StageResultType.php create mode 100644 app/Domain/Loan/Enums/WorkflowConfigurationStatus.php create mode 100644 app/Models/Loan.php create mode 100644 app/Models/LoanHistory.php create mode 100644 app/Models/LoanType.php create mode 100644 app/Models/StageDefinition.php create mode 100644 app/Models/WorkflowConfiguration.php create mode 100644 app/Models/WorkflowConfigurationStep.php create mode 100644 database/factories/LoanFactory.php create mode 100644 database/factories/LoanHistoryFactory.php create mode 100644 database/factories/LoanTypeFactory.php create mode 100644 database/factories/StageDefinitionFactory.php create mode 100644 database/factories/WorkflowConfigurationFactory.php create mode 100644 database/factories/WorkflowConfigurationStepFactory.php create mode 100644 database/migrations/2026_07_22_114854_create_loan_types_table.php create mode 100644 database/migrations/2026_07_22_114858_create_stage_definitions_table.php create mode 100644 database/migrations/2026_07_22_114901_create_workflow_configurations_table.php create mode 100644 database/migrations/2026_07_22_114906_create_workflow_configuration_steps_table.php create mode 100644 database/migrations/2026_07_22_114910_create_loans_table.php create mode 100644 database/migrations/2026_07_22_114915_create_loan_histories_table.php create mode 100644 database/seeders/BankFlowDevelopmentSeeder.php diff --git a/README.md b/README.md index e7f1871..dff9a7b 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/Domain/Loan/Enums/LoanStage.php b/app/Domain/Loan/Enums/LoanStage.php new file mode 100644 index 0000000..f1518ff --- /dev/null +++ b/app/Domain/Loan/Enums/LoanStage.php @@ -0,0 +1,12 @@ + true, + self::Submitted, self::InProgress => false, + }; + } +} diff --git a/app/Domain/Loan/Enums/StageResultType.php b/app/Domain/Loan/Enums/StageResultType.php new file mode 100644 index 0000000..f16ed56 --- /dev/null +++ b/app/Domain/Loan/Enums/StageResultType.php @@ -0,0 +1,10 @@ + */ + 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 + */ + protected function casts(): array + { + return [ + 'amount' => 'integer', + 'monthly_income' => 'integer', + 'credit_score' => 'integer', + 'has_guarantor' => 'boolean', + 'status' => LoanStatus::class, + ]; + } +} diff --git a/app/Models/LoanHistory.php b/app/Models/LoanHistory.php new file mode 100644 index 0000000..b07a88b --- /dev/null +++ b/app/Models/LoanHistory.php @@ -0,0 +1,49 @@ + */ + use HasFactory; + + public function loan(): BelongsTo + { + return $this->belongsTo(Loan::class); + } + + public function workflowConfigurationStep(): BelongsTo + { + return $this->belongsTo(WorkflowConfigurationStep::class); + } + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'stage_code' => LoanStage::class, + 'rules_snapshot' => 'array', + 'result' => StageResultType::class, + 'executed_at' => 'datetime', + ]; + } +} diff --git a/app/Models/LoanType.php b/app/Models/LoanType.php new file mode 100644 index 0000000..3af6c95 --- /dev/null +++ b/app/Models/LoanType.php @@ -0,0 +1,36 @@ + */ + use HasFactory; + + public function workflowConfigurations(): HasMany + { + return $this->hasMany(WorkflowConfiguration::class); + } + + public function loans(): HasMany + { + return $this->hasMany(Loan::class); + } + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'is_active' => 'boolean', + ]; + } +} diff --git a/app/Models/StageDefinition.php b/app/Models/StageDefinition.php new file mode 100644 index 0000000..b21f207 --- /dev/null +++ b/app/Models/StageDefinition.php @@ -0,0 +1,31 @@ + */ + use HasFactory; + + public function workflowConfigurationSteps(): HasMany + { + return $this->hasMany(WorkflowConfigurationStep::class); + } + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'is_active' => 'boolean', + ]; + } +} diff --git a/app/Models/WorkflowConfiguration.php b/app/Models/WorkflowConfiguration.php new file mode 100644 index 0000000..051c9bc --- /dev/null +++ b/app/Models/WorkflowConfiguration.php @@ -0,0 +1,58 @@ + */ + 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 + */ + protected function casts(): array + { + return [ + 'status' => WorkflowConfigurationStatus::class, + 'published_at' => 'datetime', + ]; + } +} diff --git a/app/Models/WorkflowConfigurationStep.php b/app/Models/WorkflowConfigurationStep.php new file mode 100644 index 0000000..e29c02b --- /dev/null +++ b/app/Models/WorkflowConfigurationStep.php @@ -0,0 +1,43 @@ + */ + 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 + */ + protected function casts(): array + { + return [ + 'rules' => 'array', + 'is_enabled' => 'boolean', + ]; + } +} diff --git a/database/factories/LoanFactory.php b/database/factories/LoanFactory.php new file mode 100644 index 0000000..89411be --- /dev/null +++ b/database/factories/LoanFactory.php @@ -0,0 +1,73 @@ + + */ +class LoanFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + 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', + ); + } +} diff --git a/database/factories/LoanHistoryFactory.php b/database/factories/LoanHistoryFactory.php new file mode 100644 index 0000000..d0c4b7d --- /dev/null +++ b/database/factories/LoanHistoryFactory.php @@ -0,0 +1,45 @@ + + */ +class LoanHistoryFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + 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(), + ]; + } +} diff --git a/database/factories/LoanTypeFactory.php b/database/factories/LoanTypeFactory.php new file mode 100644 index 0000000..1426079 --- /dev/null +++ b/database/factories/LoanTypeFactory.php @@ -0,0 +1,33 @@ + + */ +class LoanTypeFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + 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, + ]); + } +} diff --git a/database/factories/StageDefinitionFactory.php b/database/factories/StageDefinitionFactory.php new file mode 100644 index 0000000..5952742 --- /dev/null +++ b/database/factories/StageDefinitionFactory.php @@ -0,0 +1,44 @@ + + */ +class StageDefinitionFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + 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, + ]); + } +} diff --git a/database/factories/WorkflowConfigurationFactory.php b/database/factories/WorkflowConfigurationFactory.php new file mode 100644 index 0000000..eddf6f6 --- /dev/null +++ b/database/factories/WorkflowConfigurationFactory.php @@ -0,0 +1,65 @@ + + */ +class WorkflowConfigurationFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + 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 $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', + ); + } +} diff --git a/database/factories/WorkflowConfigurationStepFactory.php b/database/factories/WorkflowConfigurationStepFactory.php new file mode 100644 index 0000000..bdbc5a8 --- /dev/null +++ b/database/factories/WorkflowConfigurationStepFactory.php @@ -0,0 +1,37 @@ + + */ +class WorkflowConfigurationStepFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + 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, + ]); + } +} diff --git a/database/migrations/2026_07_22_114854_create_loan_types_table.php b/database/migrations/2026_07_22_114854_create_loan_types_table.php new file mode 100644 index 0000000..bf57ce3 --- /dev/null +++ b/database/migrations/2026_07_22_114854_create_loan_types_table.php @@ -0,0 +1,30 @@ +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'); + } +}; diff --git a/database/migrations/2026_07_22_114858_create_stage_definitions_table.php b/database/migrations/2026_07_22_114858_create_stage_definitions_table.php new file mode 100644 index 0000000..20ee281 --- /dev/null +++ b/database/migrations/2026_07_22_114858_create_stage_definitions_table.php @@ -0,0 +1,30 @@ +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'); + } +}; diff --git a/database/migrations/2026_07_22_114901_create_workflow_configurations_table.php b/database/migrations/2026_07_22_114901_create_workflow_configurations_table.php new file mode 100644 index 0000000..0b2ebd1 --- /dev/null +++ b/database/migrations/2026_07_22_114901_create_workflow_configurations_table.php @@ -0,0 +1,42 @@ +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'); + } +}; diff --git a/database/migrations/2026_07_22_114906_create_workflow_configuration_steps_table.php b/database/migrations/2026_07_22_114906_create_workflow_configuration_steps_table.php new file mode 100644 index 0000000..a2c6a86 --- /dev/null +++ b/database/migrations/2026_07_22_114906_create_workflow_configuration_steps_table.php @@ -0,0 +1,36 @@ +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'); + } +}; diff --git a/database/migrations/2026_07_22_114910_create_loans_table.php b/database/migrations/2026_07_22_114910_create_loans_table.php new file mode 100644 index 0000000..ea710da --- /dev/null +++ b/database/migrations/2026_07_22_114910_create_loans_table.php @@ -0,0 +1,49 @@ +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'); + } +}; diff --git a/database/migrations/2026_07_22_114915_create_loan_histories_table.php b/database/migrations/2026_07_22_114915_create_loan_histories_table.php new file mode 100644 index 0000000..b14b2fd --- /dev/null +++ b/database/migrations/2026_07_22_114915_create_loan_histories_table.php @@ -0,0 +1,37 @@ +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'); + } +}; diff --git a/database/seeders/BankFlowDevelopmentSeeder.php b/database/seeders/BankFlowDevelopmentSeeder.php new file mode 100644 index 0000000..1b79b83 --- /dev/null +++ b/database/seeders/BankFlowDevelopmentSeeder.php @@ -0,0 +1,180 @@ +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 $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 + */ + private function stepsByCode(WorkflowConfiguration $workflowConfiguration): Collection + { + return $workflowConfiguration + ->steps() + ->with('stageDefinition') + ->get() + ->keyBy(fn (WorkflowConfigurationStep $step): string => $step->stageDefinition->code); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 6b901f8..78a8340 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -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); + } } }