dscf-credit 0.4.46 → 0.4.47

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/dscf/credit/categories_controller.rb +6 -8
  3. data/app/controllers/dscf/credit/credit_limit_calculations_controller.rb +3 -3
  4. data/app/controllers/dscf/credit/credit_lines_controller.rb +7 -7
  5. data/app/controllers/dscf/credit/credit_products_controller.rb +66 -0
  6. data/app/controllers/dscf/credit/loan_applications_controller.rb +15 -15
  7. data/app/controllers/dscf/credit/loan_profiles_controller.rb +5 -5
  8. data/app/controllers/dscf/credit/scoring_tables_controller.rb +4 -4
  9. data/app/models/dscf/credit/category.rb +2 -6
  10. data/app/models/dscf/credit/credit_line.rb +13 -3
  11. data/app/models/dscf/credit/credit_product.rb +26 -0
  12. data/app/models/dscf/credit/loan_application.rb +2 -2
  13. data/app/models/dscf/credit/scoring_table.rb +2 -2
  14. data/app/serializers/dscf/credit/category_serializer.rb +1 -3
  15. data/app/serializers/dscf/credit/credit_line_serializer.rb +1 -1
  16. data/app/serializers/dscf/credit/credit_product_serializer.rb +13 -0
  17. data/app/serializers/dscf/credit/loan_application_serializer.rb +1 -1
  18. data/app/serializers/dscf/credit/scoring_table_serializer.rb +1 -1
  19. data/app/services/dscf/credit/credit_scoring_engine.rb +4 -4
  20. data/app/services/dscf/credit/facility_limit_calculation_engine.rb +10 -10
  21. data/app/services/dscf/credit/loan_profile_creation_service.rb +3 -3
  22. data/config/locales/en.yml +24 -0
  23. data/config/routes.rb +9 -0
  24. data/db/dev_seeds.rb +49 -26
  25. data/db/migrate/20260219000003_create_dscf_credit_categories.rb +0 -6
  26. data/db/migrate/20260219000005_create_dscf_credit_credit_lines.rb +1 -1
  27. data/db/migrate/20260219000013_create_dscf_credit_loan_applications.rb +1 -1
  28. data/db/migrate/20260219000029_create_dscf_credit_credit_products.rb +19 -0
  29. data/db/seeds.rb +40 -8
  30. data/lib/dscf/credit/version.rb +1 -1
  31. data/spec/factories/dscf/credit/categories.rb +0 -1
  32. data/spec/factories/dscf/credit/credit_lines.rb +6 -2
  33. data/spec/factories/dscf/credit/credit_products.rb +31 -0
  34. data/spec/factories/dscf/credit/loan_applications.rb +1 -1
  35. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9f80c67743753180bfd10ef6eb0bc0306c52ee04eb452b3010a073e2ccd9057
4
- data.tar.gz: 123b03963cd6cc6b98395ed5d6172692e293b26194da7fb58d40ba8f5fc13753
3
+ metadata.gz: 6f7d46fc73d2d2bd02f7be0fd360644a226fa5ef8e8930431b4bce1fdf25f745
4
+ data.tar.gz: 949c51063d46aefe7ce91b6737873e283d03cc2cf184d5e489a18c307e3f29fd
5
5
  SHA512:
6
- metadata.gz: 83c4541282e5fb60ffb91ae403730a429e1cbceb72b0d500863582c142a551ce52eb59341b16c35b6e8842f15ff502975fe52fbd0c99a87958cfe8eae6da8612
7
- data.tar.gz: 0d2467ce32293e362a67ef9ece84127d17599157cf109d95e7bd6c21671188aaf52f3f85037464ab86f94512179cea8276ba0469e500feffea973d9f89828736
6
+ metadata.gz: d4424721c3aaa6bb0d83db13a8e73aece6c9fd13be5bfa0401ff40468959bc92692350610e3c9936d2d74fcf58912aaeb2e1d4607b2156b2de87c36e64815986
7
+ data.tar.gz: 74978d343b7053fbfba4eb93a6cdb343892cfa0d72bdf7161b16bad0109d2855da863b8f122df7772af4b076144fa26c659fb9ba824a3302544a048e3bf7f92a
@@ -10,26 +10,24 @@ module Dscf
10
10
  :bank_id,
11
11
  :category_type,
12
12
  :name,
13
- :description,
14
- :document_reference,
15
- :scoring_table_id
13
+ :description
16
14
  )
17
15
  end
18
16
 
19
17
  def eager_loaded_associations
20
- [ :bank, :scoring_table, :credit_lines, :scoring_parameters ]
18
+ [ :bank, :scoring_parameters ]
21
19
  end
22
20
 
23
21
  def allowed_order_columns
24
- %w[id category_type name description document_reference created_at updated_at]
22
+ %w[id category_type name description created_at updated_at]
25
23
  end
26
24
 
27
25
  def default_serializer_includes
28
26
  {
29
27
  index: [],
30
- show: [ :bank, :scoring_table, :credit_lines, :scoring_parameters ],
31
- create: [ :bank, :scoring_table ],
32
- update: [ :bank, :scoring_table, :credit_lines, :scoring_parameters ]
28
+ show: [ :bank, :scoring_parameters ],
29
+ create: [ :bank ],
30
+ update: [ :bank, :scoring_parameters ]
33
31
  }
34
32
  end
35
33
  end
@@ -2,9 +2,9 @@ module Dscf::Credit
2
2
  class CreditLimitCalculationsController < ApplicationController
3
3
  def create
4
4
  loan_profile = Dscf::Credit::LoanProfile.find(params[:loan_profile_id])
5
- category = Dscf::Credit::Category.find(params[:category_id])
5
+ credit_product = Dscf::Credit::CreditProduct.find(params[:credit_product_id])
6
6
 
7
- engine = Dscf::Credit::FacilityLimitCalculationEngine.new(loan_profile.id, category.id)
7
+ engine = Dscf::Credit::FacilityLimitCalculationEngine.new(loan_profile.id, credit_product.id)
8
8
  result = engine.calculate_facility_limits
9
9
 
10
10
  if result[:success]
@@ -13,7 +13,7 @@ module Dscf::Credit
13
13
  "credit_limit_calculation.success.create",
