dscf-credit 0.1.4 → 0.1.5

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/dscf/core/copilot-instructions.md +683 -0
  3. data/app/controllers/concerns/dscf/core/reviewable_controller.rb +347 -0
  4. data/app/controllers/dscf/credit/categories_controller.rb +3 -3
  5. data/app/controllers/dscf/credit/credit_lines_controller.rb +21 -13
  6. data/app/controllers/dscf/credit/eligible_credit_lines_controller.rb +50 -8
  7. data/app/controllers/dscf/credit/facilitator_applications_controller.rb +35 -0
  8. data/app/controllers/dscf/credit/facilitators_controller.rb +8 -96
  9. data/app/controllers/dscf/credit/loan_applications_controller.rb +252 -0
  10. data/app/controllers/dscf/credit/loan_profiles_controller.rb +61 -68
  11. data/app/controllers/dscf/credit/payment_requests_controller.rb +3 -5
  12. data/app/controllers/dscf/credit/scoring_parameters_controller.rb +59 -13
  13. data/app/controllers/dscf/credit/system_configs_controller.rb +30 -12
  14. data/app/models/concerns/core/reviewable_model.rb +31 -0
  15. data/app/models/dscf/credit/bank.rb +3 -3
  16. data/app/models/dscf/credit/bank_branch.rb +1 -1
  17. data/app/models/dscf/credit/category.rb +1 -2
  18. data/app/models/dscf/credit/credit_line.rb +4 -10
  19. data/app/models/dscf/credit/eligible_credit_line.rb +2 -2
  20. data/app/models/dscf/credit/facilitator.rb +6 -17
  21. data/app/models/dscf/credit/facilitator_application.rb +20 -0
  22. data/app/models/dscf/credit/loan_application.rb +30 -0
  23. data/app/models/dscf/credit/loan_profile.rb +10 -30
  24. data/app/models/dscf/credit/parameter_normalizer.rb +1 -1
  25. data/app/models/dscf/credit/scoring_parameter.rb +5 -7
  26. data/app/models/dscf/credit/system_config.rb +4 -9
  27. data/app/serializers/dscf/credit/category_serializer.rb +0 -1
  28. data/app/serializers/dscf/credit/credit_line_serializer.rb +2 -2
  29. data/app/serializers/dscf/credit/facilitator_application_serializer.rb +7 -0
  30. data/app/serializers/dscf/credit/facilitator_serializer.rb +3 -6
  31. data/app/serializers/dscf/credit/loan_application_serializer.rb +12 -0
  32. data/app/serializers/dscf/credit/loan_profile_serializer.rb +3 -6
  33. data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +3 -4
  34. data/app/serializers/dscf/credit/system_config_serializer.rb +2 -2
  35. data/app/services/dscf/credit/credit_scoring_engine.rb +258 -0
  36. data/app/services/dscf/credit/facility_limit_calculation_engine.rb +159 -0
  37. data/app/services/dscf/credit/loan_profile_creation_service.rb +91 -0
  38. data/app/services/dscf/credit/risk_application_service.rb +61 -11
  39. data/config/locales/en.yml +63 -48
  40. data/config/routes.rb +31 -17
  41. data/db/migrate/20250822091131_create_dscf_credit_credit_lines.rb +1 -8
  42. data/db/migrate/20250822091820_create_dscf_credit_system_configs.rb +0 -7
  43. data/db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb +2 -6
  44. data/db/migrate/20250822092225_create_dscf_credit_parameter_normalizers.rb +1 -1
  45. data/db/migrate/20250822092236_create_dscf_credit_loan_applications.rb +20 -0
  46. data/db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb +7 -19
  47. data/db/migrate/20250822092426_create_dscf_credit_facilitator_applications.rb +10 -0
  48. data/db/migrate/20250822092436_create_dscf_credit_facilitators.rb +1 -16
  49. data/db/seeds.rb +316 -203
  50. data/lib/dscf/credit/version.rb +1 -1
  51. data/spec/factories/dscf/credit/banks.rb +1 -1
  52. data/spec/factories/dscf/credit/credit_lines.rb +0 -23
  53. data/spec/factories/dscf/credit/facilitator_applications.rb +37 -0
  54. data/spec/factories/dscf/credit/facilitators.rb +8 -30
  55. data/spec/factories/dscf/credit/loan_applications.rb +42 -0
  56. data/spec/factories/dscf/credit/loan_profiles.rb +20 -34
  57. data/spec/factories/dscf/credit/parameter_normalizers.rb +4 -4
  58. data/spec/factories/dscf/credit/scoring_parameters.rb +14 -11
  59. data/spec/factories/dscf/credit/system_configs.rb +21 -5
  60. metadata +20 -10
  61. data/app/controllers/concerns/dscf/credit/reviewable.rb +0 -112
  62. data/app/controllers/dscf/credit/scoring_tables_controller.rb +0 -63
  63. data/app/models/dscf/credit/scoring_table.rb +0 -24
  64. data/app/serializers/dscf/credit/scoring_table_serializer.rb +0 -9
  65. data/db/migrate/20250901172842_create_dscf_credit_scoring_tables.rb +0 -18
  66. data/spec/factories/dscf/credit/scoring_tables.rb +0 -25
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Credit
3
- VERSION = "0.1.4"
3
+ VERSION = "0.1.5"
4
4
  end
