dscf-credit 0.1.2 → 0.1.4
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/concerns/dscf/core/authenticatable.rb +81 -0
- data/app/controllers/concerns/dscf/core/common.rb +200 -0
- data/app/controllers/concerns/dscf/core/filterable.rb +12 -0
- data/app/controllers/concerns/dscf/core/json_response.rb +77 -0
- data/app/controllers/concerns/dscf/core/pagination.rb +71 -0
- data/app/controllers/concerns/dscf/core/token_authenticatable.rb +53 -0
- data/app/controllers/dscf/credit/categories_controller.rb +3 -3
- data/app/controllers/dscf/credit/credit_limit_calculations_controller.rb +50 -0
- data/app/controllers/dscf/credit/credit_line_specs_controller.rb +2 -1
- data/app/controllers/dscf/credit/credit_lines_controller.rb +3 -3
- data/app/controllers/dscf/credit/disbursements_controller.rb +55 -0
- data/app/controllers/dscf/credit/eligible_credit_lines_controller.rb +50 -0
- data/app/controllers/dscf/credit/facilitators_controller.rb +2 -1
- data/app/controllers/dscf/credit/loan_profiles_controller.rb +138 -0
- data/app/controllers/dscf/credit/payment_requests_controller.rb +54 -5
- data/app/controllers/dscf/credit/repayments_controller.rb +53 -0
- data/app/controllers/dscf/credit/scoring_parameters_controller.rb +3 -3
- data/app/controllers/dscf/credit/scoring_tables_controller.rb +8 -8
- data/app/controllers/dscf/credit/users_controller.rb +18 -0
- data/app/models/dscf/credit/bank_branch.rb +2 -1
- data/app/models/dscf/credit/category.rb +3 -1
- data/app/models/dscf/credit/credit_line.rb +3 -3
- data/app/models/dscf/credit/credit_line_spec.rb +3 -3
- data/app/models/dscf/credit/eligible_credit_line.rb +28 -0
- data/app/models/dscf/credit/loan_profile.rb +8 -5
- data/app/models/dscf/credit/loan_profile_scoring_spec.rb +4 -5
- data/app/models/dscf/credit/scoring_parameter.rb +2 -2
- data/app/models/dscf/credit/scoring_table.rb +4 -4
- data/app/serializers/dscf/credit/bank_branch_serializer.rb +1 -0
- data/app/serializers/dscf/credit/category_serializer.rb +2 -0
- data/app/serializers/dscf/credit/credit_line_serializer.rb +2 -0
- data/app/serializers/dscf/credit/credit_line_spec_serializer.rb +1 -1
- data/app/serializers/dscf/credit/daily_routine_transaction_serializer.rb +8 -0
- data/app/serializers/dscf/credit/eligible_credit_line_serializer.rb +8 -0
- data/app/serializers/dscf/credit/loan_profile_scoring_spec_serializer.rb +8 -0
- data/app/serializers/dscf/credit/loan_profile_serializer.rb +15 -0
- data/app/serializers/dscf/credit/loan_serializer.rb +12 -0
- data/app/serializers/dscf/credit/loan_transaction_serializer.rb +8 -0
- data/app/serializers/dscf/credit/payment_request_serializer.rb +10 -0
- data/app/serializers/dscf/credit/payment_serializer.rb +8 -0
- data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +2 -0
- data/app/serializers/dscf/credit/scoring_table_serializer.rb +1 -1
- data/app/services/dscf/credit/credit_limit_calculation_service.rb +153 -0
- data/app/services/dscf/credit/disbursement_service.rb +180 -0
- data/app/services/dscf/credit/facilitator_approval_service.rb +3 -4
- data/app/services/dscf/credit/repayment_service.rb +216 -0
- data/app/services/dscf/credit/risk_application_service.rb +27 -0
- data/app/services/dscf/credit/scoring_service.rb +297 -0
- data/config/locales/en.yml +91 -0
- data/config/routes.rb +19 -7
- data/db/migrate/20250822091527_create_dscf_credit_credit_line_specs.rb +1 -0
- data/db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb +2 -1
- data/db/migrate/20250822092417_create_dscf_credit_loan_profile_scoring_specs.rb +5 -8
- data/db/migrate/20250901172842_create_dscf_credit_scoring_tables.rb +2 -2
- data/db/migrate/20250917120000_create_dscf_credit_eligible_credit_lines.rb +18 -0
- data/db/seeds.rb +88 -22
- data/lib/dscf/credit/version.rb +1 -1
- data/spec/factories/dscf/credit/credit_line_specs.rb +1 -0
- data/spec/factories/dscf/credit/credit_lines.rb +3 -3
- data/spec/factories/dscf/credit/eligible_credit_lines.rb +33 -0
- data/spec/factories/dscf/credit/loan_profile_scoring_specs.rb +1 -4
- data/spec/factories/dscf/credit/loan_profiles.rb +1 -0
- data/spec/factories/dscf/credit/scoring_tables.rb +1 -1
- metadata +29 -2
@@ -0,0 +1,50 @@
|
|
1
|
+
module Dscf::Credit
|
2
|
+
class EligibleCreditLinesController < ApplicationController
|
3
|
+
include Dscf::Core::Common
|
4
|
+
|
5
|
+
before_action :set_object, only: [ :show, :apply_risk ]
|
6
|
+
|
7
|
+
def apply_risk
|
8
|
+
unless params[:risk].present?
|
9
|
+
return render_error("eligible_credit_line.errors.apply_risk", errors: [ "Risk parameter is required" ])
|
10
|
+
end
|
11
|
+
|
12
|
+
service = RiskApplicationService.new(@obj, params[:risk])
|
13
|
+
result = service.apply_risk
|
14
|
+
|
15
|
+
if result[:success]
|
16
|
+
render_success(data: result[:data].reload)
|
17
|
+
else
|
18
|
+
render_error(errors: result[:errors])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def model_params
|
25
|
+
params.require(:eligible_credit_line).permit(
|
26
|
+
:loan_profile_id,
|
27
|
+
:credit_line_id,
|
28
|
+
:credit_limit,
|
29
|
+
:available_limit
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def eager_loaded_associations
|
34
|
+
[ :loan_profile, :credit_line ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def allowed_order_columns
|
38
|
+
%w[id credit_limit available_limit risk created_at updated_at loan_profile_id credit_line_id]
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_serializer_includes
|
42
|
+
{
|
43
|
+
index: [ :loan_profile, :credit_line ],
|
44
|
+
show: [ :loan_profile, :credit_line ],
|
45
|
+
create: [ :loan_profile, :credit_line ],
|
46
|
+
update: [ :loan_profile, :credit_line ]
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -16,7 +16,7 @@ module Dscf::Credit
|
|
16
16
|
|
17
17
|
def approve
|
18
18
|
begin
|
19
|
-
FacilitatorApprovalService.new(@facilitator, current_user).approve
|
19
|
+
FacilitatorApprovalService.new(@facilitator, current_user).approve
|
20
20
|
render_success(data: @facilitator)
|
21
21
|
rescue StandardError => e
|
22
22
|
render_error(errors: e.message)
|
@@ -83,6 +83,7 @@ module Dscf::Credit
|
|
83
83
|
:user_id,
|
84
84
|
:bank_id,
|
85
85
|
:kyc_status,
|
86
|
+
:total_limit,
|
86
87
|
:kyc_review_date,
|
87
88
|
:additional_info,
|
88
89
|
:review_feedback
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Dscf::Credit
|
2
|
+
class LoanProfilesController < ApplicationController
|
3
|
+
include Dscf::Core::Common
|
4
|
+
include Dscf::Credit::Reviewable
|
5
|
+
|
6
|
+
def create
|
7
|
+
super do
|
8
|
+
loan_profile = @clazz.new(model_params)
|
9
|
+
|
10
|
+
ActiveRecord::Base.transaction do
|
11
|
+
loan_profile.save!
|
12
|
+
|
13
|
+
if loan_profile.user.present?
|
14
|
+
loan_profile.loan_profile_scoring_specs.create!(
|
15
|
+
created_by: loan_profile.user
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
loan_profile
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def calculate_score
|
25
|
+
loan_profile = @clazz.find(params[:id])
|
26
|
+
scoring_service = Dscf::Credit::ScoringService.new(loan_profile)
|
27
|
+
|
28
|
+
external_scoring_data = params[:scoring_input_data]
|
29
|
+
|
30
|
+
result = scoring_service.calculate_credit_score(external_scoring_data)
|
31
|
+
|
32
|
+
if result[:success]
|
33
|
+
ActiveRecord::Base.transaction do
|
34
|
+
scoring_spec = if external_scoring_data.present?
|
35
|
+
loan_profile.loan_profile_scoring_specs.find_or_initialize_by(
|
36
|
+
scoring_input_data: external_scoring_data
|
37
|
+
)
|
38
|
+
else
|
39
|
+
# If no external data, find existing spec with input data or latest one
|
40
|
+
loan_profile.loan_profile_scoring_specs
|
41
|
+
.where.not(scoring_input_data: [ nil, {} ])
|
42
|
+
.order(created_at: :desc)
|
43
|
+
.first ||
|
44
|
+
loan_profile.loan_profile_scoring_specs.order(created_at: :desc).first ||
|
45
|
+
loan_profile.loan_profile_scoring_specs.build
|
46
|
+
end
|
47
|
+
|
48
|
+
loan_profile.loan_profile_scoring_specs.where.not(id: scoring_spec.id).update_all(active: false)
|
49
|
+
|
50
|
+
scoring_spec.assign_attributes(
|
51
|
+
score: result[:score],
|
52
|
+
total_limit: result[:facility_limit],
|
53
|
+
scoring_input_data: result[:scoring_input_data] || scoring_spec.scoring_input_data || {},
|
54
|
+
active: true,
|
55
|
+
created_by: scoring_spec.new_record? ? current_user : scoring_spec.created_by
|
56
|
+
)
|
57
|
+
|
58
|
+
scoring_spec.save!
|
59
|
+
|
60
|
+
update_loan_profile_status(loan_profile, result[:score])
|
61
|
+
end
|
62
|
+
|
63
|
+
loan_profile.reload
|
64
|
+
|
65
|
+
render_success(
|
66
|
+
"loan_profile.success.calculate_score",
|
67
|
+
data: {
|
68
|
+
loan_profile: loan_profile,
|
69
|
+
scoring_result: result
|
70
|
+
},
|
71
|
+
serializer_options: { include: [ :loan_profile_scoring_specs ] }
|
72
|
+
)
|
73
|
+
else
|
74
|
+
render_error(
|
75
|
+
"loan_profile.errors.calculate_score",
|
76
|
+
errors: [ result[:error] ],
|
77
|
+
status: :unprocessable_entity
|
78
|
+
)
|
79
|
+
end
|
80
|
+
rescue ActiveRecord::RecordNotFound
|
81
|
+
render_error(
|
82
|
+
"loan_profile.errors.not_found",
|
83
|
+
status: :not_found
|
84
|
+
)
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def update_loan_profile_status(loan_profile, score)
|
90
|
+
new_status = case score
|
91
|
+
when 0...50
|
92
|
+
"rejected"
|
93
|
+
when 50...60
|
94
|
+
"manual_review"
|
95
|
+
else
|
96
|
+
"approved"
|
97
|
+
end
|
98
|
+
|
99
|
+
loan_profile.update!(
|
100
|
+
status: new_status,
|
101
|
+
reviewed_by: current_user,
|
102
|
+
review_date: Time.current
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def model_params
|
109
|
+
params.require(:loan_profile).permit(
|
110
|
+
:bank_id,
|
111
|
+
:review_branch_id,
|
112
|
+
:user_id,
|
113
|
+
:backer_id,
|
114
|
+
:backer_type,
|
115
|
+
:status,
|
116
|
+
:total_amount,
|
117
|
+
:available_amount
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def eager_loaded_associations
|
122
|
+
[ :bank, :review_branch, :reviewed_by, :user, :backer, :loan_profile_scoring_specs, :loans, :eligible_credit_lines ]
|
123
|
+
end
|
124
|
+
|
125
|
+
def allowed_order_columns
|
126
|
+
%w[id status total_amount available_amount review_date created_at updated_at]
|
127
|
+
end
|
128
|
+
|
129
|
+
def default_serializer_includes
|
130
|
+
{
|
131
|
+
index: [ :bank, :review_branch, :reviewed_by, :user, :backer ],
|
132
|
+
show: [ :bank, :review_branch, :reviewed_by, :user, :backer, :loan_profile_scoring_specs, :loans, :eligible_credit_lines ],
|
133
|
+
create: [ :bank, :review_branch, :reviewed_by, :user, :backer, :loan_profile_scoring_specs ],
|
134
|
+
update: [ :bank, :review_branch, :reviewed_by, :user, :backer, :loan_profile_scoring_specs, :loans, :eligible_credit_lines ]
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -2,20 +2,69 @@ module Dscf::Credit
|
|
2
2
|
class PaymentRequestsController < ApplicationController
|
3
3
|
include Dscf::Core::Common
|
4
4
|
|
5
|
+
def create
|
6
|
+
super do
|
7
|
+
obj = @clazz.new(model_params)
|
8
|
+
obj.request_type = "credit"
|
9
|
+
obj.initiated_at = Time.current
|
10
|
+
obj
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def approve
|
15
|
+
@obj = @clazz.find(params[:id])
|
16
|
+
|
17
|
+
if @obj.update(status: "approved", approved_at: Time.current)
|
18
|
+
@obj = @clazz.includes(eager_loaded_associations).find(@obj.id) if eager_loaded_associations.present?
|
19
|
+
includes = serializer_includes_for_action(:show)
|
20
|
+
options = includes.present? ? { include: includes } : {}
|
21
|
+
render_success(data: @obj, serializer_options: options)
|
22
|
+
else
|
23
|
+
render_error(errors: @obj.errors.full_messages[0], status: :unprocessable_entity)
|
24
|
+
end
|
25
|
+
rescue StandardError => e
|
26
|
+
render_error(error: e.message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def eligible_credit_lines
|
30
|
+
payment_request = @clazz.find(params[:id])
|
31
|
+
|
32
|
+
if payment_request.user.nil?
|
33
|
+
return render_error(error: "Payment request has no associated user", status: :unprocessable_entity)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Find all eligible credit lines for the user through their approved loan profiles
|
37
|
+
# Only include credit lines with available credit
|
38
|
+
eligible_credit_lines = Dscf::Credit::EligibleCreditLine
|
39
|
+
.joins(loan_profile: :user)
|
40
|
+
.where(dscf_credit_loan_profiles: {
|
41
|
+
user_id: payment_request.user_id,
|
42
|
+
status: [ "approved", "kyc_approved" ] # Only approved loan profiles
|
43
|
+
})
|
44
|
+
.where("dscf_credit_eligible_credit_lines.available_limit > 0") # Only with available credit
|
45
|
+
.includes(:loan_profile, :credit_line)
|
46
|
+
.order("dscf_credit_eligible_credit_lines.available_limit DESC") # Order by available limit
|
47
|
+
|
48
|
+
render_success(
|
49
|
+
data: eligible_credit_lines,
|
50
|
+
)
|
51
|
+
rescue ActiveRecord::RecordNotFound
|
52
|
+
render_error(error: "Payment request not found", status: :not_found)
|
53
|
+
rescue StandardError => e
|
54
|
+
render_error(error: e.message)
|
55
|
+
end
|
56
|
+
|
5
57
|
private
|
6
58
|
|
7
59
|
def model_params
|
8
60
|
params.require(:payment_request).permit(
|
9
61
|
:user_id,
|
10
62
|
:order_id,
|
11
|
-
:request_type,
|
12
63
|
:amount,
|
13
64
|
:receiver_account_reference,
|
14
65
|
:status,
|
15
|
-
:failure_reason
|
16
|
-
|
17
|
-
:approved_at
|
18
|
-
)
|
66
|
+
:failure_reason
|
67
|
+
)
|
19
68
|
end
|
20
69
|
|
21
70
|
def eager_loaded_associations
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Dscf::Credit
|
2
|
+
class RepaymentsController < ApplicationController
|
3
|
+
def create
|
4
|
+
loan = Dscf::Credit::Loan.find(params[:loan_id])
|
5
|
+
payment_amount = params[:amount].to_f
|
6
|
+
|
7
|
+
service = Dscf::Credit::RepaymentService.new(loan, payment_amount, current_user)
|
8
|
+
result = service.process_repayment
|
9
|
+
|
10
|
+
if result[:success]
|
11
|
+
render_success(
|
12
|
+
"repayment.success.create",
|
13
|
+
data: {
|
14
|
+
loan: result[:loan],
|
15
|
+
payment_details: result[:payment_details]
|
16
|
+
},
|
17
|
+
serializer_options: {
|
18
|
+
include: [
|
19
|
+
:loan_profile,
|
20
|
+
:credit_line,
|
21
|
+
:payment_request,
|
22
|
+
:loan_transactions
|
23
|
+
]
|
24
|
+
}
|
25
|
+
)
|
26
|
+
else
|
27
|
+
render_error(
|
28
|
+
"repayment.errors.create",
|
29
|
+
errors: [ result[:error] ],
|
30
|
+
status: :unprocessable_entity
|
31
|
+
)
|
32
|
+
end
|
33
|
+
rescue ActiveRecord::RecordNotFound => e
|
34
|
+
render_error(
|
35
|
+
"repayment.errors.not_found",
|
36
|
+
errors: [ e.message ],
|
37
|
+
status: :not_found
|
38
|
+
)
|
39
|
+
rescue StandardError => e
|
40
|
+
render_error(
|
41
|
+
"repayment.errors.create",
|
42
|
+
errors: [ e.message ],
|
43
|
+
status: :internal_server_error
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def model_params
|
50
|
+
params.permit(:loan_id, :amount)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -36,7 +36,7 @@ module Dscf::Credit
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def eager_loaded_associations
|
39
|
-
[ :bank, :created_by, :reviewed_by, :scoring_param_type, :previous_version, :parameter_normalizers ]
|
39
|
+
[ :bank, :created_by, :reviewed_by, :scoring_param_type, :previous_version, :parameter_normalizers, :scoring_tables, :categories ]
|
40
40
|
end
|
41
41
|
|
42
42
|
def allowed_order_columns
|
@@ -46,9 +46,9 @@ module Dscf::Credit
|
|
46
46
|
def default_serializer_includes
|
47
47
|
{
|
48
48
|
index: [ :bank ],
|
49
|
-
show: [ :bank, :created_by, :reviewed_by, :scoring_param_type, :previous_version, :parameter_normalizers ],
|
49
|
+
show: [ :bank, :created_by, :reviewed_by, :scoring_param_type, :previous_version, :parameter_normalizers, :scoring_tables, :categories ],
|
50
50
|
create: [ :bank, :created_by, :reviewed_by, :scoring_param_type ],
|
51
|
-
update: [ :bank, :created_by, :reviewed_by, :scoring_param_type, :parameter_normalizers ]
|
51
|
+
update: [ :bank, :created_by, :reviewed_by, :scoring_param_type, :parameter_normalizers, :scoring_tables, :categories ]
|
52
52
|
}
|
53
53
|
end
|
54
54
|
end
|
@@ -13,7 +13,7 @@ module Dscf::Credit
|
|
13
13
|
def activate
|
14
14
|
@obj = @clazz.find(params[:id])
|
15
15
|
if @obj.update(active: true)
|
16
|
-
render_success(data: @obj, serializer_options: { include: [ :
|
16
|
+
render_success(data: @obj, serializer_options: { include: [ :category, :scoring_parameter, :created_by ] })
|
17
17
|
else
|
18
18
|
render_error(errors: @obj.errors.full_messages[0], status: :unprocessable_entity)
|
19
19
|
end
|
@@ -24,7 +24,7 @@ module Dscf::Credit
|
|
24
24
|
def deactivate
|
25
25
|
@obj = @clazz.find(params[:id])
|
26
26
|
if @obj.update(active: false)
|
27
|
-
render_success(data: @obj, serializer_options: { include: [ :
|
27
|
+
render_success(data: @obj, serializer_options: { include: [ :category, :scoring_parameter, :created_by ] })
|
28
28
|
else
|
29
29
|
render_error(errors: @obj.errors.full_messages[0], status: :unprocessable_entity)
|
30
30
|
end
|
@@ -36,7 +36,7 @@ module Dscf::Credit
|
|
36
36
|
|
37
37
|
def model_params
|
38
38
|
params.require(:scoring_table).permit(
|
39
|
-
:
|
39
|
+
:category_id,
|
40
40
|
:scoring_parameter_id,
|
41
41
|
:weight,
|
42
42
|
:active
|
@@ -44,7 +44,7 @@ module Dscf::Credit
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def eager_loaded_associations
|
47
|
-
[ :
|
47
|
+
[ :category, :scoring_parameter, :created_by ]
|
48
48
|
end
|
49
49
|
|
50
50
|
def allowed_order_columns
|
@@ -53,10 +53,10 @@ module Dscf::Credit
|
|
53
53
|
|
54
54
|
def default_serializer_includes
|
55
55
|
{
|
56
|
-
index: [ :
|
57
|
-
show: [ :
|
58
|
-
create: [ :
|
59
|
-
update: [ :
|
56
|
+
index: [ :category, :scoring_parameter, :created_by ],
|
57
|
+
show: [ :category, :scoring_parameter, :created_by ],
|
58
|
+
create: [ :category, :scoring_parameter, :created_by ],
|
59
|
+
update: [ :category, :scoring_parameter, :created_by ]
|
60
60
|
}
|
61
61
|
end
|
62
62
|
end
|
@@ -25,6 +25,24 @@ module Dscf::Credit
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def show
|
29
|
+
super do
|
30
|
+
has_retailer_business = @obj.businesses.any? do |business|
|
31
|
+
business.business_type&.name&.downcase == "retailer"
|
32
|
+
end
|
33
|
+
|
34
|
+
unless has_retailer_business
|
35
|
+
return render_error(
|
36
|
+
"errors.access_denied",
|
37
|
+
errors: "User does not have retailer business type",
|
38
|
+
status: :forbidden
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
@obj
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
28
46
|
private
|
29
47
|
|
30
48
|
def model_params
|
@@ -4,6 +4,7 @@ module Dscf::Credit
|
|
4
4
|
|
5
5
|
belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
|
6
6
|
has_many :bank_staffs, class_name: "Dscf::Credit::BankStaff", foreign_key: "bank_branch_id", dependent: :destroy
|
7
|
+
has_many :loan_profiles, class_name: "Dscf::Credit::LoanProfile", foreign_key: "review_branch_id", dependent: :nullify
|
7
8
|
|
8
9
|
validates :branch_name, presence: true
|
9
10
|
validates :branch_name, uniqueness: { scope: :bank_id }
|
@@ -17,7 +18,7 @@ module Dscf::Credit
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def self.ransackable_associations(auth_object = nil)
|
20
|
-
%w[bank bank_staffs]
|
21
|
+
%w[bank bank_staffs loan_profiles]
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -4,6 +4,8 @@ module Dscf::Credit
|
|
4
4
|
self.inheritance_column = nil # Disable STI since 'type' is a business attribute
|
5
5
|
|
6
6
|
has_many :credit_lines, class_name: "Dscf::Credit::CreditLine", foreign_key: "category_id", dependent: :nullify
|
7
|
+
has_many :scoring_tables, class_name: "Dscf::Credit::ScoringTable", foreign_key: "category_id", dependent: :destroy
|
8
|
+
has_many :scoring_parameters, through: :scoring_tables
|
7
9
|
|
8
10
|
validates :type, :name, presence: true
|
9
11
|
validates :name, uniqueness: { scope: :type }
|
@@ -15,7 +17,7 @@ module Dscf::Credit
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def self.ransackable_associations(auth_object = nil)
|
18
|
-
%w[credit_lines]
|
20
|
+
%w[credit_lines scoring_tables scoring_parameters]
|
19
21
|
end
|
20
22
|
end
|
21
23
|
end
|
@@ -9,8 +9,8 @@ module Dscf::Credit
|
|
9
9
|
|
10
10
|
has_many :credit_line_specs, class_name: "Dscf::Credit::CreditLineSpec", foreign_key: "credit_line_id", dependent: :destroy
|
11
11
|
has_many :loans, class_name: "Dscf::Credit::Loan", foreign_key: "credit_line_id", dependent: :destroy
|
12
|
-
has_many :
|
13
|
-
has_many :scoring_parameters, through: :
|
12
|
+
has_many :eligible_credit_lines, class_name: "Dscf::Credit::EligibleCreditLine", foreign_key: "credit_line_id", dependent: :destroy
|
13
|
+
has_many :scoring_parameters, through: :category, source: :scoring_parameters
|
14
14
|
|
15
15
|
validates :name, presence: true
|
16
16
|
validates :code, uniqueness: { scope: :bank_id }, allow_blank: true
|
@@ -27,7 +27,7 @@ module Dscf::Credit
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def self.ransackable_associations(auth_object = nil)
|
30
|
-
%w[bank category created_by reviewed_by credit_line_specs loans
|
30
|
+
%w[bank category created_by reviewed_by credit_line_specs loans eligible_credit_lines scoring_parameters]
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
@@ -5,8 +5,8 @@ module Dscf::Credit
|
|
5
5
|
belongs_to :credit_line, class_name: "Dscf::Credit::CreditLine", foreign_key: "credit_line_id"
|
6
6
|
belongs_to :created_by, polymorphic: true
|
7
7
|
|
8
|
-
validates :min_amount, :max_amount, :interest_rate, :penalty_rate, :facilitation_fee_rate, :tax_rate, :max_penalty_days, :loan_duration, :interest_frequency, :interest_income_tax, :vat, :max_interest_calculation_days, :penalty_frequency, :penalty_income_tax, presence: true
|
9
|
-
validates :min_amount, :max_amount, numericality: { greater_than: 0 }
|
8
|
+
validates :min_amount, :max_amount, :interest_rate, :penalty_rate, :facilitation_fee_rate, :tax_rate, :credit_line_multiplier, :max_penalty_days, :loan_duration, :interest_frequency, :interest_income_tax, :vat, :max_interest_calculation_days, :penalty_frequency, :penalty_income_tax, presence: true
|
9
|
+
validates :min_amount, :max_amount, :credit_line_multiplier, numericality: { greater_than: 0 }
|
10
10
|
validates :interest_rate, :penalty_rate, :facilitation_fee_rate, :tax_rate, :interest_income_tax, :vat, :penalty_income_tax, numericality: { greater_than_or_equal_to: 0, less_than: 10 }
|
11
11
|
validates :max_penalty_days, :loan_duration, :max_interest_calculation_days, numericality: { greater_than: 0 }
|
12
12
|
validates :interest_frequency, inclusion: { in: %w[daily weekly monthly quarterly annually] }
|
@@ -19,7 +19,7 @@ module Dscf::Credit
|
|
19
19
|
scope :by_penalty_frequency, ->(frequency) { where(penalty_frequency: frequency) }
|
20
20
|
|
21
21
|
def self.ransackable_attributes(auth_object = nil)
|
22
|
-
%w[id min_amount max_amount interest_rate penalty_rate facilitation_fee_rate tax_rate max_penalty_days loan_duration interest_frequency interest_income_tax vat max_interest_calculation_days penalty_frequency penalty_income_tax active created_at updated_at]
|
22
|
+
%w[id min_amount max_amount interest_rate penalty_rate facilitation_fee_rate tax_rate credit_line_multiplier max_penalty_days loan_duration interest_frequency interest_income_tax vat max_interest_calculation_days penalty_frequency penalty_income_tax active created_at updated_at]
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.ransackable_associations(auth_object = nil)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Dscf::Credit
|
2
|
+
class EligibleCreditLine < ApplicationRecord
|
3
|
+
self.table_name = "dscf_credit_eligible_credit_lines"
|
4
|
+
|
5
|
+
belongs_to :loan_profile, class_name: "Dscf::Credit::LoanProfile", foreign_key: "loan_profile_id"
|
6
|
+
belongs_to :credit_line, class_name: "Dscf::Credit::CreditLine", foreign_key: "credit_line_id"
|
7
|
+
|
8
|
+
validates :credit_limit, presence: true
|
9
|
+
validates :credit_limit, numericality: { greater_than: 0 }
|
10
|
+
validates :available_limit, presence: true
|
11
|
+
validates :available_limit, numericality: { greater_than_or_equal_to: 0 }
|
12
|
+
validates :risk, numericality: { greater_than_or_equal_to: 0.01, less_than_or_equal_to: 0.9 }, allow_nil: true
|
13
|
+
|
14
|
+
scope :by_loan_profile, ->(loan_profile_id) { where(loan_profile_id: loan_profile_id) }
|
15
|
+
scope :by_credit_line, ->(credit_line_id) { where(credit_line_id: credit_line_id) }
|
16
|
+
scope :by_risk_range, ->(min, max) { where(risk: min..max) }
|
17
|
+
scope :by_credit_limit_range, ->(min, max) { where(credit_limit: min..max) }
|
18
|
+
scope :by_available_limit_range, ->(min, max) { where(available_limit: min..max) }
|
19
|
+
|
20
|
+
def self.ransackable_attributes(auth_object = nil)
|
21
|
+
%w[id credit_limit available_limit risk created_at updated_at]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.ransackable_associations(auth_object = nil)
|
25
|
+
%w[loan_profile credit_line]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -3,22 +3,25 @@ module Dscf::Credit
|
|
3
3
|
self.table_name = "dscf_credit_loan_profiles"
|
4
4
|
|
5
5
|
belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
|
6
|
-
belongs_to :
|
6
|
+
belongs_to :review_branch, class_name: "Dscf::Credit::BankBranch", foreign_key: "review_branch_id", optional: true
|
7
|
+
belongs_to :reviewed_by, polymorphic: true, optional: true
|
7
8
|
belongs_to :backer, polymorphic: true, optional: true
|
8
9
|
if defined?(Dscf::Core::User)
|
9
10
|
belongs_to :user, class_name: "Dscf::Core::User", foreign_key: "user_id"
|
10
11
|
end
|
11
12
|
has_many :loan_profile_scoring_specs, class_name: "Dscf::Credit::LoanProfileScoringSpec", foreign_key: "loan_profile_id", dependent: :destroy
|
12
13
|
has_many :loans, class_name: "Dscf::Credit::Loan", foreign_key: "loan_profile_id", dependent: :destroy
|
14
|
+
has_many :eligible_credit_lines, class_name: "Dscf::Credit::EligibleCreditLine", foreign_key: "loan_profile_id", dependent: :destroy
|
13
15
|
|
14
|
-
validates :status, inclusion: { in: %w[pending approved rejected kyc_approved kyc_rejected] }
|
15
|
-
enum :status, { pending: "pending", approved: "approved", rejected: "rejected", kyc_approved: "kyc_approved", kyc_rejected: "kyc_rejected" }
|
16
|
+
validates :status, inclusion: { in: %w[pending approved rejected manual_review kyc_approved kyc_rejected] }
|
17
|
+
enum :status, { pending: "pending", approved: "approved", rejected: "rejected", manual_review: "manual_review", kyc_approved: "kyc_approved", kyc_rejected: "kyc_rejected" }
|
16
18
|
validates :total_amount, :available_amount, numericality: { greater_than_or_equal_to: 0 }
|
17
19
|
validate :available_amount_not_greater_than_total_amount
|
18
20
|
|
19
21
|
scope :pending, -> { where(status: "pending") }
|
20
22
|
scope :approved, -> { where(status: "approved") }
|
21
23
|
scope :rejected, -> { where(status: "rejected") }
|
24
|
+
scope :manual_review, -> { where(status: "manual_review") }
|
22
25
|
scope :kyc_approved, -> { where(status: "kyc_approved") }
|
23
26
|
scope :kyc_rejected, -> { where(status: "kyc_rejected") }
|
24
27
|
scope :with_available_amount, -> { where("available_amount > 0") }
|
@@ -26,11 +29,11 @@ module Dscf::Credit
|
|
26
29
|
scope :by_available_amount_range, ->(min, max) { where(available_amount: min..max) }
|
27
30
|
|
28
31
|
def self.ransackable_attributes(auth_object = nil)
|
29
|
-
%w[id status total_amount available_amount review_feedback review_date created_at updated_at reviewed_by_type reviewed_by_id]
|
32
|
+
%w[id status total_amount available_amount review_feedback review_date created_at updated_at reviewed_by_type reviewed_by_id review_branch_id]
|
30
33
|
end
|
31
34
|
|
32
35
|
def self.ransackable_associations(auth_object = nil)
|
33
|
-
%w[bank user backer reviewed_by loan_profile_scoring_specs loans]
|
36
|
+
%w[bank user backer reviewed_by review_branch loan_profile_scoring_specs loans eligible_credit_lines]
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
@@ -4,19 +4,18 @@ module Dscf::Credit
|
|
4
4
|
|
5
5
|
belongs_to :loan_profile, class_name: "Dscf::Credit::LoanProfile", foreign_key: "loan_profile_id"
|
6
6
|
belongs_to :created_by, polymorphic: true
|
7
|
-
belongs_to :reviewed_by, polymorphic: true
|
8
7
|
|
9
|
-
validates :
|
10
|
-
validates :
|
8
|
+
validates :score, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
9
|
+
validates :total_limit, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
11
10
|
|
12
11
|
scope :active, -> { where(active: true) }
|
13
12
|
|
14
13
|
def self.ransackable_attributes(auth_object = nil)
|
15
|
-
%w[id score active
|
14
|
+
%w[id score total_limit active created_at updated_at]
|
16
15
|
end
|
17
16
|
|
18
17
|
def self.ransackable_associations(auth_object = nil)
|
19
|
-
%w[loan_profile created_by
|
18
|
+
%w[loan_profile created_by]
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
@@ -9,7 +9,7 @@ module Dscf::Credit
|
|
9
9
|
belongs_to :previous_version, class_name: "Dscf::Credit::ScoringParameter", optional: true
|
10
10
|
has_many :parameter_normalizers, class_name: "Dscf::Credit::ParameterNormalizer", foreign_key: "scoring_parameter_id", dependent: :destroy
|
11
11
|
has_many :scoring_tables, class_name: "Dscf::Credit::ScoringTable", foreign_key: "scoring_parameter_id", dependent: :destroy
|
12
|
-
has_many :
|
12
|
+
has_many :categories, through: :scoring_tables
|
13
13
|
|
14
14
|
validates :name, :data_type, :weight, presence: true
|
15
15
|
validates :weight, numericality: { greater_than: 0, less_than_or_equal_to: 1 }
|
@@ -25,7 +25,7 @@ module Dscf::Credit
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def self.ransackable_associations(auth_object = nil)
|
28
|
-
%w[bank created_by reviewed_by scoring_param_type previous_version parameter_normalizers scoring_tables
|
28
|
+
%w[bank created_by reviewed_by scoring_param_type previous_version parameter_normalizers scoring_tables categories]
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -2,15 +2,15 @@ module Dscf::Credit
|
|
2
2
|
class ScoringTable < ApplicationRecord
|
3
3
|
self.table_name = "dscf_credit_scoring_tables"
|
4
4
|
|
5
|
-
belongs_to :
|
5
|
+
belongs_to :category, class_name: "Dscf::Credit::Category", foreign_key: "category_id"
|
6
6
|
belongs_to :scoring_parameter, class_name: "Dscf::Credit::ScoringParameter", foreign_key: "scoring_parameter_id"
|
7
7
|
belongs_to :created_by, polymorphic: true
|
8
8
|
|
9
9
|
validates :weight, presence: true, numericality: { greater_than: 0, less_than_or_equal_to: 1 }
|
10
|
-
validates :
|
10
|
+
validates :category_id, uniqueness: { scope: :scoring_parameter_id }
|
11
11
|
|
12
12
|
scope :active, -> { where(active: true) }
|
13
|
-
scope :
|
13
|
+
scope :by_category, ->(category_id) { where(category_id: category_id) }
|
14
14
|
scope :by_scoring_parameter, ->(scoring_parameter_id) { where(scoring_parameter_id: scoring_parameter_id) }
|
15
15
|
|
16
16
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -18,7 +18,7 @@ module Dscf::Credit
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def self.ransackable_associations(auth_object = nil)
|
21
|
-
%w[
|
21
|
+
%w[category scoring_parameter created_by]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|