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.
- checksums.yaml +4 -4
- data/app/controllers/dscf/credit/categories_controller.rb +6 -8
- data/app/controllers/dscf/credit/credit_limit_calculations_controller.rb +3 -3
- data/app/controllers/dscf/credit/credit_lines_controller.rb +7 -7
- data/app/controllers/dscf/credit/credit_products_controller.rb +66 -0
- data/app/controllers/dscf/credit/loan_applications_controller.rb +15 -15
- data/app/controllers/dscf/credit/loan_profiles_controller.rb +5 -5
- data/app/controllers/dscf/credit/scoring_tables_controller.rb +4 -4
- data/app/models/dscf/credit/category.rb +2 -6
- data/app/models/dscf/credit/credit_line.rb +13 -3
- data/app/models/dscf/credit/credit_product.rb +26 -0
- data/app/models/dscf/credit/loan_application.rb +2 -2
- data/app/models/dscf/credit/scoring_table.rb +2 -2
- data/app/serializers/dscf/credit/category_serializer.rb +1 -3
- data/app/serializers/dscf/credit/credit_line_serializer.rb +1 -1
- data/app/serializers/dscf/credit/credit_product_serializer.rb +13 -0
- data/app/serializers/dscf/credit/loan_application_serializer.rb +1 -1
- data/app/serializers/dscf/credit/scoring_table_serializer.rb +1 -1
- data/app/services/dscf/credit/credit_scoring_engine.rb +4 -4
- data/app/services/dscf/credit/facility_limit_calculation_engine.rb +10 -10
- data/app/services/dscf/credit/loan_profile_creation_service.rb +3 -3
- data/config/locales/en.yml +24 -0
- data/config/routes.rb +9 -0
- data/db/dev_seeds.rb +49 -26
- data/db/migrate/20260219000003_create_dscf_credit_categories.rb +0 -6
- data/db/migrate/20260219000005_create_dscf_credit_credit_lines.rb +1 -1
- data/db/migrate/20260219000013_create_dscf_credit_loan_applications.rb +1 -1
- data/db/migrate/20260219000029_create_dscf_credit_credit_products.rb +19 -0
- data/db/seeds.rb +40 -8
- data/lib/dscf/credit/version.rb +1 -1
- data/spec/factories/dscf/credit/categories.rb +0 -1
- data/spec/factories/dscf/credit/credit_lines.rb +6 -2
- data/spec/factories/dscf/credit/credit_products.rb +31 -0
- data/spec/factories/dscf/credit/loan_applications.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f7d46fc73d2d2bd02f7be0fd360644a226fa5ef8e8930431b4bce1fdf25f745
|
|
4
|
+
data.tar.gz: 949c51063d46aefe7ce91b6737873e283d03cc2cf184d5e489a18c307e3f29fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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, :
|
|
18
|
+
[ :bank, :scoring_parameters ]
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
def allowed_order_columns
|
|
24
|
-
%w[id category_type name description
|
|
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, :
|
|
31
|
-
create: [ :bank
|
|
32
|
-
update: [ :bank, :
|
|
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
|
-
|
|
5
|
+
credit_product = Dscf::Credit::CreditProduct.find(params[:credit_product_id])
|
|
6
6
|
|
|
7
|
-
engine = Dscf::Credit::FacilityLimitCalculationEngine.new(loan_profile.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
|
-
|
|
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
|
-
:
|
|
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, :
|
|
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
|
|
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, :
|
|
60
|
+
index: [ :bank, :credit_product, reviews: { reviewed_by: :user_profile } ],
|
|
61
61
|
show: [
|
|
62
|
-
:bank, :
|
|
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, :
|
|
67
|
+
create: [ :bank, :credit_product, :created_by, :reviews ],
|
|
68
68
|
update: [
|
|
69
|
-
:bank, :
|
|
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.
|
|
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
|
-
|
|
104
|
-
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
|
|
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
|
-
|
|
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&.
|
|
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
|
-
|
|
279
|
-
return [] unless
|
|
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
|
-
|
|
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
|
-
:
|
|
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, :
|
|
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, :
|
|
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, :
|
|
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, :
|
|
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, :
|
|
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
|
-
|
|
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: [ "
|
|
34
|
+
errors: [ "Credit Product ID is required" ],
|
|
35
35
|
status: :unprocessable_entity
|
|
36
|
-
) unless
|
|
36
|
+
) unless credit_product_id
|
|
37
37
|
|
|
38
|
-
facility_engine = FacilityLimitCalculationEngine.new(loan_profile.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(:
|
|
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, :
|
|
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, :
|
|
84
|
+
index: [ :bank, :credit_products, reviews: { reviewed_by: :user_profile } ],
|
|
85
85
|
show: [
|
|
86
|
-
:bank, :created_by, :parent_template, :scoring_table_parameters, :
|
|
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, :
|
|
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
|
|
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
|
|
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 :
|
|
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
|
|
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 :
|
|
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
|
|
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 :
|
|
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
|
|
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, :
|
|
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 :
|
|
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 :
|
|
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 :
|
|
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
|
-
@
|
|
6
|
-
@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
|
|
12
|
-
return failure("No scoring table configured for
|
|
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, :
|
|
3
|
+
attr_reader :loan_profile, :credit_product_id, :errors
|
|
4
4
|
|
|
5
|
-
def initialize(loan_profile_id,
|
|
5
|
+
def initialize(loan_profile_id, credit_product_id)
|
|
6
6
|
@errors = []
|
|
7
7
|
@loan_profile = LoanProfile.find(loan_profile_id)
|
|
8
|
-
@
|
|
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("
|
|
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
|
|
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(:
|
|
70
|
+
CreditLine.joins(:credit_product, :credit_line_specs)
|
|
71
71
|
.where(
|
|
72
|
-
|
|
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
|
-
|
|
165
|
-
return nil unless
|
|
166
|
-
ScoringTable.find_by(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
|
-
|
|
86
|
-
return unless
|
|
85
|
+
credit_product = @loan_application.credit_product
|
|
86
|
+
return unless credit_product
|
|
87
87
|
|
|
88
|
-
engine = FacilityLimitCalculationEngine.new(loan_profile.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]
|
data/config/locales/en.yml
CHANGED
|
@@ -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
|
|
211
|
-
c.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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
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
|
|
498
|
-
cl.
|
|
499
|
-
cl.description
|
|
500
|
-
cl.created_by
|
|
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
|
|
540
|
-
cl.
|
|
541
|
-
cl.description
|
|
542
|
-
cl.created_by
|
|
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
|
|
582
|
-
cl.
|
|
583
|
-
cl.description
|
|
584
|
-
cl.created_by
|
|
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
|
|
767
|
-
app.
|
|
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
|
|
819
|
-
app.
|
|
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
|
|
869
|
-
app.
|
|
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 :
|
|
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 :
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
804
|
-
app.
|
|
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
|
|
823
|
-
app.
|
|
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}"
|
data/lib/dscf/credit/version.rb
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|