5
5
  end
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :bank, class: "Dscf::Credit::Bank" do
3
3
  association :user, factory: :user if defined?(Dscf::Core::User)
4
- name { Faker::Bank.name }
4
+ sequence(:name) { |n| "#{Faker::Bank.name} #{n}" }
5
5
  sequence(:registration_number) { |n| "REG#{n.to_s.rjust(8, '0')}" }
6
6
  sequence(:swift_code) { |n| "#{Faker::Bank.swift_bic[0..3]}#{n.to_s.rjust(4, '0')}" }
7
7
  headquarters_address { Faker::Address.full_address }
@@ -3,32 +3,9 @@ FactoryBot.define do
3
3
  association :bank, factory: :bank
4
4
  association :category, factory: :category
5
5
  association :created_by, factory: :user if defined?(Dscf::Core::User)
6
- association :reviewed_by, factory: :user if defined?(Dscf::Core::User)
7
6
  name { "#{Faker::Finance.credit_card(:visa)} Credit Line" }
8
7
  sequence(:code) { |n| "CL#{n.to_s.rjust(4, '0')}" }
9
8
  description { Faker::Lorem.paragraph }
10
- status { %w[pending approved suspended].sample }
11
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) if status.in?(%w[approved]) }
12
9
  document_reference { Faker::Alphanumeric.alphanumeric(number: 10).upcase }
13
- review_feedback { {} }
14
-
15
- trait :pending do
16
- status { "pending" }
17
- review_date { nil }
18
- end
19
-
20
- trait :approved do
21
- status { "approved" }
22
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
23
- end
24
-
25
- trait :active do
26
- status { "approved" }
27
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
28
- end
29
-
30
- trait :suspended do
31
- status { "suspended" }
32
- end
33
10
  end
34
11
  end
@@ -0,0 +1,37 @@
1
+ FactoryBot.define do
2
+ factory :facilitator_application, class: "Dscf::Credit::FacilitatorApplication" do
3
+ association :user, factory: :user
4
+ association :bank, factory: :bank
5
+ facilitator_info do
6
+ {
7
+ "business_name" => Faker::Company.name,
8
+ "license_number" => "LIC#{Faker::Number.number(digits: 6)}",
9
+ "business_type" => [ "retail", "wholesale", "manufacturing" ].sample,
10
+ "years_in_business" => Faker::Number.between(from: 1, to: 20),
11
+ "monthly_turnover" => Faker::Number.decimal(l_digits: 5, r_digits: 2).to_f,
12
+ "contact_person" => Faker::Name.name,
13
+ "phone" => Faker::PhoneNumber.phone_number,
14
+ "email" => Faker::Internet.email,
15
+ "address" => Faker::Address.full_address
16
+ }
17
+ end
18
+
19
+ trait :pending do
20
+ after(:create) do |application|
21
+ application.reviews.create!(status: "pending", context: "default")
22
+ end
23
+ end
24
+
25
+ trait :approved do
26
+ after(:create) do |application|
27
+ application.reviews.create!(status: "approved", context: "default")
28
+ end
29
+ end
30
+
31
+ trait :rejected do
32
+ after(:create) do |application|
33
+ application.reviews.create!(status: "rejected", context: "default")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,40 +1,18 @@
1
1
  FactoryBot.define do
