dscf-credit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +28 -0
  4. data/Rakefile +8 -0
  5. data/app/controllers/dscf/credit/application_controller.rb +8 -0
  6. data/app/controllers/dscf/credit/bank_branches_controller.rb +38 -0
  7. data/app/controllers/dscf/credit/bank_staffs_controller.rb +77 -0
  8. data/app/controllers/dscf/credit/banks_controller.rb +39 -0
  9. data/app/controllers/dscf/credit/categories_controller.rb +32 -0
  10. data/app/controllers/dscf/credit/credit_line_specs_controller.rb +72 -0
  11. data/app/controllers/dscf/credit/credit_lines_controller.rb +76 -0
  12. data/app/controllers/dscf/credit/facilitators_controller.rb +227 -0
  13. data/app/controllers/dscf/credit/loans_controller.rb +41 -0
  14. data/app/controllers/dscf/credit/parameter_normalizers_controller.rb +31 -0
  15. data/app/controllers/dscf/credit/payment_requests_controller.rb +38 -0
  16. data/app/controllers/dscf/credit/payments_controller.rb +36 -0
  17. data/app/controllers/dscf/credit/scoring_parameters_controller.rb +50 -0
  18. data/app/controllers/dscf/credit/scoring_tables_controller.rb +63 -0
  19. data/app/controllers/dscf/credit/system_config_definitions_controller.rb +34 -0
  20. data/app/controllers/dscf/credit/system_configs_controller.rb +49 -0
  21. data/app/controllers/dscf/credit/users_controller.rb +53 -0
  22. data/app/jobs/dscf/credit/application_job.rb +6 -0
  23. data/app/mailers/dscf/credit/application_mailer.rb +8 -0
  24. data/app/mailers/dscf/credit/bank_staff_welcome_mailer.rb +15 -0
  25. data/app/mailers/dscf/credit/facilitator_mailer.rb +54 -0
  26. data/app/models/dscf/credit/accounting_audit_request.rb +17 -0
  27. data/app/models/dscf/credit/accounting_entry.rb +15 -0
  28. data/app/models/dscf/credit/application_record.rb +7 -0
  29. data/app/models/dscf/credit/bank.rb +38 -0
  30. data/app/models/dscf/credit/bank_branch.rb +23 -0
  31. data/app/models/dscf/credit/bank_staff.rb +26 -0
  32. data/app/models/dscf/credit/category.rb +21 -0
  33. data/app/models/dscf/credit/credit_line.rb +30 -0
  34. data/app/models/dscf/credit/credit_line_spec.rb +37 -0
  35. data/app/models/dscf/credit/daily_routine_transaction.rb +18 -0
  36. data/app/models/dscf/credit/facilitator.rb +31 -0
  37. data/app/models/dscf/credit/facilitator_performance.rb +17 -0
  38. data/app/models/dscf/credit/failed_operations_log.rb +17 -0
  39. data/app/models/dscf/credit/loan.rb +33 -0
  40. data/app/models/dscf/credit/loan_profile.rb +43 -0
  41. data/app/models/dscf/credit/loan_profile_scoring_spec.rb +23 -0
  42. data/app/models/dscf/credit/loan_transaction.rb +17 -0
  43. data/app/models/dscf/credit/parameter_normalizer.rb +11 -0
  44. data/app/models/dscf/credit/payment.rb +22 -0
  45. data/app/models/dscf/credit/payment_request.rb +29 -0
  46. data/app/models/dscf/credit/scoring_parameter.rb +30 -0
  47. data/app/models/dscf/credit/scoring_table.rb +24 -0
  48. data/app/models/dscf/credit/system_config.rb +25 -0
  49. data/app/models/dscf/credit/system_config_definition.rb +25 -0
  50. data/app/serializers/dscf/core/business_serializer.rb +9 -0
  51. data/app/serializers/dscf/core/business_type_serializer.rb +7 -0
  52. data/app/serializers/dscf/core/role_serializer.rb +7 -0
  53. data/app/serializers/dscf/core/user_profile_serializer.rb +13 -0
  54. data/app/serializers/dscf/core/user_role_serializer.rb +16 -0
  55. data/app/serializers/dscf/core/user_serializer.rb +17 -0
  56. data/app/serializers/dscf/credit/bank_branch_serializer.rb +9 -0
  57. data/app/serializers/dscf/credit/bank_serializer.rb +9 -0
  58. data/app/serializers/dscf/credit/bank_staff_serializer.rb +8 -0
  59. data/app/serializers/dscf/credit/category_serializer.rb +7 -0
  60. data/app/serializers/dscf/credit/credit_line_serializer.rb +12 -0
  61. data/app/serializers/dscf/credit/credit_line_spec_serializer.rb +12 -0
  62. data/app/serializers/dscf/credit/parameter_normalizer_serializer.rb +8 -0
  63. data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +12 -0
  64. data/app/serializers/dscf/credit/scoring_table_serializer.rb +9 -0
  65. data/app/serializers/dscf/credit/system_config_definition_serializer.rb +9 -0
  66. data/app/serializers/dscf/credit/system_config_serializer.rb +18 -0
  67. data/app/services/dscf/credit/bank_staff_creation_service.rb +99 -0
  68. data/app/services/dscf/credit/facilitator_additional_info_service.rb +62 -0
  69. data/app/services/dscf/credit/facilitator_approval_service.rb +88 -0
  70. data/app/views/dscf/credit/bank_staff_welcome_mailer/welcome_email.html.erb +116 -0
  71. data/app/views/dscf/credit/bank_staff_welcome_mailer/welcome_email.text.erb +40 -0
  72. data/app/views/dscf/credit/facilitator_mailer/additional_info_received.html.erb +39 -0
  73. data/app/views/dscf/credit/facilitator_mailer/additional_info_received.text.erb +17 -0
  74. data/app/views/dscf/credit/facilitator_mailer/approval_notification.html.erb +59 -0
  75. data/app/views/dscf/credit/facilitator_mailer/approval_notification.text.erb +26 -0
  76. data/app/views/dscf/credit/facilitator_mailer/limit_update_notification.html.erb +39 -0
  77. data/app/views/dscf/credit/facilitator_mailer/limit_update_notification.text.erb +17 -0
  78. data/app/views/dscf/credit/facilitator_mailer/rejection_notification.html.erb +41 -0
  79. data/app/views/dscf/credit/facilitator_mailer/rejection_notification.text.erb +19 -0
  80. data/app/views/layouts/mailer.html.erb +125 -0
  81. data/app/views/layouts/mailer.text.erb +12 -0
  82. data/config/locales/en.yml +208 -0
  83. data/config/routes.rb +50 -0
  84. data/db/migrate/20250822091011_create_dscf_credit_categories.rb +15 -0
  85. data/db/migrate/20250822091015_create_dscf_credit_banks.rb +23 -0
  86. data/db/migrate/20250822091055_create_dscf_credit_bank_branches.rb +23 -0
  87. data/db/migrate/20250822091131_create_dscf_credit_credit_lines.rb +25 -0
  88. data/db/migrate/20250822091527_create_dscf_credit_credit_line_specs.rb +32 -0
  89. data/db/migrate/20250822091744_create_dscf_credit_system_config_definitions.rb +18 -0
  90. data/db/migrate/20250822091820_create_dscf_credit_system_configs.rb +17 -0
  91. data/db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb +30 -0
  92. data/db/migrate/20250822092225_create_dscf_credit_parameter_normalizers.rb +18 -0
  93. data/db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb +25 -0
  94. data/db/migrate/20250822092417_create_dscf_credit_loan_profile_scoring_specs.rb +23 -0
  95. data/db/migrate/20250822092436_create_dscf_credit_facilitators.rb +22 -0
  96. data/db/migrate/20250822092528_create_dscf_credit_facilitator_performances.rb +26 -0
  97. data/db/migrate/20250822092608_create_dscf_credit_payment_requests.rb +26 -0
  98. data/db/migrate/20250822092654_create_dscf_credit_loans.rb +29 -0
  99. data/db/migrate/20250822092717_create_dscf_credit_loan_transactions.rb +21 -0
  100. data/db/migrate/20250822092738_create_dscf_credit_daily_routine_transactions.rb +23 -0
  101. data/db/migrate/20250822092819_create_dscf_credit_accounting_audit_requests.rb +20 -0
  102. data/db/migrate/20250822092843_create_dscf_credit_payments.rb +23 -0
  103. data/db/migrate/20250822092908_create_dscf_credit_failed_operations_logs.rb +21 -0
  104. data/db/migrate/20250822092936_create_dscf_credit_accounting_entries.rb +21 -0
  105. data/db/migrate/20250825231109_create_dscf_credit_bank_staff.rb +14 -0
  106. data/db/migrate/20250901172842_create_dscf_credit_scoring_tables.rb +18 -0
  107. data/db/seeds.rb +745 -0
  108. data/lib/dscf/credit/engine.rb +28 -0
  109. data/lib/dscf/credit/version.rb +5 -0
  110. data/lib/dscf/credit.rb +9 -0
  111. data/lib/tasks/dscf/credit_tasks.rake +4 -0
  112. data/spec/factories/dscf/credit/accounting_audit_requests.rb +58 -0
  113. data/spec/factories/dscf/credit/accounting_entries.rb +29 -0
  114. data/spec/factories/dscf/credit/bank_branches.rb +21 -0
  115. data/spec/factories/dscf/credit/bank_staffs.rb +19 -0
  116. data/spec/factories/dscf/credit/banks.rb +30 -0
  117. data/spec/factories/dscf/credit/categories.rb +27 -0
  118. data/spec/factories/dscf/credit/credit_line_specs.rb +57 -0
  119. data/spec/factories/dscf/credit/credit_lines.rb +32 -0
  120. data/spec/factories/dscf/credit/daily_routine_transactions.rb +36 -0
  121. data/spec/factories/dscf/credit/facilitator_performances.rb +30 -0
  122. data/spec/factories/dscf/credit/facilitators.rb +35 -0
  123. data/spec/factories/dscf/credit/failed_operations_logs.rb +34 -0
  124. data/spec/factories/dscf/credit/loan_profile_scoring_specs.rb +31 -0
  125. data/spec/factories/dscf/credit/loan_profiles.rb +49 -0
  126. data/spec/factories/dscf/credit/loan_transactions.rb +37 -0
  127. data/spec/factories/dscf/credit/loans.rb +37 -0
  128. data/spec/factories/dscf/credit/parameter_normalizers.rb +24 -0
  129. data/spec/factories/dscf/credit/payment_requests.rb +40 -0
  130. data/spec/factories/dscf/credit/payments.rb +39 -0
  131. data/spec/factories/dscf/credit/scoring_parameters.rb +48 -0
  132. data/spec/factories/dscf/credit/scoring_tables.rb +25 -0
  133. data/spec/factories/dscf/credit/system_config_definitions.rb +45 -0
  134. data/spec/factories/dscf/credit/system_configs.rb +29 -0
  135. metadata +456 -0