14
14
  data: {
15
15
  loan_profile: loan_profile,
16
- category: category,
16
+ credit_product: credit_product,
17
17
  eligible_credit_lines: result[:data],
18
18
  calculation_summary: {
19
19
  total_credit_lines_processed: result[:data]&.size || 0,
@@ -34,7 +34,7 @@ module Dscf::Credit
34
34
  def model_params
35
35
  params.require(:credit_line).permit(
36
36
  :bank_id,
37
- :category_id,
37
+ :credit_product_id,
38
38
  :name,
39
39
  :code,
40
40
  :description,
@@ -44,7 +44,7 @@ module Dscf::Credit
44
44
 
45
45
  def eager_loaded_associations
46
46
  [
47
- :bank, :category, :created_by, :loans, :eligible_credit_lines,
47
+ :bank, :credit_product, :created_by, :loans, :eligible_credit_lines,
48
48
  credit_line_specs: :base_scoring_parameter,
49
49
  reviews: { reviewed_by: :user_profile },
50
50
  audit_logs: :actor
@@ -52,21 +52,21 @@ module Dscf::Credit
52
52
  end
53
53
 
54
54
  def allowed_order_columns
55
- %w[id name code created_at updated_at category_id]
55
+ %w[id name code created_at updated_at credit_product_id]
56
56
  end
57
57
 
58
58
  def default_serializer_includes
59
59
  {
60
- index: [ :bank, :category, reviews: { reviewed_by: :user_profile } ],
60
+ index: [ :bank, :credit_product, reviews: { reviewed_by: :user_profile } ],
61
61
  show: [
62
- :bank, :category, :created_by, :loans, :eligible_credit_lines,
62
+ :bank, :credit_product, :created_by, :loans, :eligible_credit_lines,
63
63
  credit_line_specs: :base_scoring_parameter,
64
64
  reviews: { reviewed_by: :user_profile },
65
65
  audit_logs: :actor
66
66
  ],
67
- create: [ :bank, :category, :created_by, :reviews ],
67
+ create: [ :bank, :credit_product, :created_by, :reviews ],
68
68
  update: [
69
- :bank, :category, :created_by, :credit_line_specs, :loans, :eligible_credit_lines,
69
+ :bank, :credit_product, :created_by, :credit_line_specs, :loans, :eligible_credit_lines,
70
70
  reviews: { reviewed_by: :user_profile }
71
71
  ]
72
72
  }
@@ -0,0 +1,66 @@
1
+ module Dscf
2
+ module Credit
3
+ class CreditProductsController < ApplicationController
4
+ include Dscf::Core::Common
5
+ include Dscf::Core::ReviewableController
6
+ include Dscf::Core::AuditableController
7
+
8
+ auditable associated: [ :reviews ],
9
+ on: %i[approve submit reject request_modification resubmit]
10
+
11
+ auditable on: %i[create],
12
+ associated: { reviews: { only: [ :status ] } }
13
+
14
+ def create
15
+ super do
16
+ credit_product = @clazz.new(model_params)
17
+ credit_product.reviews.build(status: "draft", context: "default")
18
+ credit_product
19
+ end
20
+ end
21
+
22
+ def update
23
+ unless @obj.editable?
24
+ return render_error(
25
+ errors: [ "Cannot update credit product after submission. Use modification request workflow instead." ],
26
+ status: :unprocessable_entity
27
+ )
28
+ end
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ def model_params
35
+ params.require(:credit_product).permit(
36
+ :bank_id,
37
+ :scoring_table_id,
38
+ :name,
39
+ :description,
40
+ :document_reference
41
+ )
42
+ end
43
+
44
+ def eager_loaded_associations
45
+ [
46
+ :bank, :scoring_table, :credit_lines,
47
+ reviews: { reviewed_by: :user_profile },
48
+ audit_logs: :actor
49
+ ]
50
+ end
51
+
52
+ def allowed_order_columns
53
+ %w[id name description created_at updated_at bank_id]
54
+ end
55
+
56
+ def default_serializer_includes
57
+ {
58
+ index: [ :bank, reviews: { reviewed_by: :user_profile } ],
59
+ show: [ :bank, :scoring_table, :credit_lines, reviews: { reviewed_by: :user_profile }, audit_logs: :actor ],
60
+ create: [ :bank, :reviews ],
61
+ update: [ :bank, :scoring_table, reviews: { reviewed_by: :user_profile }, audit_logs: :actor ]
62
+ }
63
+ end
64
+ end
65
+ end
66
+ end
@@ -32,7 +32,7 @@ module Dscf::Credit
32
32
  end
33
33
 
34
34
  scoring_result_data = {
35
- scoring_table_id: loan_application.category&.scoring_table&.id,
35
+ scoring_table_id: loan_application.credit_product&.scoring_table&.id,
36
36
  scoring_input_data: input_snapshot,
37
37
  breakdown: {} # Breakdown unavailable for manually-approved pending applications
38
38
  }
@@ -100,11 +100,11 @@ module Dscf::Credit
100
100
  return render_error(errors: [ "Information source '#{source_code}' not found" ], status: :not_found)
101
101
  end
102
102
 
103
- category = @obj.category
104
- scoring_table = category&.scoring_table
103
+ credit_product = @obj.credit_product
104
+ scoring_table = credit_product&.scoring_table
105
105
 
106
106
  unless scoring_table
107
- return render_error(errors: [ "No scoring table configured for this loan application's category" ], status: :unprocessable_entity)
107
+ return render_error(errors: [ "No scoring table configured for this loan application's credit product" ], status: :unprocessable_entity)
108
108
  end
109
109
 
110
110
  # Load parameters for this source with normalizers
@@ -156,7 +156,7 @@ module Dscf::Credit
156
156
 
157
157
  if result[:success]
158
158
  scoring_result_data = {
159
- scoring_table_id: loan_application.category&.scoring_table&.id,
159
+ scoring_table_id: loan_application.credit_product&.scoring_table&.id,
160
160
  breakdown: result[:breakdown],
161
161
  scoring_input_data: build_scoring_input_snapshot(loan_application)
162
162
  }
@@ -249,7 +249,7 @@ module Dscf::Credit
249
249
 
250
250
  # Build feedback message based on status and score
251
251
  def build_review_feedback(status, score, loan_application = nil)
252
- scoring_table = loan_application&.category&.scoring_table || @obj&.category&.scoring_table
252
+ scoring_table = loan_application&.credit_product&.scoring_table || @obj&.credit_product&.scoring_table
253
253
  passing = scoring_table&.passing_score || 60.0
254
254
  pending_thresh = scoring_table&.pending_threshold || 50.0
255
255
 
@@ -275,13 +275,13 @@ module Dscf::Credit
275
275
  end
276
276
 
277
277
  def build_eligibility_response(loan_application)
278
- category = loan_application.category
279
- return [] unless category
278
+ credit_product = loan_application.credit_product
279
+ return [] unless credit_product
280
280
 
281
281
  score = loan_application.score.to_f
282
282
  loan_profile = loan_application.loan_profile
283
283
 
284
- category.credit_lines.includes(credit_line_specs: :base_scoring_parameter).map do |credit_line|
284
+ credit_product.credit_lines.includes(credit_line_specs: :base_scoring_parameter).map do |credit_line|
285
285
  spec = credit_line.credit_line_specs.active.order(created_at: :desc).first
286
286
  next unless spec
287
287
 
@@ -326,7 +326,7 @@ module Dscf::Credit
326
326
  def model_params
327
327
  params.require(:loan_application).permit(
328
328
  :bank_id,
329
- :category_id,
329
+ :credit_product_id,
330
330
  :backer_type,
331
331
  :backer_id,
332
332
  :review_branch_id,
@@ -337,7 +337,7 @@ module Dscf::Credit
337
337
 
338
338
  def eager_loaded_associations
339
339
  [
340
- :bank, :user, :backer, :review_branch, :loan_profile, :category,
340
+ :bank, :user, :backer, :review_branch, :loan_profile, :credit_product,
341
341
  loan_application_data: [ :information_source ],
342
342
  reviews: { reviewed_by: :user_profile },
343
343
  audit_logs: :actor
@@ -351,22 +351,22 @@ module Dscf::Credit
351
351
  def default_serializer_includes
352
352
  {
353
353
  index: [
354
- :bank, :user, :backer, :review_branch, :loan_profile, :category,
354
+ :bank, :user, :backer, :review_branch, :loan_profile, :credit_product,
355
355
  loan_application_data: [ :information_source ],
356
356
  reviews: { reviewed_by: :user_profile }
357
357
  ],
358
358
  show: [
359
- :bank, :user, :backer, :review_branch, :loan_profile, :category,
359
+ :bank, :user, :backer, :review_branch, :loan_profile, :credit_product,
360
360
  loan_application_data: [ :information_source ],
361
361
  reviews: { reviewed_by: :user_profile },
362
362
  audit_logs: :actor
363
363
  ],
364
364
  create: [
365
- :bank, :user, :backer, :review_branch, :loan_profile, :category, :reviews,
365
+ :bank, :user, :backer, :review_branch, :loan_profile, :credit_product, :reviews,
366
366
  { loan_application_data: [ :information_source ] }
367
367
  ],
368
368
  update: [
369
- :bank, :user, :backer, :review_branch, :loan_profile, :category,
369
+ :bank, :user, :backer, :review_branch, :loan_profile, :credit_product,
370
370
  loan_application_data: [ :information_source ],
371
371
  reviews: { reviewed_by: :user_profile }
372
372
  ]
@@ -27,15 +27,15 @@ module Dscf::Credit
27
27
 
28
28
  def calculate_facility_limits
29
29
  loan_profile = @clazz.find(params[:id])
30
- category_id = facility_params[:category_id]
30
+ credit_product_id = facility_params[:credit_product_id]
31
31
 
32
32
  return render_error(
33
33
  "loan_profile.errors.calculate_facility_limits",
34
- errors: [ "Category ID is required" ],
34
+ errors: [ "Credit Product ID is required" ],
35
35
  status: :unprocessable_entity
36
- ) unless category_id
36
+ ) unless credit_product_id
37
37
 
38
- facility_engine = FacilityLimitCalculationEngine.new(loan_profile.id, category_id)
38
+ facility_engine = FacilityLimitCalculationEngine.new(loan_profile.id, credit_product_id)
39
39
  result = facility_engine.calculate_facility_limits
40
40
 
41
41
  if result[:success]
@@ -110,7 +110,7 @@ module Dscf::Credit
110
110
  end
111
111
 
112
112
  def facility_params
113
- params.permit(:category_id)
113
+ params.permit(:credit_product_id)
114
114
  end
115
115
 
116
116
  def eager_loaded_associations
@@ -69,7 +69,7 @@ module Dscf::Credit
69
69
 
70
70
  def eager_loaded_associations
71
71
  [
72
- :bank, :created_by, :parent_template, :scoring_table_parameters, :categories,
72
+ :bank, :created_by, :parent_template, :scoring_table_parameters, :credit_products,
73
73
  reviews: { reviewed_by: :user_profile },
74
74
  audit_logs: :actor
75
75
  ]
@@ -81,15 +81,15 @@ module Dscf::Credit
81
81
 
82
82
  def default_serializer_includes
83
83
  {
84
- index: [ :bank, :categories, reviews: { reviewed_by: :user_profile } ],
84
+ index: [ :bank, :credit_products, reviews: { reviewed_by: :user_profile } ],
85
85
  show: [
86
- :bank, :created_by, :parent_template, :scoring_table_parameters, :categories,
86
+ :bank, :created_by, :parent_template, :scoring_table_parameters, :credit_products,
87
87
  reviews: { reviewed_by: :user_profile },
88
88
  audit_logs: :actor
89
89
  ],
90
90
  create: [ :bank, :created_by, :reviews ],
91
91
  update: [
92
- :bank, :created_by, :categories,
92
+ :bank, :created_by, :credit_products,
93
93
  reviews: { reviewed_by: :user_profile }
94
94
  ]
95
95
  }
@@ -4,10 +4,6 @@ module Dscf
4
4
  self.table_name = "dscf_credit_categories"
5
5
 
6
6
  belongs_to :bank, class_name: "Dscf::Credit::Bank", optional: true
7
- belongs_to :scoring_table, class_name: "Dscf::Credit::ScoringTable",
8
- foreign_key: "scoring_table_id", optional: true
9
- has_many :credit_lines, class_name: "Dscf::Credit::CreditLine",
10
- foreign_key: "category_id", dependent: :nullify
11
7
  has_many :scoring_parameters, class_name: "Dscf::Credit::ScoringParameter",
12
8
  foreign_key: "category_id", dependent: :nullify
13
9
 
@@ -18,11 +14,11 @@ module Dscf
18
14
  scope :by_bank, ->(bank_id) { where(bank_id: bank_id) }
19
15
 
20
16
  def self.ransackable_attributes(auth_object = nil)
21
- %w[id category_type name description document_reference bank_id created_at updated_at]
17
+ %w[id category_type name description bank_id created_at updated_at]
22
18
  end
23
19
 
24
20
  def self.ransackable_associations(auth_object = nil)
25
- %w[bank credit_lines scoring_table scoring_parameters]
21
+ %w[bank scoring_parameters]
26
22
  end
27
23
  end
28
24
  end
@@ -6,23 +6,33 @@ module Dscf::Credit
6
6
  include Dscf::Core::AuditableModel
7
7
 
8
8
  belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
9
- belongs_to :category, class_name: "Dscf::Credit::Category", foreign_key: "category_id"
9
+ belongs_to :credit_product, class_name: "Dscf::Credit::CreditProduct", foreign_key: "credit_product_id"
10
10
  belongs_to :created_by, polymorphic: true
11
11
 
12
12
  has_many :credit_line_specs, class_name: "Dscf::Credit::CreditLineSpec", foreign_key: "credit_line_id", dependent: :destroy
13
13
  has_many :loans, class_name: "Dscf::Credit::Loan", foreign_key: "credit_line_id", dependent: :destroy
14
14
  has_many :eligible_credit_lines, class_name: "Dscf::Credit::EligibleCreditLine", foreign_key: "credit_line_id", dependent: :destroy
15
- has_many :scoring_parameters, through: :category, source: :scoring_parameters
16
15
 
17
16
  validates :name, presence: true
18
17
  validates :code, uniqueness: { scope: :bank_id }, allow_blank: true
18
+ validate :credit_product_must_be_approved, if: -> { credit_product_id_changed? || new_record? }
19
19
 
20
20
  def self.ransackable_attributes(auth_object = nil)
21
21
  %w[id name code description document_reference created_at updated_at]
22
22
  end
23
23
 
24
24
  def self.ransackable_associations(auth_object = nil)
25
- %w[bank category created_by credit_line_specs loans eligible_credit_lines scoring_parameters reviews]
25
+ %w[bank credit_product created_by credit_line_specs loans eligible_credit_lines reviews]
26
+ end
27
+
28
+ private
29
+
30
+ def credit_product_must_be_approved
31
+ return unless credit_product
32
+
33
+ unless credit_product.approved?
34
+ errors.add(:credit_product, "must be approved before creating a credit line")
35
+ end
26
36
  end
27
37
  end
28
38
  end
@@ -0,0 +1,26 @@
1
+ module Dscf
2
+ module Credit
3
+ class CreditProduct < ApplicationRecord
4
+ self.table_name = "dscf_credit_credit_products"
5
+
6
+ include Dscf::Core::ReviewableModel
7
+ include Dscf::Core::AuditableModel
8
+
9
+ belongs_to :bank, class_name: "Dscf::Credit::Bank"
10
+ belongs_to :scoring_table, class_name: "Dscf::Credit::ScoringTable", optional: true
11
+ has_many :credit_lines, class_name: "Dscf::Credit::CreditLine",
12
+ foreign_key: :credit_product_id, dependent: :nullify
13
+
14
+ validates :name, presence: true, uniqueness: { scope: :bank_id }
15
+ validates :bank, presence: true
16
+
17
+ def self.ransackable_attributes(auth_object = nil)
18
+ %w[id name description document_reference bank_id scoring_table_id created_at updated_at]
19
+ end
20
+
21
+ def self.ransackable_associations(auth_object = nil)
22
+ %w[bank scoring_table credit_lines reviews audit_logs]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -11,7 +11,7 @@ module Dscf::Credit
11
11
  end
12
12
  belongs_to :backer, polymorphic: true
13
13
  belongs_to :review_branch, class_name: "Dscf::Credit::BankBranch", foreign_key: "review_branch_id"
14
- belongs_to :category, class_name: "Dscf::Credit::Category", foreign_key: "category_id", optional: true
14
+ belongs_to :credit_product, class_name: "Dscf::Credit::CreditProduct", foreign_key: "credit_product_id", optional: true
15
15
 
16
16
  has_one :loan_profile, class_name: "Dscf::Credit::LoanProfile", foreign_key: "loan_application_id", dependent: :destroy
17
17
  has_many :loan_application_data, class_name: "Dscf::Credit::LoanApplicationDatum", foreign_key: "loan_application_id", dependent: :destroy
@@ -26,7 +26,7 @@ module Dscf::Credit
26
26
  end
27
27
 
28
28
  def self.ransackable_associations(auth_object = nil)
29
- %w[bank user backer review_branch category loan_profile loan_application_data reviews audit_logs]
29
+ %w[bank user backer review_branch credit_product loan_profile loan_application_data reviews audit_logs]
30
30
  end
31
31
  end
32
32
  end
@@ -11,7 +11,7 @@ module Dscf::Credit
11
11
  has_many :scoring_table_parameters, class_name: "Dscf::Credit::ScoringTableParameter", foreign_key: "scoring_table_id", dependent: :destroy
12
12
  has_many :scoring_parameters, through: :scoring_table_parameters
13
13
  has_many :scoring_results, class_name: "Dscf::Credit::ScoringResult", foreign_key: "scoring_table_id"
14
- has_many :categories, class_name: "Dscf::Credit::Category", foreign_key: "scoring_table_id"
14
+ has_many :credit_products, class_name: "Dscf::Credit::CreditProduct", foreign_key: "scoring_table_id"
15
15
 
16
16
  validates :code, presence: true, uniqueness: true
17
17
  validates :name, presence: true, uniqueness: { scope: :bank_id }
@@ -28,7 +28,7 @@ module Dscf::Credit
28
28
  end
29
29
 
30
30
  def self.ransackable_associations(auth_object = nil)
31
- %w[bank created_by parent_template scoring_table_parameters scoring_parameters scoring_results categories reviews audit_logs]
31
+ %w[bank created_by parent_template scoring_table_parameters scoring_parameters scoring_results credit_products reviews audit_logs]
32
32
  end
33
33
 
34
34
  private
@@ -1,11 +1,9 @@
1
1
  module Dscf
2
2
  module Credit
3
3
  class CategorySerializer < ActiveModel::Serializer
4
- attributes :id, :category_type, :name, :description, :document_reference, :created_at, :updated_at
4
+ attributes :id, :category_type, :name, :description, :created_at, :updated_at
5
5
 
6
6
  belongs_to :bank, serializer: Dscf::Credit::BankSerializer
7
- belongs_to :scoring_table, serializer: Dscf::Credit::ScoringTableSerializer
8
- has_many :credit_lines, serializer: Dscf::Credit::CreditLineSerializer
9
7
  has_many :scoring_parameters, serializer: Dscf::Credit::ScoringParameterSerializer
10
8
  end
11
9
  end
@@ -4,7 +4,7 @@ module Dscf::Credit
4
4
  :created_at, :updated_at
5
5
 
6
6
  belongs_to :bank, serializer: Dscf::Credit::BankSerializer
7
- belongs_to :category, serializer: Dscf::Credit::CategorySerializer
7
+ belongs_to :credit_product, serializer: Dscf::Credit::CreditProductSerializer
8
8
  belongs_to :created_by, serializer: Dscf::Core::UserSerializer
9
9
  has_many :credit_line_specs, serializer: Dscf::Credit::CreditLineSpecSerializer
10
10
  has_many :loans, serializer: Dscf::Credit::LoanSerializer
@@ -0,0 +1,13 @@
1
+ module Dscf
2
+ module Credit
3
+ class CreditProductSerializer < ActiveModel::Serializer
4
+ attributes :id, :name, :description, :document_reference, :created_at, :updated_at
5
+
6
+ belongs_to :bank, serializer: Dscf::Credit::BankSerializer
7
+ belongs_to :scoring_table, serializer: Dscf::Credit::ScoringTableSerializer
8
+ has_many :credit_lines, serializer: Dscf::Credit::CreditLineSerializer
9
+ has_many :reviews, serializer: Dscf::Core::ReviewSerializer
10
+ has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
11
+ end
12
+ end
13
+ end
@@ -6,7 +6,7 @@ module Dscf::Credit
6
6
  belongs_to :user, serializer: Dscf::Core::UserSerializer
7
7
  belongs_to :backer, polymorphic: true
8
8
  belongs_to :review_branch, serializer: Dscf::Credit::BankBranchSerializer
9
- belongs_to :category, serializer: Dscf::Credit::CategorySerializer
9
+ belongs_to :credit_product, serializer: Dscf::Credit::CreditProductSerializer
10
10
  has_many :reviews, serializer: Dscf::Core::ReviewSerializer
11
11
  has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
12
12
  has_many :loan_application_data, serializer: Dscf::Credit::LoanApplicationDatumSerializer
@@ -7,7 +7,7 @@ module Dscf::Credit
7
7
  belongs_to :bank, serializer: Dscf::Credit::BankSerializer
8
8
  belongs_to :created_by, polymorphic: true
9
9
  belongs_to :parent_template, serializer: Dscf::Credit::ScoringTableSerializer
10
- has_many :categories, serializer: Dscf::Credit::CategorySerializer
10
+ has_many :credit_products, serializer: Dscf::Credit::CreditProductSerializer
11
11
  has_many :scoring_table_parameters
12
12
  has_many :reviews, serializer: Dscf::Core::ReviewSerializer
13
13
  has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
@@ -2,14 +2,14 @@ module Dscf::Credit
2
2
  class CreditScoringEngine
3
3
  def initialize(loan_application)
4
4
  @loan_application = loan_application
5
- @category = loan_application.category
6
- @scoring_table = @category&.scoring_table
5
+ @credit_product = loan_application.credit_product
6
+ @scoring_table = @credit_product&.scoring_table
7
7
  @data_by_source_id = load_data_by_source
8
8
  end
9
9
 
10
10
  def calculate_score
11
- return failure("No category assigned to loan application") unless @category
12
- return failure("No scoring table configured for category '#{@category.name}'") unless @scoring_table
11
+ return failure("No credit product assigned to loan application") unless @credit_product
12
+ return failure("No scoring table configured for credit product '#{@credit_product.name}'") unless @scoring_table
13
13
 
14
14
  parameters = @scoring_table.scoring_table_parameters
15
15
  .includes(:scoring_parameter, :scoring_table_normalizers, :information_source)
@@ -1,18 +1,18 @@
1
1
  module Dscf::Credit
2
2
  class FacilityLimitCalculationEngine
3
- attr_reader :loan_profile, :category_id, :errors
3
+ attr_reader :loan_profile, :credit_product_id, :errors
4
4
 
5
- def initialize(loan_profile_id, category_id)
5
+ def initialize(loan_profile_id, credit_product_id)
6
6
  @errors = []
7
7
  @loan_profile = LoanProfile.find(loan_profile_id)
8
- @category_id = category_id
8
+ @credit_product_id = credit_product_id
9
9
  rescue ActiveRecord::RecordNotFound => e
10
10
  @errors << "Loan profile not found: \#{e.message}"
11
11
  end
12
12
 
13
13
  def calculate_facility_limits
14
14
  return error_result("Loan profile is required") unless loan_profile
15
- return error_result("Category ID is required") unless category_id
15
+ return error_result("Credit Product ID is required") unless credit_product_id
16
16
 
17
17
  begin
18
18
  ActiveRecord::Base.transaction do
@@ -22,7 +22,7 @@ module Dscf::Credit
22
22
 
23
23
  credit_lines = get_credit_lines_for_calculation
24
24
  if credit_lines.empty?
25
- return error_result("No credit lines found for category \#{category_id} and bank \#{loan_profile.loan_application.bank_id}")
25
+ return error_result("No credit lines found for credit product #{credit_product_id} and bank #{loan_profile.loan_application.bank_id}")
26
26
  end
27
27
 
28
28
  eligible_credit_lines = []
@@ -67,9 +67,9 @@ module Dscf::Credit
67
67
  private
68
68
 
69
69
  def get_credit_lines_for_calculation
70
- CreditLine.joins(:category, :credit_line_specs)
70
+ CreditLine.joins(:credit_product, :credit_line_specs)
71
71
  .where(
72
- category_id: category_id,
72
+ credit_product_id: credit_product_id,
73
73
  bank_id: loan_profile.loan_application.bank_id,
74
74
  dscf_credit_credit_line_specs: { active: true }
75
75
  )
@@ -161,9 +161,9 @@ module Dscf::Credit
161
161
  end
162
162
 
163
163
  def get_scoring_table
164
- category = Category.find_by(id: category_id)
165
- return nil unless category&.scoring_table_id
166
- ScoringTable.find_by(id: category.scoring_table_id)
164
+ credit_product = CreditProduct.find_by(id: credit_product_id)
165
+ return nil unless credit_product&.scoring_table_id
166
+ ScoringTable.find_by(id: credit_product.scoring_table_id)
167
167
  end
168
168
 
169
169
  def update_loan_profile_total_limit
@@ -82,10 +82,10 @@ module Dscf::Credit
82
82
  end
83
83
 
84
84
  def calculate_facility_limits(loan_profile)
85
- category = @loan_application.category
86
- return unless category
85
+ credit_product = @loan_application.credit_product
86
+ return unless credit_product
87
87
 
88
- engine = FacilityLimitCalculationEngine.new(loan_profile.id, category.id)
88
+ engine = FacilityLimitCalculationEngine.new(loan_profile.id, credit_product.id)
89
89
  result = engine.calculate_facility_limits
90
90
 
91
91
  unless result[:success]
@@ -179,6 +179,30 @@ en:
179
179
  update: "Failed to update credit line specification"
180
180
  destroy: "Failed to delete credit line specification"
181
181
 
182
+ credit_product:
183
+ success:
184
+ index: "Credit products retrieved successfully"
185
+ show: "Credit product details retrieved successfully"
186
+ create: "Credit product created successfully"
187
+ update: "Credit product updated successfully"
188
+ submit: "Credit product submitted for review successfully"
189
+ approve: "Credit product approved successfully"
190
+ reject: "Credit product rejected successfully"
191
+ destroy: "Credit product deleted successfully"
192
+ request_modification: "Modification requested for credit product successfully"
193
+ resubmit: "Credit product resubmitted successfully"
194
+ errors:
195
+ index: "Failed to retrieve credit products"
196
+ show: "Failed to retrieve credit product details"
197
+ create: "Failed to create credit product"
198
+ update: "Failed to update credit product"
199
+ submit: "Failed to submit credit product for review"
200
+ approve: "Failed to approve credit product"
201
+ reject: "Failed to reject credit product"
202
+ destroy: "Failed to delete credit product"
203
+ request_modification: "Failed to request modification for credit product"
204
+ resubmit: "Failed to resubmit credit product"
205
+
182
206
  credit_line:
183
207
  success:
184
208
  index: "Credit lines retrieved successfully"
data/config/routes.rb CHANGED
@@ -56,6 +56,15 @@ Dscf::Credit::Engine.routes.draw do
56
56
  end
57
57
  end
58
58
  resources :system_config_definitions
59
+ resources :credit_products do
60
+ member do
61
+ patch "submit"
62
+ patch "approve"
63
+ patch "reject"
64
+ patch "request_modification"
65
+ patch "resubmit"
66
+ end
67
+ end
59
68
  resources :credit_lines do
60
69
  member do
61
70
  patch "submit"
data/db/dev_seeds.rb CHANGED
@@ -207,17 +207,38 @@ dev_category = Dscf::Credit::Category.find_or_create_by!(
207
207
  category_type: "credit_line",
208
208
  name: "DEV Retail"
209
209
  ) do |c|
210
- c.description = "[DEV] Retail category for dev seed scenarios — do not use in production"
211
- c.bank = bunna_bank
212
- c.scoring_table = dev_scoring_table
210
+ c.description = "[DEV] Retail category for dev seed scenarios — do not use in production"
211
+ c.bank = bunna_bank
213
212
  end
214
213
 
215
- # Ensure scoring_table is linked (idempotency: update if missing)
216
- if dev_category.scoring_table_id.nil?
217
- dev_category.update!(scoring_table: dev_scoring_table)
214
+ puts "✓ DEV Category created (id=#{dev_category.id}, name=#{dev_category.name})"
215
+
216
+ # =============================================================================
217
+ # 2.5 DEV CREDIT PRODUCT
218
+ # Wraps the scoring table — CreditLines belong to CreditProduct, not Category
219
+ # Category is now purely an organizer for ScoringParameter records
220
+ # =============================================================================
221
+ puts ""
222
+ puts "--- [2.5] DEV Credit Product ---"
223
+
224
+ dev_credit_product = Dscf::Credit::CreditProduct.find_or_create_by!(
225
+ bank: bunna_bank,
226
+ name: "DEV Retail Credit Product"
227
+ ) do |cp|
228
+ cp.scoring_table = dev_scoring_table
229
+ cp.description = "[DEV] Credit product linking DEV Retailer Scoring to dev credit lines"
230
+ cp.document_reference = "DEV-CP-RET-001"
231
+ end
232
+
233
+ if defined?(Dscf::Core::Review)
234
+ Dscf::Core::Review.find_or_create_by!(reviewable: dev_credit_product, context: "default") do |r|
235
+ r.status = "approved"
236
+ r.reviewed_by = admin_user
237
+ r.reviewed_at = Time.current
238
+ end
218
239
  end
219
240
 
220
- puts "✓ DEV Category created (id=#{dev_category.id}, scoring_table_id=#{dev_category.scoring_table_id})"
241
+ puts "✓ DEV Credit Product created (id=#{dev_credit_product.id}, scoring_table=#{dev_scoring_table.code})"
221
242
 
222
243
  # =============================================================================
223
244
  # 3. DEV SCORING PARAMETERS (4 parameters, all under dev_category)
@@ -494,10 +515,10 @@ credit_line_30d = Dscf::Credit::CreditLine.find_or_create_by!(
494
515
  bank: bunna_bank,
495
516
  code: "DEV-RET-30D"
496
517
  ) do |cl|
497
- cl.name = "DEV 30-Day Retail Credit"
498
- cl.category = dev_category
499
- cl.description = "[DEV] 30-day short-term retail supplier credit"
500
- cl.created_by = admin_user
518
+ cl.name = "DEV 30-Day Retail Credit"
519
+ cl.credit_product = dev_credit_product
520
+ cl.description = "[DEV] 30-day short-term retail supplier credit"
521
+ cl.created_by = admin_user
501
522
  end
502
523
 
503
524
  if defined?(Dscf::Core::Review)
@@ -536,10 +557,10 @@ credit_line_90d = Dscf::Credit::CreditLine.find_or_create_by!(
536
557
  bank: bunna_bank,
537
558
  code: "DEV-RET-90D"
538
559
  ) do |cl|
539
- cl.name = "DEV 90-Day Retail Credit"
540
- cl.category = dev_category
541
- cl.description = "[DEV] 90-day medium-term retail supplier credit"
542
- cl.created_by = admin_user
560
+ cl.name = "DEV 90-Day Retail Credit"
561
+ cl.credit_product = dev_credit_product
562
+ cl.description = "[DEV] 90-day medium-term retail supplier credit"
563
+ cl.created_by = admin_user
543
564
  end
544
565
 
545
566
  if defined?(Dscf::Core::Review)
@@ -578,10 +599,10 @@ credit_line_180d = Dscf::Credit::CreditLine.find_or_create_by!(
578
599
  bank: bunna_bank,
579
600
  code: "DEV-WHO-180D"
580
601
  ) do |cl|
581
- cl.name = "DEV 180-Day Wholesale Credit"
582
- cl.category = dev_category
583
- cl.description = "[DEV] 180-day long-term wholesale distributor credit"
584
- cl.created_by = admin_user
602
+ cl.name = "DEV 180-Day Wholesale Credit"
603
+ cl.credit_product = dev_credit_product
604
+ cl.description = "[DEV] 180-day long-term wholesale distributor credit"
605
+ cl.created_by = admin_user
585
606
  end
586
607
 
587
608
  if defined?(Dscf::Core::Review)
@@ -763,8 +784,8 @@ else
763
784
  review_branch: dev_branch,
764
785
  bank_statement_source: "internal"
765
786
  ) do |app|
766
- app.backer = dev_facilitator
767
- app.category = dev_category
787
+ app.backer = dev_facilitator
788
+ app.credit_product = dev_credit_product
768
789
  end
769
790
 
770
791
  # Score is set directly (mirrors what scoring service would write)
@@ -815,8 +836,8 @@ else
815
836
  review_branch: dev_branch,
816
837
  bank_statement_source: "internal"
817
838
  ) do |app|
818
- app.backer = dev_facilitator
819
- app.category = dev_category
839
+ app.backer = dev_facilitator
840
+ app.credit_product = dev_credit_product
820
841
  end
821
842
 
822
843
  loan_app_mekdes.update!(score: 47.50) if loan_app_mekdes.score != 47.50
@@ -865,8 +886,8 @@ else
865
886
  review_branch: dev_branch,
866
887
  bank_statement_source: "internal"
867
888
  ) do |app|
868
- app.backer = dev_facilitator
869
- app.category = dev_category
889
+ app.backer = dev_facilitator
890
+ app.credit_product = dev_credit_product
870
891
  # Score deliberately left nil — scoring not yet triggered (draft state)
871
892
  end
872
893
 
@@ -1199,7 +1220,8 @@ puts " DEV SEEDS COMPLETE"
1199
1220
  puts "=" * 70
1200
1221
  puts ""
1201
1222
  puts " Scoring Table: DEV Retailer Scoring (dev_retailer_v1)"
1202
- puts " Category: DEV Retail"
1223
+ puts " Category: DEV Retail (scoring parameter organizer only)"
1224
+ puts " Credit Product: DEV Retail Credit Product (links scoring table to credit lines)"
1203
1225
  puts " Parameters: #{Dscf::Credit::ScoringParameter.where("code LIKE 'dev_%'").count} dev parameters"
1204
1226
  puts " Credit Lines: DEV-RET-30D, DEV-RET-90D, DEV-WHO-180D"
1205
1227
  puts ""
@@ -1223,6 +1245,7 @@ end
1223
1245
  puts " Banks: #{Dscf::Credit::Bank.count}"
1224
1246
  puts " Credit Lines: #{Dscf::Credit::CreditLine.count}"
1225
1247
  puts " Scoring Tables: #{Dscf::Credit::ScoringTable.count}"
1248
+ puts " Credit Products: #{Dscf::Credit::CreditProduct.count}"
1226
1249
  puts " Scoring Parameters: #{Dscf::Credit::ScoringParameter.count}"
1227
1250
  puts " Info Sources: #{Dscf::Credit::InformationSource.count}"
1228
1251
  puts " Loan Applications: #{Dscf::Credit::LoanApplication.count}"
@@ -6,18 +6,12 @@ class CreateDscfCreditCategories < ActiveRecord::Migration[8.0]
6
6
  t.string :category_type, null: false
7
7
  t.string :name, null: false
8
8
  t.text :description
9
- t.string :document_reference
10
- t.references :scoring_table, null: true,
11
- foreign_key: { to_table: :dscf_credit_scoring_tables }
12
9
 
13
10
  t.timestamps
14
11
  end
15
12
 
16
13
  add_index :dscf_credit_categories, :category_type
17
14
  add_index :dscf_credit_categories, :name
18
- add_index :dscf_credit_categories, :document_reference
19
15
  add_index :dscf_credit_categories, [ :category_type, :name ], unique: true
20
- add_index :dscf_credit_categories, [ :category_type, :scoring_table_id ],
21
- name: 'idx_categories_type_scoring_table'
22
16
  end
23
17
  end
@@ -2,7 +2,7 @@ class CreateDscfCreditCreditLines < ActiveRecord::Migration[8.0]
2
2
  def change
3
3
  create_table :dscf_credit_credit_lines do |t|
4
4
  t.references :bank, null: false, foreign_key: { to_table: :dscf_credit_banks }
5
- t.references :category, null: false, foreign_key: { to_table: :dscf_credit_categories }
5
+ t.references :credit_product, null: false
6
6
  t.string :name, null: false
7
7
  t.string :code
8
8
  t.text :description
@@ -5,7 +5,7 @@ class CreateDscfCreditLoanApplications < ActiveRecord::Migration[8.0]
5
5
  t.references :user, null: false, foreign_key: { to_table: :dscf_core_users }
6
6
  t.references :backer, polymorphic: true
7
7
  t.references :review_branch, null: false, foreign_key: { to_table: :dscf_credit_bank_branches }
8
- t.references :category, null: true, foreign_key: { to_table: :dscf_credit_categories }
8
+ t.references :credit_product, null: true
9
9
  t.string :bank_statement_source
10
10
  t.decimal :score, precision: 15, scale: 2
11
11
 
@@ -0,0 +1,19 @@
1
+ class CreateDscfCreditCreditProducts < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :dscf_credit_credit_products do |t|
4
+ t.references :bank, null: false,
5
+ foreign_key: { to_table: :dscf_credit_banks }
6
+ t.references :scoring_table, null: true,
7
+ foreign_key: { to_table: :dscf_credit_scoring_tables }
8
+ t.string :name, null: false
9
+ t.text :description
10
+ t.string :document_reference
11
+
12
+ t.timestamps
13
+ end
14
+
15
+ add_index :dscf_credit_credit_products, [ :name, :bank_id ], unique: true
16
+ add_foreign_key :dscf_credit_credit_lines, :dscf_credit_credit_products, column: :credit_product_id
17
+ add_foreign_key :dscf_credit_loan_applications, :dscf_credit_credit_products, column: :credit_product_id
18
+ end
19
+ end
data/db/seeds.rb CHANGED
@@ -432,24 +432,55 @@ end
432
432
 
433
433
  puts "✓ #{Dscf::Credit::ScoringTable.count} scoring tables seeded"
434
434
 
435
- # 5. Credit Lines (depends on banks, users, and categories)
435
+ # 5. Credit Products (depends on banks and scoring tables)
436
+ puts "Seeding credit products..."
437
+ sme_credit_product = Dscf::Credit::CreditProduct.find_or_create_by!(bank: bunna_bank, name: 'SME Credit Product') do |cp|
438
+ cp.scoring_table = scoring_table_retailer
439
+ cp.description = 'Credit product for Small and Medium Enterprises'
440
+ cp.document_reference = 'DSCF-CP-SME-001'
441
+ end
442
+
443
+ personal_credit_product = Dscf::Credit::CreditProduct.find_or_create_by!(bank: bunna_bank, name: 'Personal Credit Product') do |cp|
444
+ cp.scoring_table = scoring_table_retailer
445
+ cp.description = 'Credit product for individual borrowers'
446
+ cp.document_reference = 'DSCF-CP-PER-001'
447
+ end
448
+
449
+ agricultural_credit_product = Dscf::Credit::CreditProduct.find_or_create_by!(bank: bunna_bank, name: 'Agricultural Credit Product') do |cp|
450
+ cp.scoring_table = scoring_table_wholesale
451
+ cp.description = 'Credit product for agricultural sector operators'
452
+ cp.document_reference = 'DSCF-CP-AGR-001'
453
+ end
454
+
455
+ # Create approved reviews for credit products
456
+ [ sme_credit_product, personal_credit_product, agricultural_credit_product ].each do |cp|
457
+ Dscf::Core::Review.find_or_create_by(reviewable: cp, context: 'default') do |r|
458
+ r.status = 'approved'
459
+ r.reviewed_by = admin_user
460
+ r.reviewed_at = Time.current
461
+ end
462
+ end
463
+
464
+ puts "✓ #{Dscf::Credit::CreditProduct.count} credit products seeded"
465
+
466
+ # 6. Credit Lines (depends on banks, users, and credit products)
436
467
  puts "Seeding credit lines..."
437
468
  credit_line1 = Dscf::Credit::CreditLine.find_or_create_by(bank: bunna_bank, name: 'SME Credit Line') do |line|
438
- line.category = sme_category
469
+ line.credit_product = sme_credit_product
439
470
  line.code = 'BUN-SME-001'
440
471
  line.description = 'Credit line for Small and Medium Enterprises'
441
472
  line.created_by = admin_user
442
473
  end
443
474
 
444
475
  credit_line2 = Dscf::Credit::CreditLine.find_or_create_by(bank: bunna_bank, name: 'Personal Credit Line') do |line|
445
- line.category = personal_category
476
+ line.credit_product = personal_credit_product
446
477
  line.code = 'BUN-PER-002'
447
478
  line.description = 'Personal credit line for individuals'
448
479
  line.created_by = admin_user
449
480
  end
450
481
 
451
482
  Dscf::Credit::CreditLine.find_or_create_by(bank: bunna_bank, name: 'Agricultural Credit Line') do |line|
452
- line.category = agricultural_category
483
+ line.credit_product = agricultural_credit_product
453
484
  line.code = 'BUN-AGR-001'
454
485
  line.description = 'Credit line for agricultural sector'
455
486
  line.created_by = bank_admin
@@ -800,8 +831,8 @@ loan_application1 = Dscf::Credit::LoanApplication.find_or_create_by!(
800
831
  review_branch: bunna_head_office,
801
832
  bank_statement_source: "internal"
802
833
  ) do |app|
803
- app.backer = facilitator1
804
- app.category = sme_category
834
+ app.backer = facilitator1
835
+ app.credit_product = sme_credit_product
805
836
  end
806
837
 
807
838
  # Create draft review for loan_application1
@@ -819,8 +850,8 @@ loan_application2 = Dscf::Credit::LoanApplication.find_or_create_by!(
819
850
  review_branch: bunna_head_office,
820
851
  bank_statement_source: "external"
821
852
  ) do |app|
822
- app.backer = facilitator1
823
- app.category = personal_category
853
+ app.backer = facilitator1
854
+ app.credit_product = personal_credit_product
824
855
  end
825
856
 
826
857
  # Create draft review for loan_application2
@@ -1045,6 +1076,7 @@ puts "- Banks: #{Dscf::Credit::Bank.count}"
1045
1076
  puts "- Bank Branches: #{Dscf::Credit::BankBranch.count}"
1046
1077
  puts "- Bank Staff: #{Dscf::Credit::BankStaff.count}"
1047
1078
  puts "- Categories: #{Dscf::Credit::Category.count}"
1079
+ puts "- Credit Products: #{Dscf::Credit::CreditProduct.count}"
1048
1080
  puts "- Credit Lines: #{Dscf::Credit::CreditLine.count}"
1049
1081
  puts "- Loan Profiles: #{Dscf::Credit::LoanProfile.count}"
1050
1082
  puts "- Scoring Results: #{Dscf::Credit::ScoringResult.count}"
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Credit
3
- VERSION = "0.4.46"
3
+ VERSION = "0.4.47"
4
4
  end
5
5
  end
@@ -4,7 +4,6 @@ FactoryBot.define do
4
4
  sequence(:category_type) { |n| %w[scoring loan_profile payment risk_assessment][n % 4] }
5
5
  sequence(:name) { |n| "Category #{n}" }
6
6
  description { "A test category" }
7
- document_reference { "DOC-CAT-001" }
8
7
 
9
8
  trait :scoring do
10
9
  category_type { "scoring" }
@@ -1,7 +1,11 @@
1
1
  FactoryBot.define do
2
2
  factory :credit_line, class: "Dscf::Credit::CreditLine" do
3
- association :bank, factory: :bank
4
- association :category, factory: :category
3
+ transient do
4
+ bank_instance { association(:bank) }
5
+ end
6
+
7
+ bank { bank_instance }
8
+ credit_product { association(:credit_product, :approved, bank: bank_instance) }
5
9
  association :created_by, factory: :user if defined?(Dscf::Core::User)
6
10
  name { "#{Faker::Finance.credit_card(:visa)} Credit Line" }
7
11
  sequence(:code) { |n| "CL#{n.to_s.rjust(4, '0')}" }
@@ -0,0 +1,31 @@
1
+ FactoryBot.define do
2
+ factory :credit_product, class: "Dscf::Credit::CreditProduct" do
3
+ association :bank, factory: :bank
4
+ scoring_table { nil }
5
+ sequence(:name) { |n| "30-Day Retailer Credit #{n}" }
6
+ description { "Short-term credit facility for retail distributors in the supply chain" }
7
+ document_reference { "DOC-CP-001" }
8
+
9
+ trait :with_scoring_table do
10
+ association :scoring_table, factory: :scoring_table
11
+ end
12
+
13
+ trait :approved do
14
+ after(:create) do |credit_product|
15
+ credit_product.reviews.create!(status: "approved", context: "default")
16
+ end
17
+ end
18
+
19
+ trait :pending do
20
+ after(:create) do |credit_product|
21
+ credit_product.reviews.create!(status: "pending", context: "default")
22
+ end
23
+ end
24
+
25
+ trait :draft do
26
+ after(:create) do |credit_product|
27
+ credit_product.reviews.create!(status: "draft", context: "default")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,7 +4,7 @@ FactoryBot.define do
4
4
  association :user, factory: :user if defined?(Dscf::Core::User)
5
5
  association :backer, factory: :user if defined?(Dscf::Core::User)
6
6
  association :review_branch, factory: :bank_branch
7
- category { nil }
7
+ credit_product { nil }
8
8
  bank_statement_source { "internal" }
9
9
  score { 75.5 }
10
10
 
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.4.46
4
+ version: 0.4.47
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adoniyas
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-19 00:00:00.000000000 Z
10
+ date: 2026-03-05 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dscf-core
@@ -321,6 +321,7 @@ files:
321
321
  - app/controllers/dscf/credit/credit_limit_calculations_controller.rb
322
322
  - app/controllers/dscf/credit/credit_line_specs_controller.rb
323
323
  - app/controllers/dscf/credit/credit_lines_controller.rb
324
+ - app/controllers/dscf/credit/credit_products_controller.rb
324
325
  - app/controllers/dscf/credit/disbursements_controller.rb
325
326
  - app/controllers/dscf/credit/eligible_credit_lines_controller.rb
326
327
  - app/controllers/dscf/credit/facilitator_applications_controller.rb
@@ -353,6 +354,7 @@ files:
353
354
  - app/models/dscf/credit/category.rb
354
355
  - app/models/dscf/credit/credit_line.rb
355
356
  - app/models/dscf/credit/credit_line_spec.rb
357
+ - app/models/dscf/credit/credit_product.rb
356
358
  - app/models/dscf/credit/daily_routine_transaction.rb
357
359
  - app/models/dscf/credit/eligible_credit_line.rb
358
360
  - app/models/dscf/credit/facilitator.rb
@@ -379,6 +381,7 @@ files:
379
381
  - app/serializers/dscf/credit/category_serializer.rb
380
382
  - app/serializers/dscf/credit/credit_line_serializer.rb
381
383
  - app/serializers/dscf/credit/credit_line_spec_serializer.rb
384
+ - app/serializers/dscf/credit/credit_product_serializer.rb
382
385
  - app/serializers/dscf/credit/daily_routine_transaction_serializer.rb
383
386
  - app/serializers/dscf/credit/eligible_credit_line_serializer.rb
384
387
  - app/serializers/dscf/credit/facilitator_application_serializer.rb
@@ -451,6 +454,7 @@ files:
451
454
  - db/migrate/20260219000026_create_dscf_credit_eligible_credit_lines.rb
452
455
  - db/migrate/20260219000027_create_dscf_credit_loan_accruals.rb
453
456
  - db/migrate/20260219000028_create_dscf_credit_loan_application_data.rb
457
+ - db/migrate/20260219000029_create_dscf_credit_credit_products.rb
454
458
  - db/seeds.rb
455
459
  - lib/dscf/credit.rb
456
460
  - lib/dscf/credit/engine.rb
@@ -464,6 +468,7 @@ files:
464
468
  - spec/factories/dscf/credit/categories.rb
465
469
  - spec/factories/dscf/credit/credit_line_specs.rb
466
470
  - spec/factories/dscf/credit/credit_lines.rb
471
+ - spec/factories/dscf/credit/credit_products.rb
467
472
  - spec/factories/dscf/credit/daily_routine_transactions.rb
468
473
  - spec/factories/dscf/credit/eligible_credit_lines.rb
469
474
  - spec/factories/dscf/credit/facilitator_applications.rb