2
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_reviewed_by, factory: :user if defined?(Dscf::Core::User)
6
- name { Faker::Name.name }
7
- type { %w[individual corporate agent].sample }
3
+ association :facilitator_application, factory: :facilitator_application
8
4
  total_limit { Faker::Number.decimal(l_digits: 6, r_digits: 2) }
9
- kyc_status { %w[pending approved rejected].sample }
10
- kyc_review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) if kyc_status == "approved" }
11
- review_feedback { {} }
12
-
13
- trait :pending_kyc do
14
- kyc_status { "pending" }
15
- kyc_review_date { nil }
16
- end
17
5
 
18
6
  trait :approved do
19
- kyc_status { "approved" }
20
- kyc_review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
21
- end
22
-
23
- trait :rejected do
24
- kyc_status { "rejected" }
25
- kyc_review_date { nil }
26
- end
27
-
28
- trait :individual do
29
- type { "individual" }
30
- end
31
-
32
- trait :corporate do
33
- type { "corporate" }
7
+ after(:create) do |facilitator|
8
+ facilitator.reviews.create!(status: "approved", context: "default")
9
+ end
34
10
  end
35
11
 
36
- trait :agent do
37
- type { "agent" }
12
+ trait :pending do
13
+ after(:create) do |facilitator|
14
+ facilitator.reviews.create!(status: "pending", context: "default")
15
+ end
38
16
  end
39
17
  end
40
18
  end
@@ -0,0 +1,42 @@
1
+ FactoryBot.define do
2
+ factory :loan_application, class: "Dscf::Credit::LoanApplication" do
3
+ association :bank, factory: :bank
4
+ association :user, factory: :user if defined?(Dscf::Core::User)
5
+ association :backer, factory: :user if defined?(Dscf::Core::User)
6
+ association :review_branch, factory: :bank_branch
7
+ bank_statement_source { "internal" }
8
+ user_info { { name: "John Doe", phone: "+251911123456", email: "john@example.com" } }
9
+ facilitator_info { { business_name: "ABC Trading", license_number: "LIC001" } }
10
+ bank_info { { account_number: "1234567890", account_holder: "John Doe" } }
11
+ field_assessment { { credit_score: 750, risk_level: "low" } }
12
+ score { 75.5 }
13
+
14
+ trait :external do
15
+ bank_statement_source { "external" }
16
+ end
17
+
18
+ trait :pending do
19
+ after(:create) do |loan_application|
20
+ loan_application.reviews.create!(reviewed_by: create(:user), status: "pending")
21
+ end
22
+ end
23
+
24
+ trait :approved do
25
+ after(:create) do |loan_application|
26
+ loan_application.reviews.create!(reviewed_by: create(:user), status: "approved")
27
+ end
28
+ end
29
+
30
+ trait :rejected do
31
+ after(:create) do |loan_application|
32
+ loan_application.reviews.create!(reviewed_by: create(:user), status: "rejected")
33
+ end
34
+ end
35
+
36
+ trait :modify do
37
+ after(:create) do |loan_application|
38
+ loan_application.reviews.create!(reviewed_by: create(:user), status: "modify")
39
+ end
40
+ end
41
+ end
42
+ end
@@ -1,54 +1,40 @@
1
1
  FactoryBot.define do
2
2
  factory :loan_profile, class: "Dscf::Credit::LoanProfile" do
3
- association :bank, factory: :bank
4
- association :review_branch, factory: :bank_branch
5
- association :reviewed_by, factory: :user if defined?(Dscf::Core::User)
6
- association :user, factory: :user if defined?(Dscf::Core::User)
7
- association :backer, factory: :user if defined?(Dscf::Core::User)
8
- status { %w[pending approved rejected kyc_approved kyc_rejected].sample }
9
- total_amount { Faker::Number.decimal(l_digits: 6, r_digits: 2).to_f }
10
- available_amount { total_amount - Faker::Number.decimal(l_digits: 4, r_digits: 2).to_f }
11
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) if status == "approved" }
12
- review_feedback { status == "rejected" ? { reason: Faker::Lorem.sentence } : {} }
3
+ association :loan_application, factory: :loan_application
4
+ sequence(:code) { |n| "BB#{format('%06d', n)}" }
5
+ score { Faker::Number.decimal(l_digits: 2, r_digits: 2).to_f }
6
+ total_limit { Faker::Number.decimal(l_digits: 6, r_digits: 2).to_f }
13
7
 