@@ -0,0 +1,28 @@
1
+ module Dscf
2
+ module Credit
3
+ class Engine < ::Rails::Engine
4
+ isolate_namespace Dscf::Credit
5
+ config.api_only = true
6
+
7
+ config.generators do |g|
8
+ g.test_framework :rspec, routing_specs: false
9
+ g.fixture_replacement :factory_bot
10
+ g.factory_bot dir: "spec/factories"
11
+ end
12
+
13
+ initializer "dscf_credit.factories", after: "factory_bot.set_factory_paths" do
14
+ if defined?(FactoryBot)
15
+ FactoryBot.definition_file_paths << root.join("spec/factories").to_s
16
+ end
17
+ end
18
+
19
+ initializer :append_migrations do |app|
20
+ unless app.root.to_s.match?(root.to_s + File::SEPARATOR)
21
+ app.config.paths["db/migrate"].concat(config.paths["db/migrate"].expanded)
22
+ end
23
+ end
24
+
25
+ require "active_storage/engine"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ module Dscf
2
+ module Credit
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ require "dscf/credit/version"
2
+ require "dscf/credit/engine"
3
+ require "dscf/core"
4
+
5
+ module Dscf
6
+ module Credit
7
+ # Your code goes here...
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :dscf_credit do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,58 @@
1
+ FactoryBot.define do
2
+ factory :accounting_audit_request, class: "Dscf::Credit::AccountingAuditRequest" do
3
+ transaction_type { %w[debit credit transfer adjustment].sample }
4
+ amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
5
+ webhook_key { Faker::Internet.uuid }
6
+ status { "pending" }
7
+
8
+ association :source_entity, factory: :loan
9
+
10
+ trait :pending do
11
+ status { "pending" }
12
+ end
13
+
14
+ trait :processing do
15
+ status { "processing" }
16
+ end
17
+
18
+ trait :completed do
19
+ status { "completed" }
20
+ end
21
+
22
+ trait :failed do
23
+ status { "failed" }
24
+ end
25
+
26
+ trait :debit_transaction do
27
+ transaction_type { "debit" }
28
+ end
29
+
30
+ trait :credit_transaction do
31
+ transaction_type { "credit" }
32
+ end
33
+
34
+ trait :transfer_transaction do
35
+ transaction_type { "transfer" }
36
+ end
37
+
38
+ trait :adjustment_transaction do
39
+ transaction_type { "adjustment" }
40
+ end
41
+
42
+ trait :large_amount do
43
+ amount { Faker::Number.decimal(l_digits: 8, r_digits: 2) }
44
+ end
45
+
46
+ trait :small_amount do
47
+ amount { Faker::Number.between(from: 1.0, to: 100.0).round(2) }
48
+ end
49
+
50
+ trait :with_payment_source do
51
+ association :source_entity, factory: :payment
52
+ end
53
+
54
+ trait :with_loan_source do
55
+ association :source_entity, factory: :loan
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,29 @@
1
+ FactoryBot.define do
2
+ factory :accounting_entry, class: "Dscf::Credit::AccountingEntry" do
3
+ association :audit_request, factory: :accounting_audit_request
4
+ account_code { Faker::Alphanumeric.alphanumeric(number: 10) }
5
+ entry_type { %w[debit credit].sample }
6
+ amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
7
+ status { %w[pending completed failed].sample }
8
+
9
+ trait :debit_entry do
10
+ entry_type { "debit" }
11
+ end
12
+
13
+ trait :credit_entry do
14
+ entry_type { "credit" }
15
+ end
16
+
17
+ trait :pending do
18
+ status { "pending" }
19
+ end
20
+
21
+ trait :completed do
22
+ status { "completed" }
23
+ end
24
+
25
+ trait :failed do
26
+ status { "failed" }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ FactoryBot.define do
2
+ factory :bank_branch, class: "Dscf::Credit::BankBranch" do
3
+ association :bank, factory: :bank
4
+ branch_name { "#{Faker::Address.city} Branch" }
5
+ sequence(:branch_code) { |n| "BR#{n.to_s.rjust(3, '0')}" }
6
+ branch_address { Faker::Address.full_address }
7
+ city { Faker::Address.city }
8
+ country { Faker::Address.country }
9
+ contact_email { Faker::Internet.email }
10
+ contact_phone { Faker::PhoneNumber.phone_number }
11
+ status { %w[active inactive].sample }
12
+
13
+ trait :active do
14
+ status { "active" }
15
+ end
16
+
17
+ trait :inactive do
18
+ status { "inactive" }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ FactoryBot.define do
2
+ factory :bank_staff, class: "Dscf::Credit::BankStaff" do
3
+ association :user, factory: :user if defined?(Dscf::Core::User)
4
+ association :bank_branch, factory: :bank_branch
5
+ status { %w[active inactive terminated].sample }
6
+
7
+ trait :active do
8
+ status { "active" }
9
+ end
10
+
11
+ trait :inactive do
12
+ status { "inactive" }
13
+ end
14
+
15
+ trait :terminated do
16
+ status { "terminated" }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ FactoryBot.define do
2
+ factory :bank, class: "Dscf::Credit::Bank" do
3
+ association :user, factory: :user if defined?(Dscf::Core::User)
4
+ name { Faker::Bank.name }
5
+ sequence(:registration_number) { |n| "REG#{n.to_s.rjust(8, '0')}" }
6
+ sequence(:swift_code) { |n| "#{Faker::Bank.swift_bic[0..3]}#{n.to_s.rjust(4, '0')}" }
7
+ headquarters_address { Faker::Address.full_address }
8
+ city { Faker::Address.city }
9
+ country { Faker::Address.country }
10
+ contact_email { Faker::Internet.email }
11
+ contact_phone { Faker::PhoneNumber.phone_number }
12
+ status { %w[active inactive suspended].sample }
13
+
14
+ trait :active do
15
+ status { "active" }
16
+ end
17
+
18
+ trait :inactive do
19
+ status { "inactive" }
20
+ end
21
+
22
+ trait :suspended do
23
+ status { "suspended" }
24
+ end
25
+
26
+ trait :without_user do
27
+ user { nil }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ FactoryBot.define do
2
+ factory :category, class: "Dscf::Credit::Category" do
3
+ type { %w[scoring loan_profile payment risk_assessment].sample }
4
+ sequence(:name) { |n| "#{type.humanize} Category #{n}" }
5
+ description { Faker::Lorem.paragraph }
6
+
7
+ trait :scoring do
8
+ type { "scoring" }
9
+ name { "Scoring Category" }
10
+ end
11
+
12
+ trait :loan_profile do
13
+ type { "loan_profile" }
14
+ name { "Loan Profile Category" }
15
+ end
16
+
17
+ trait :payment do
18
+ type { "payment" }
19
+ name { "Payment Category" }
20
+ end
21
+
22
+ trait :risk_assessment do
23
+ type { "risk_assessment" }
24
+ name { "Risk Assessment Category" }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ FactoryBot.define do
2
+ factory :credit_line_spec, class: "Dscf::Credit::CreditLineSpec" do
3
+ association :credit_line, factory: :credit_line
4
+ association :created_by, factory: :user if defined?(Dscf::Core::User)
5
+ min_amount { Faker::Number.decimal(l_digits: 4, r_digits: 2).to_f }
6
+ max_amount { min_amount + Faker::Number.decimal(l_digits: 5, r_digits: 2).to_f }
7
+ interest_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
8
+ penalty_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
9
+ facilitation_fee_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
10
+ tax_rate { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
11
+ max_penalty_days { Faker::Number.between(from: 30, to: 90) }
12
+ loan_duration { Faker::Number.between(from: 30, to: 365) }
13
+ interest_frequency { %w[daily weekly monthly quarterly annually].sample }
14
+ interest_income_tax { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
15
+ vat { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
16
+ max_interest_calculation_days { Faker::Number.between(from: 30, to: 365) }
17
+ penalty_frequency { %w[daily weekly monthly].sample }
18
+ penalty_income_tax { Faker::Number.decimal(l_digits: 1, r_digits: 4).to_f }
19
+ active { true }
20
+
21
+ trait :active do
22
+ active { true }
23
+ end
24
+
25
+ trait :inactive do
26
+ active { false }
27
+ end
28
+
29
+ trait :short_term do
30
+ loan_duration { Faker::Number.between(from: 30, to: 90) }
31
+ interest_frequency { 'monthly' }
32
+ penalty_frequency { 'weekly' }
33
+ end
34
+
35
+ trait :medium_term do
36
+ loan_duration { Faker::Number.between(from: 91, to: 180) }
37
+ interest_frequency { 'monthly' }
38
+ penalty_frequency { 'monthly' }
39
+ end
40
+
41
+ trait :long_term do
42
+ loan_duration { Faker::Number.between(from: 181, to: 365) }
43
+ interest_frequency { 'quarterly' }
44
+ penalty_frequency { 'monthly' }
45
+ end
46
+
47
+ trait :daily_interest do
48
+ interest_frequency { 'daily' }
49
+ penalty_frequency { 'daily' }
50
+ end
51
+
52
+ trait :monthly_interest do
53
+ interest_frequency { 'monthly' }
54
+ penalty_frequency { 'weekly' }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ FactoryBot.define do
2
+ factory :credit_line, class: "Dscf::Credit::CreditLine" do
3
+ association :bank, factory: :bank
4
+ association :category, factory: :category
5
+ association :created_by, factory: :user if defined?(Dscf::Core::User)
6
+ association :approved_by, factory: :user if defined?(Dscf::Core::User)
7
+ name { "#{Faker::Finance.credit_card(:visa)} Credit Line" }
8
+ sequence(:code) { |n| "CL#{n.to_s.rjust(4, '0')}" }
9
+ description { Faker::Lorem.paragraph }
10
+ status { %w[pending approved active suspended].sample }
11
+ approval_date { Faker::Date.between(from: 6.months.ago, to: Date.current) if status.in?(%w[approved active]) }
12
+
13
+ trait :pending do
14
+ status { "pending" }
15
+ approval_date { nil }
16
+ end
17
+
18
+ trait :approved do
19
+ status { "approved" }
20
+ approval_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
21
+ end
22
+
23
+ trait :active do
24
+ status { "active" }
25
+ approval_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
26
+ end
27
+
28
+ trait :suspended do
29
+ status { "suspended" }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ FactoryBot.define do
2
+ factory :daily_routine_transaction, class: "Dscf::Credit::DailyRoutineTransaction" do
3
+ association :loan, factory: :loan
4
+ routine_type { %w[interest_calculation penalty_assessment payment_due_notification].sample }
5
+ amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
6
+ status { %w[pending processing completed failed].sample }
7
+ failure_reason { Faker::Lorem.sentence if status == "failed" }
8
+ initiated_at { Faker::Time.between(from: 1.month.ago, to: Time.current) }
9
+ approved_at { initiated_at + rand(1..6).hours if status.in?(%w[completed failed]) }
10
+
11
+ trait :pending do
12
+ status { "pending" }
13
+ started_at { nil }
14
+ completed_at { nil }
15
+ processed_records_count { 0 }
16
+ failed_records_count { 0 }
17
+ end
18
+
19
+ trait :in_progress do
20
+ status { "in_progress" }
21
+ completed_at { nil }
22
+ end
23
+
24
+ trait :completed do
25
+ status { "completed" }
26
+ completed_at { started_at + rand(1..6).hours }
27
+ error_details { nil }
28
+ end
29
+
30
+ trait :failed do
31
+ status { "failed" }
32
+ completed_at { started_at + rand(1..6).hours }
33
+ error_details { Faker::Json.shallow_json(width: 3) }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ FactoryBot.define do
2
+ factory :facilitator_performance, class: "Dscf::Credit::FacilitatorPerformance" do
3
+ association :facilitator, factory: :facilitator
4
+ association :created_by, factory: :user if defined?(Dscf::Core::User)
5
+ score { Faker::Number.decimal(l_digits: 2, r_digits: 2) }
6
+ total_outstanding_loans { Faker::Number.between(from: 0, to: 50) }
7
+ total_outstanding_amount { Faker::Number.decimal(l_digits: 6, r_digits: 2) }
8
+ approval_required { [ true, false ].sample }
9
+ input_data { Faker::Json.shallow_json(width: 3, options: { key: 'Name.name', value: 'Number.number' }) }
10
+ expires_at { Faker::Date.between(from: 1.month.from_now, to: 1.year.from_now) }
11
+
12
+ trait :high_performance do
13
+ score { Faker::Number.between(from: 80.0, to: 100.0) }
14
+ total_outstanding_loans { Faker::Number.between(from: 20, to: 50) }
15
+ end
16
+
17
+ trait :low_performance do
18
+ score { Faker::Number.between(from: 0.0, to: 30.0) }
19
+ total_outstanding_loans { Faker::Number.between(from: 0, to: 5) }
20
+ end
21
+
22
+ trait :requiring_approval do
23
+ approval_required { true }
24
+ end
25
+
26
+ trait :expired do
27
+ expires_at { Faker::Date.between(from: 1.year.ago, to: 1.month.ago) }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ FactoryBot.define do
2
+ factory :facilitator, class: "Dscf::Credit::Facilitator" do
3
+ association :bank, factory: :bank
4
+ association :user, factory: :user if defined?(Dscf::Core::User)
5
+ association :kyc_approved_by, factory: :user if defined?(Dscf::Core::User)
6
+ name { Faker::Name.name }
7
+ type { %w[individual corporate agent].sample }
8
+ total_limit { Faker::Number.decimal(l_digits: 6, r_digits: 2) }
9
+ kyc_status { %w[pending approved rejected].sample }
10
+
11
+ trait :pending_kyc do
12
+ kyc_status { "pending" }
13
+ end
14
+
15
+ trait :approved do
16
+ kyc_status { "approved" }
17
+ end
18
+
19
+ trait :rejected do
20
+ kyc_status { "rejected" }
21
+ end
22
+
23
+ trait :individual do
24
+ type { "individual" }
25
+ end
26
+
27
+ trait :corporate do
28
+ type { "corporate" }
29
+ end
30
+
31
+ trait :agent do
32
+ type { "agent" }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ FactoryBot.define do
2
+ factory :failed_operations_log, class: "Dscf::Credit::FailedOperationsLog" do
3
+ association :entity, factory: :loan
4
+ failure_type { %w[loan_disbursement payment_processing interest_calculation credit_scoring daily_routine accounting_entry].sample }
5
+ reason { Faker::Lorem.sentence }
6
+ retry_count { Faker::Number.between(from: 0, to: 5) }
7
+ resolved { [ true, false ].sample }
8
+ resolved_at { resolved ? Faker::Time.between(from: 1.day.ago, to: Time.current) : nil }
9
+
10
+ trait :resolved do
11
+ resolved { true }
12
+ resolved_at { Faker::Time.between(from: 1.day.ago, to: Time.current) }
13
+ end
14
+
15
+ trait :unresolved do
16
+ resolved { false }
17
+ resolved_at { nil }
18
+ end
19
+
20
+ trait :with_retries do
21
+ retry_count { 3 }
22
+ end
23
+
24
+ trait :payment_failure do
25
+ failure_type { "payment_processing" }
26
+ association :entity, factory: :payment
27
+ end
28
+
29
+ trait :loan_failure do
30
+ failure_type { "loan_disbursement" }
31
+ association :entity, factory: :loan
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ FactoryBot.define do
2
+ factory :loan_profile_scoring_spec, class: "Dscf::Credit::LoanProfileScoringSpec" do
3
+ association :loan_profile, factory: :loan_profile
4
+ association :created_by, factory: :user if defined?(Dscf::Core::User)
5
+ association :approved_by, factory: :user if defined?(Dscf::Core::User)
6
+ scoring_input_data { Faker::Json.shallow_json(width: 5, options: { key: 'Name.name', value: 'Number.number' }) }
7
+ score { Faker::Number.decimal(l_digits: 2, r_digits: 2) }
8
+ active { true }
9
+ expires_at { Faker::Date.between(from: 3.months.from_now, to: 1.year.from_now) }
10
+
11
+ trait :active do
12
+ active { true }
13
+ end
14
+
15
+ trait :inactive do
16
+ active { false }
17
+ end
18
+
19
+ trait :high_score do
20
+ score { Faker::Number.between(from: 80.0, to: 100.0) }
21
+ end
22
+
23
+ trait :low_score do
24
+ score { Faker::Number.between(from: 0.0, to: 30.0) }
25
+ end
26
+
27
+ trait :expired do
28
+ expires_at { Faker::Date.between(from: 1.year.ago, to: 1.month.ago) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,49 @@
1
+ FactoryBot.define do
2
+ factory :loan_profile, class: "Dscf::Credit::LoanProfile" do
3
+ association :bank, factory: :bank
4
+ association :approved_by, factory: :user if defined?(Dscf::Core::User)
5
+ association :user, factory: :user if defined?(Dscf::Core::User)
6
+ association :backer, factory: :user if defined?(Dscf::Core::User)
7
+ status { %w[pending approved rejected].sample }
8
+ total_amount { Faker::Number.decimal(l_digits: 6, r_digits: 2).to_f }
9
+ available_amount { total_amount - Faker::Number.decimal(l_digits: 4, r_digits: 2).to_f }
10
+ rejection_reason { Faker::Lorem.sentence if status == "rejected" }
11
+
12
+ trait :pending do
13
+ status { "pending" }
14
+ rejection_reason { nil }
15
+ end
16
+
17
+ trait :approved do
18
+ status { "approved" }
19
+ rejection_reason { nil }
20
+ end
21
+
22
+ trait :rejected do
23
+ status { "rejected" }
24
+ rejection_reason { Faker::Lorem.sentence }
25
+ end
26
+
27
+ trait :fully_available do
28
+ available_amount { total_amount }
29
+ end
30
+
31
+ trait :fully_utilized do
32
+ available_amount { 0 }
33
+ end
34
+
35
+ trait :partially_utilized do
36
+ available_amount { total_amount * 0.5 }
37
+ end
38
+
39
+ trait :small_amount do
40
+ total_amount { Faker::Number.between(from: 1000, to: 10000) }
41
+ available_amount { total_amount * rand(0.1..1.0) }
42
+ end
43
+
44
+ trait :large_amount do
45
+ total_amount { Faker::Number.between(from: 100000, to: 1000000) }
46
+ available_amount { total_amount * rand(0.1..1.0) }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ FactoryBot.define do
2
+ factory :loan_transaction, class: "Dscf::Credit::LoanTransaction" do
3
+ association :loan, factory: :loan
4
+ transaction_type { %w[disbursement repayment interest_accrual penalty fee_charge].sample }
5
+ amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
6
+ transaction_reference { Faker::Alphanumeric.alphanumeric(number: 20).upcase }
7
+ status { %w[pending processing completed failed reversed].sample }
8
+
9
+ trait :disbursement do
10
+ transaction_type { "disbursement" }
11
+ end
12
+
13
+ trait :repayment do
14
+ transaction_type { "repayment" }
15
+ end
16
+
17
+ trait :interest_accrual do
18
+ transaction_type { "interest_accrual" }
19
+ end
20
+
21
+ trait :penalty do
22
+ transaction_type { "penalty" }
23
+ end
24
+
25
+ trait :fee_charge do
26
+ transaction_type { "fee_charge" }
27
+ end
28
+
29
+ trait :completed do
30
+ status { "completed" }
31
+ end
32
+
33
+ trait :failed do
34
+ status { "failed" }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ FactoryBot.define do
2
+ factory :loan, class: "Dscf::Credit::Loan" do
3
+ association :loan_profile, factory: :loan_profile
4
+ association :credit_line, factory: :credit_line
5
+ association :payment_request, factory: :payment_request
6
+ status { %w[pending approved disbursed active overdue paid closed].sample }
7
+ principal_amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
8
+ accrued_interest { Faker::Number.decimal(l_digits: 3, r_digits: 2) }
9
+ accrued_penalty { Faker::Number.decimal(l_digits: 3, r_digits: 2) }
10
+ facilitation_fee { Faker::Number.decimal(l_digits: 3, r_digits: 2) }
11
+ total_loan_amount { principal_amount + accrued_interest + facilitation_fee }
12
+ remaining_amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
13
+ due_date { Faker::Date.between(from: 1.month.from_now, to: 6.months.from_now) }
14
+ disbursed_at { Faker::Time.between(from: 2.months.ago, to: Time.current) if status.in?(%w[disbursed active overdue paid closed]) }
15
+
16
+ trait :pending do
17
+ status { "pending" }
18
+ disbursed_at { nil }
19
+ end
20
+
21
+ trait :active do
22
+ status { "active" }
23
+ disbursed_at { Faker::Time.between(from: 2.months.ago, to: 1.month.ago) }
24
+ end
25
+
26
+ trait :overdue do
27
+ status { "overdue" }
28
+ due_date { Faker::Date.between(from: 1.month.ago, to: Date.current) }
29
+ disbursed_at { Faker::Time.between(from: 3.months.ago, to: 2.months.ago) }
30
+ end
31
+
32
+ trait :paid do
33
+ status { "paid" }
34
+ remaining_amount { 0 }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ FactoryBot.define do
2
+ factory :parameter_normalizer, class: "Dscf::Credit::ParameterNormalizer" do
3
+ association :scoring_parameter, factory: :scoring_parameter
4
+ name { Faker::Lorem.words(number: 2).join(' ').titlecase }
5
+ description { Faker::Lorem.sentence }
6
+ raw_value { Faker::Lorem.word }
7
+ normalized_value { Faker::Number.decimal(l_digits: 1, r_digits: 2) }
8
+
9
+ trait :high_value do
10
+ raw_value { "excellent" }
11
+ normalized_value { Faker::Number.between(from: 8.0, to: 10.0) }
12
+ end
13
+
14
+ trait :medium_value do
15
+ raw_value { "good" }
16
+ normalized_value { Faker::Number.between(from: 5.0, to: 7.9) }
17
+ end
18
+
19
+ trait :low_value do
20
+ raw_value { "poor" }
21
+ normalized_value { Faker::Number.between(from: 0.0, to: 4.9) }
22
+ end
23
+ end
24
+ end