14
8
  trait :pending do
15
- status { "pending" }
16
- review_feedback { {} }
17
- review_date { nil }
9
+ after(:create) do |loan_profile|
10
+ loan_profile.reviews.create!(status: "pending", context: "default")
11
+ end
18
12
  end
19
13
 
20
14
  trait :approved do
21
- status { "approved" }
22
- review_feedback { {} }
23
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
15
+ after(:create) do |loan_profile|
16
+ loan_profile.reviews.create!(status: "approved", context: "default")
17
+ end
24
18
  end
25
19
 
26
- trait :rejected do
27
- status { "rejected" }
28
- review_feedback { { reason: Faker::Lorem.sentence } }
29
- review_date { nil }
20
+ trait :high_score do
21
+ score { Faker::Number.between(from: 70, to: 85) }
30
22
  end
31
23
 
32
- trait :fully_available do
33
- available_amount { total_amount }
24
+ trait :medium_score do
25
+ score { Faker::Number.between(from: 50, to: 69) }
34
26
  end
35
27
 
36
- trait :fully_utilized do
37
- available_amount { 0 }
28
+ trait :low_score do
29
+ score { Faker::Number.between(from: 30, to: 49) }
38
30
  end
39
31
 
40
- trait :partially_utilized do
41
- available_amount { total_amount * 0.5 }
32
+ trait :small_limit do
33
+ total_limit { Faker::Number.between(from: 1000, to: 10000) }
42
34
  end
43
35
 
44
- trait :small_amount do
45
- total_amount { Faker::Number.between(from: 1000, to: 10000) }
46
- available_amount { total_amount * rand(0.1..1.0) }
47
- end
48
-
49
- trait :large_amount do
50
- total_amount { Faker::Number.between(from: 100000, to: 1000000) }
51
- available_amount { total_amount * rand(0.1..1.0) }
36
+ trait :large_limit do
37
+ total_limit { Faker::Number.between(from: 100000, to: 1000000) }
52
38
  end
53
39
  end
54
40
  end
@@ -4,21 +4,21 @@ FactoryBot.define do
4
4
  name { Faker::Lorem.words(number: 2).join(' ').titlecase }
5
5
  description { Faker::Lorem.sentence }
6
6
  raw_value { Faker::Lorem.word }
7
- normalized_value { Faker::Number.decimal(l_digits: 1, r_digits: 2) }
7
+ normalized_value { Faker::Number.decimal(l_digits: 0, r_digits: 4) }
8
8
 
9
9
  trait :high_value do
10
10
  raw_value { "excellent" }
11
- normalized_value { Faker::Number.between(from: 8.0, to: 10.0) }
11
+ normalized_value { Faker::Number.between(from: 0.8, to: 1.0) }
12
12
  end
13
13
 
14
14
  trait :medium_value do
15
15
  raw_value { "good" }
16
- normalized_value { Faker::Number.between(from: 5.0, to: 7.9) }
16
+ normalized_value { Faker::Number.between(from: 0.5, to: 0.79) }
17
17
  end
18
18
 
19
19
  trait :low_value do
20
20
  raw_value { "poor" }
21
- normalized_value { Faker::Number.between(from: 0.0, to: 4.9) }
21
+ normalized_value { Faker::Number.between(from: 0.0, to: 0.49) }
22
22
  end
23
23
  end
24
24
  end
@@ -1,21 +1,18 @@
1
1
  FactoryBot.define do
2
2
  factory :scoring_parameter, class: "Dscf::Credit::ScoringParameter" do
3
3
  association :bank, factory: :bank
4
+ association :category, factory: :category
4
5
  association :created_by, factory: :user if defined?(Dscf::Core::User)
5
- association :reviewed_by, factory: :user if defined?(Dscf::Core::User)
6
6
  association :scoring_param_type, factory: :scoring_param_type
7
- sequence(:name) { |n| "#{Faker::Lorem.words(number: 2).join('_').downcase}_#{n}" }
7
+ sequence(:name) { |n| "scoring_param_#{SecureRandom.hex(4)}_#{n}_#{Time.current.to_i}" }
8
8
  description { Faker::Lorem.paragraph }
9
9
  data_type { %w[integer decimal boolean string].sample }
10
10
  weight { Faker::Number.decimal(l_digits: 0, r_digits: 4) }
11
11
  min_value { Faker::Number.decimal(l_digits: 2, r_digits: 2) }
12
12
  max_value { min_value + Faker::Number.decimal(l_digits: 3, r_digits: 2) }
13
13
  active { true }
14
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
15
14
  source { Faker::Lorem.word }
16
15
  document_reference { Faker::Alphanumeric.alphanumeric(number: 10).upcase }
17
- status { "pending" }
18
- review_feedback { {} }
19
16
 
20
17
  trait :active do
21
18
  active { true }
@@ -50,21 +47,27 @@ FactoryBot.define do
50
47
  end
51
48
 
52
49
  trait :pending do
53
- status { "pending" }
50
+ after(:create) do |scoring_parameter|
51
+ scoring_parameter.reviews.create!(reviewed_by: create(:user), status: "pending")
52
+ end
54
53
  end
55
54
 
56
55
  trait :approved do
57
- status { "approved" }
58
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
56
+ after(:create) do |scoring_parameter|
57
+ scoring_parameter.reviews.create!(reviewed_by: create(:user), status: "approved")
58
+ end
59
59
  end
60
60
 
61
61
  trait :rejected do
62
- status { "rejected" }
63
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) }
62
+ after(:create) do |scoring_parameter|
63
+ scoring_parameter.reviews.create!(reviewed_by: create(:user), status: "rejected")
64
+ end
64
65
  end
65
66
 
66
67
  trait :modify do
67
- status { "modify" }
68
+ after(:create) do |scoring_parameter|
69
+ scoring_parameter.reviews.create!(reviewed_by: create(:user), status: "modify")
70
+ end
68
71
  end
69
72
  end
70
73
  end
@@ -2,11 +2,7 @@ FactoryBot.define do
2
2
  factory :system_config, class: "Dscf::Credit::SystemConfig" do
3
3
  association :config_definition, factory: :system_config_definition
4
4
  association :last_updated_by, factory: :user if defined?(Dscf::Core::User)
5
- association :reviewed_by, factory: :user if defined?(Dscf::Core::User)
6
5
  config_value { Faker::Lorem.word }
7
- status { %w[pending approved rejected modify].sample }
8
- review_date { Faker::Date.between(from: 6.months.ago, to: Date.current) if status == 'approved' }
9
- review_feedback { {} }
10
6
 
11
7
  trait :string_value do
12
8
  config_value { Faker::Lorem.sentence }
@@ -28,8 +24,28 @@ FactoryBot.define do
28
24
  value { { key: Faker::Lorem.word, value: Faker::Number.number(digits: 3) }.to_json }
29
25
  end
30
26
 
27
+ trait :pending do
28
+ after(:create) do |system_config|
29
+ system_config.reviews.create!(reviewed_by: create(:user), status: "pending", context: "default")
30
+ end
31
+ end
32
+
33
+ trait :approved do
34
+ after(:create) do |system_config|
35
+ system_config.reviews.create!(reviewed_by: create(:user), status: "approved", context: "default")
36
+ end
37
+ end
38
+
39
+ trait :rejected do
40
+ after(:create) do |system_config|
41
+ system_config.reviews.create!(reviewed_by: create(:user), status: "rejected", context: "default")
42
+ end
43
+ end
44
+
31
45
  trait :modify do
32
- status { "modify" }
46
+ after(:create) do |system_config|
47
+ system_config.reviews.create!(reviewed_by: create(:user), status: "modify", context: "default")
48
+ end
33
49
  end
34
50
  end
35
51
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-credit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adoniyas
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-21 00:00:00.000000000 Z
10
+ date: 2025-09-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dscf-core
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.1.3
18
+ version: 0.1.8
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.1.3
25
+ version: 0.1.8
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: active_model_serializers
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -315,11 +315,12 @@ files:
315
315
  - Rakefile
316
316
  - app/controllers/concerns/dscf/core/authenticatable.rb
317
317
  - app/controllers/concerns/dscf/core/common.rb
318
+ - app/controllers/concerns/dscf/core/copilot-instructions.md
318
319
  - app/controllers/concerns/dscf/core/filterable.rb
319
320
  - app/controllers/concerns/dscf/core/json_response.rb
320
321
  - app/controllers/concerns/dscf/core/pagination.rb
322
+ - app/controllers/concerns/dscf/core/reviewable_controller.rb
321
323
  - app/controllers/concerns/dscf/core/token_authenticatable.rb
322
- - app/controllers/concerns/dscf/credit/reviewable.rb
323
324
  - app/controllers/dscf/credit/application_controller.rb
324
325
  - app/controllers/dscf/credit/bank_branches_controller.rb
325
326
  - app/controllers/dscf/credit/bank_staffs_controller.rb
@@ -330,7 +331,9 @@ files:
330
331
  - app/controllers/dscf/credit/credit_lines_controller.rb
331
332
  - app/controllers/dscf/credit/disbursements_controller.rb
332
333
  - app/controllers/dscf/credit/eligible_credit_lines_controller.rb
334
+ - app/controllers/dscf/credit/facilitator_applications_controller.rb
333
335
  - app/controllers/dscf/credit/facilitators_controller.rb
336
+ - app/controllers/dscf/credit/loan_applications_controller.rb
334
337
  - app/controllers/dscf/credit/loan_profiles_controller.rb
335
338
  - app/controllers/dscf/credit/loans_controller.rb
336
339
  - app/controllers/dscf/credit/parameter_normalizers_controller.rb
@@ -339,7 +342,6 @@ files:
339
342
  - app/controllers/dscf/credit/repayments_controller.rb
340
343
  - app/controllers/dscf/credit/scoring_param_types_controller.rb
341
344
  - app/controllers/dscf/credit/scoring_parameters_controller.rb
342
- - app/controllers/dscf/credit/scoring_tables_controller.rb
343
345
  - app/controllers/dscf/credit/system_config_definitions_controller.rb
344
346
  - app/controllers/dscf/credit/system_configs_controller.rb
345
347
  - app/controllers/dscf/credit/users_controller.rb
@@ -347,6 +349,7 @@ files:
347
349
  - app/mailers/dscf/credit/application_mailer.rb
348
350
  - app/mailers/dscf/credit/bank_staff_welcome_mailer.rb
349
351
  - app/mailers/dscf/credit/facilitator_mailer.rb
352
+ - app/models/concerns/core/reviewable_model.rb
350
353
  - app/models/dscf/credit/accounting_audit_request.rb
351
354
  - app/models/dscf/credit/accounting_entry.rb
352
355
  - app/models/dscf/credit/application_record.rb
@@ -359,9 +362,11 @@ files:
359
362
  - app/models/dscf/credit/daily_routine_transaction.rb
360
363
  - app/models/dscf/credit/eligible_credit_line.rb
361
364
  - app/models/dscf/credit/facilitator.rb
365
+ - app/models/dscf/credit/facilitator_application.rb
362
366
  - app/models/dscf/credit/facilitator_performance.rb
363
367
  - app/models/dscf/credit/failed_operations_log.rb
364
368
  - app/models/dscf/credit/loan.rb
369
+ - app/models/dscf/credit/loan_application.rb
365
370
  - app/models/dscf/credit/loan_profile.rb
366
371
  - app/models/dscf/credit/loan_profile_scoring_spec.rb
367
372
  - app/models/dscf/credit/loan_transaction.rb
@@ -370,7 +375,6 @@ files:
370
375
  - app/models/dscf/credit/payment_request.rb
371
376
  - app/models/dscf/credit/scoring_param_type.rb
372
377
  - app/models/dscf/credit/scoring_parameter.rb
373
- - app/models/dscf/credit/scoring_table.rb
374
378
  - app/models/dscf/credit/system_config.rb
375
379
  - app/models/dscf/credit/system_config_definition.rb
376
380
  - app/serializers/dscf/core/business_serializer.rb
@@ -387,8 +391,10 @@ files:
387
391
  - app/serializers/dscf/credit/credit_line_spec_serializer.rb
388
392
  - app/serializers/dscf/credit/daily_routine_transaction_serializer.rb
389
393
  - app/serializers/dscf/credit/eligible_credit_line_serializer.rb
394
+ - app/serializers/dscf/credit/facilitator_application_serializer.rb
390
395
  - app/serializers/dscf/credit/facilitator_performance_serializer.rb
391
396
  - app/serializers/dscf/credit/facilitator_serializer.rb
397
+ - app/serializers/dscf/credit/loan_application_serializer.rb
392
398
  - app/serializers/dscf/credit/loan_profile_scoring_spec_serializer.rb
393
399
  - app/serializers/dscf/credit/loan_profile_serializer.rb
394
400
  - app/serializers/dscf/credit/loan_serializer.rb
@@ -398,15 +404,17 @@ files:
398
404
  - app/serializers/dscf/credit/payment_serializer.rb
399
405
  - app/serializers/dscf/credit/scoring_param_type_serializer.rb
400
406
  - app/serializers/dscf/credit/scoring_parameter_serializer.rb
401
- - app/serializers/dscf/credit/scoring_table_serializer.rb
402
407
  - app/serializers/dscf/credit/system_config_definition_serializer.rb
403
408
  - app/serializers/dscf/credit/system_config_serializer.rb
404
409
  - app/services/dscf/credit/bank_staff_creation_service.rb
405
410
  - app/services/dscf/credit/credit_limit_calculation_service.rb
411
+ - app/services/dscf/credit/credit_scoring_engine.rb
406
412
  - app/services/dscf/credit/disbursement_service.rb
407
413
  - app/services/dscf/credit/facilitator_additional_info_service.rb
408
414
  - app/services/dscf/credit/facilitator_approval_service.rb
409
415
  - app/services/dscf/credit/facilitator_creation_service.rb
416
+ - app/services/dscf/credit/facility_limit_calculation_engine.rb
417
+ - app/services/dscf/credit/loan_profile_creation_service.rb
410
418
  - app/services/dscf/credit/repayment_service.rb
411
419
  - app/services/dscf/credit/risk_application_service.rb
412
420
  - app/services/dscf/credit/scoring_service.rb
@@ -434,8 +442,10 @@ files:
434
442
  - db/migrate/20250822092040_create_dscf_credit_scoring_param_types.rb
435
443
  - db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb
436
444
  - db/migrate/20250822092225_create_dscf_credit_parameter_normalizers.rb
445
+ - db/migrate/20250822092236_create_dscf_credit_loan_applications.rb
437
446
  - db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb
438
447
  - db/migrate/20250822092417_create_dscf_credit_loan_profile_scoring_specs.rb
448
+ - db/migrate/20250822092426_create_dscf_credit_facilitator_applications.rb
439
449
  - db/migrate/20250822092436_create_dscf_credit_facilitators.rb
440
450
  - db/migrate/20250822092528_create_dscf_credit_facilitator_performances.rb
441
451
  - db/migrate/20250822092608_create_dscf_credit_payment_requests.rb
@@ -447,7 +457,6 @@ files:
447
457
  - db/migrate/20250822092908_create_dscf_credit_failed_operations_logs.rb
448
458
  - db/migrate/20250822092936_create_dscf_credit_accounting_entries.rb
449
459
  - db/migrate/20250825231109_create_dscf_credit_bank_staff.rb
450
- - db/migrate/20250901172842_create_dscf_credit_scoring_tables.rb
451
460
  - db/migrate/20250917120000_create_dscf_credit_eligible_credit_lines.rb
452
461
  - db/seeds.rb
453
462
  - lib/dscf/credit.rb
@@ -464,9 +473,11 @@ files:
464
473
  - spec/factories/dscf/credit/credit_lines.rb
465
474
  - spec/factories/dscf/credit/daily_routine_transactions.rb
466
475
  - spec/factories/dscf/credit/eligible_credit_lines.rb
476
+ - spec/factories/dscf/credit/facilitator_applications.rb
467
477
  - spec/factories/dscf/credit/facilitator_performances.rb
468
478
  - spec/factories/dscf/credit/facilitators.rb
469
479
  - spec/factories/dscf/credit/failed_operations_logs.rb
480
+ - spec/factories/dscf/credit/loan_applications.rb
470
481
  - spec/factories/dscf/credit/loan_profile_scoring_specs.rb
471
482
  - spec/factories/dscf/credit/loan_profiles.rb
472
483
  - spec/factories/dscf/credit/loan_transactions.rb
@@ -476,7 +487,6 @@ files:
476
487
  - spec/factories/dscf/credit/payments.rb
477
488
  - spec/factories/dscf/credit/scoring_param_types.rb
478
489
  - spec/factories/dscf/credit/scoring_parameters.rb
479
- - spec/factories/dscf/credit/scoring_tables.rb
480
490
  - spec/factories/dscf/credit/system_config_definitions.rb
481
491
  - spec/factories/dscf/credit/system_configs.rb
482
492
  homepage: https://mksaddis.com/
@@ -1,112 +0,0 @@
1
- module Dscf
2
- module Credit
3
- module Reviewable
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- before_action :set_reviewable_resource, only: %i[approve reject request_modification resubmit]
8
- end
9
-
10
- # PATCH /resource/:id/approve
11
- def approve
12
- if reviewable_resource.update(
13
- status: :approved,
14
- reviewed_by: current_reviewer,
15
- review_date: Time.current
16
- )
17
- render_success(data: reviewable_resource)
18
- else
19
- render_error(errors: reviewable_resource.errors.full_messages)
20
- end
21
- end
22
-
23
- # PATCH /resource/:id/request_modification
24
- def request_modification
25
- feedback = safe_feedback_param! or return
26
-
27
- if reviewable_resource.update(
28
- status: :modify,
29
- review_feedback: feedback,
30
- reviewed_by: current_reviewer,
31
- review_date: Time.current
32
- )
33
- render_success(data: reviewable_resource)
34
- else
35
- render_error(errors: reviewable_resource.errors.full_messages)
36
- end
37
- end
38
-
39
- # PATCH /resource/:id/reject
40
- def reject
41
- feedback = safe_feedback_param! or return
42
-
43
- unless feedback.key?("message")
44
- return render_error(errors: "Provide review_feedback with a 'message' key")
45
- end
46
-
47
- if reviewable_resource.update(
48
- status: :rejected,
49
- review_feedback: feedback,
50
- reviewed_by: current_reviewer,
51
- review_date: Time.current
52
- )
53
- render_success(data: reviewable_resource)
54
- else
55
- render_error(errors: reviewable_resource.errors.full_messages)
56
- end
57
- end
58
-
59
- # PATCH /resource/:id/resubmit
60
- def resubmit
61
- unless reviewable_resource.status.to_s == "modify"
62
- return render_error(errors: "Only items with status 'modify' can be resubmitted", status: :unprocessable_entity)
63
- end
64
-
65
- updates = model_params.merge(
66
- status: :pending,
67
- review_feedback: {},
68
- )
69
-
70
- if reviewable_resource.update(updates)
71
- render_success(data: reviewable_resource)
72
- else
73
- render_error(errors: reviewable_resource.errors.full_messages)
74
- end
75
- end
76
-
77
- private
78
-
79
- def safe_feedback_param!
80
- feedback = params[:review_feedback]
81
- unless feedback.respond_to?(:to_unsafe_h)
82
- render_error(errors: "review_feedback must be a JSON object") and return
83
- end
84
- feedback.to_unsafe_h
85
- end
86
-
87
- def model_class
88
- model_name = controller_name.classify
89
-
90
- controller_namespace = self.class.name.deconstantize
91
- "#{controller_namespace}::#{model_name}".constantize
92
- rescue NameError
93
- model_name.constantize
94
- end
95
-
96
- def reviewable_resource
97
- @reviewable_resource ||= model_class.find(params[:id])
98
- end
99
-
100
- # Override if current_user is not the reviewer
101
- def current_reviewer
102
- current_user
103
- end
104
-
105
- def set_reviewable_resource
106
- reviewable_resource
107
- rescue ActiveRecord::RecordNotFound => e
108
- render_error("errors.record_not_found", status: :not_found, errors: [ e.message ])
109
- end
110
- end
111
- end
112
- end