dscf-credit 0.1.1 → 0.1.3
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/concerns/dscf/credit/reviewable.rb +112 -0
- data/app/controllers/dscf/credit/categories_controller.rb +6 -5
- 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 +11 -38
- 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 +39 -150
- 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_param_types_controller.rb +31 -0
- data/app/controllers/dscf/credit/scoring_parameters_controller.rb +13 -8
- data/app/controllers/dscf/credit/scoring_tables_controller.rb +8 -8
- data/app/controllers/dscf/credit/system_configs_controller.rb +10 -7
- data/app/models/dscf/credit/bank_branch.rb +2 -1
- data/app/models/dscf/credit/category.rb +4 -2
- data/app/models/dscf/credit/credit_line.rb +9 -6
- 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/facilitator.rb +5 -4
- data/app/models/dscf/credit/facilitator_performance.rb +1 -2
- data/app/models/dscf/credit/loan_profile.rb +8 -4
- data/app/models/dscf/credit/loan_profile_scoring_spec.rb +4 -6
- data/app/models/dscf/credit/scoring_param_type.rb +17 -0
- data/app/models/dscf/credit/scoring_parameter.rb +8 -7
- data/app/models/dscf/credit/scoring_table.rb +4 -4
- data/app/models/dscf/credit/system_config.rb +5 -4
- data/app/serializers/dscf/credit/bank_branch_serializer.rb +1 -0
- data/app/serializers/dscf/credit/category_serializer.rb +3 -1
- data/app/serializers/dscf/credit/credit_line_serializer.rb +4 -2
- 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/facilitator_performance_serializer.rb +1 -1
- data/app/serializers/dscf/credit/facilitator_serializer.rb +2 -2
- 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_param_type_serializer.rb +7 -0
- data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +6 -3
- data/app/serializers/dscf/credit/scoring_table_serializer.rb +1 -1
- data/app/serializers/dscf/credit/system_config_serializer.rb +2 -2
- 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 +4 -3
- data/app/services/dscf/credit/facilitator_creation_service.rb +157 -0
- 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 +125 -8
- data/config/routes.rb +42 -11
- data/db/migrate/20250822091011_create_dscf_credit_categories.rb +2 -0
- data/db/migrate/20250822091131_create_dscf_credit_credit_lines.rb +7 -4
- data/db/migrate/20250822091527_create_dscf_credit_credit_line_specs.rb +1 -0
- data/db/migrate/20250822091820_create_dscf_credit_system_configs.rb +5 -2
- data/db/migrate/20250822092040_create_dscf_credit_scoring_param_types.rb +12 -0
- data/db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb +11 -6
- data/db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb +6 -3
- data/db/migrate/20250822092417_create_dscf_credit_loan_profile_scoring_specs.rb +5 -7
- data/db/migrate/20250822092436_create_dscf_credit_facilitators.rb +5 -2
- data/db/migrate/20250822092528_create_dscf_credit_facilitator_performances.rb +0 -3
- 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 +134 -40
- data/lib/dscf/credit/version.rb +1 -1
- data/spec/factories/dscf/credit/categories.rb +1 -0
- data/spec/factories/dscf/credit/credit_line_specs.rb +1 -0
- data/spec/factories/dscf/credit/credit_lines.rb +9 -7
- data/spec/factories/dscf/credit/eligible_credit_lines.rb +33 -0
- data/spec/factories/dscf/credit/facilitator_performances.rb +0 -5
- data/spec/factories/dscf/credit/facilitators.rb +6 -1
- data/spec/factories/dscf/credit/loan_profile_scoring_specs.rb +1 -7
- data/spec/factories/dscf/credit/loan_profiles.rb +11 -6
- data/spec/factories/dscf/credit/scoring_param_types.rb +31 -0
- data/spec/factories/dscf/credit/scoring_parameters.rb +26 -4
- data/spec/factories/dscf/credit/scoring_tables.rb +1 -1
- data/spec/factories/dscf/credit/system_configs.rb +8 -2
- metadata +50 -2
@@ -40,6 +40,7 @@ module Dscf::Credit
|
|
40
40
|
:penalty_rate,
|
41
41
|
:facilitation_fee_rate,
|
42
42
|
:tax_rate,
|
43
|
+
:credit_line_multiplier,
|
43
44
|
:max_penalty_days,
|
44
45
|
:loan_duration,
|
45
46
|
:interest_frequency,
|
@@ -57,7 +58,7 @@ module Dscf::Credit
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def allowed_order_columns
|
60
|
-
%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]
|
61
|
+
%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]
|
61
62
|
end
|
62
63
|
|
63
64
|
def default_serializer_includes
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Dscf::Credit
|
2
2
|
class CreditLinesController < ApplicationController
|
3
3
|
include Dscf::Core::Common
|
4
|
+
include Dscf::Credit::Reviewable
|
4
5
|
|
5
6
|
def create
|
6
7
|
super do
|
@@ -10,36 +11,6 @@ module Dscf::Credit
|
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
13
|
-
def approve
|
14
|
-
@obj = @clazz.find(params[:id])
|
15
|
-
if @obj.update(
|
16
|
-
status: "approved",
|
17
|
-
approved_by: current_user,
|
18
|
-
approval_date: Time.current
|
19
|
-
)
|
20
|
-
render_success(data: @obj)
|
21
|
-
else
|
22
|
-
render_error(errors: @obj.errors.full_messages, status: :unprocessable_entity)
|
23
|
-
end
|
24
|
-
rescue StandardError => e
|
25
|
-
render_error(error: e.message)
|
26
|
-
end
|
27
|
-
|
28
|
-
def reject
|
29
|
-
@obj = @clazz.find(params[:id])
|
30
|
-
if @obj.update(
|
31
|
-
status: "pending",
|
32
|
-
approved_by: current_user,
|
33
|
-
approval_date: Time.current
|
34
|
-
)
|
35
|
-
render_success(data: @obj)
|
36
|
-
else
|
37
|
-
render_error(errors: @obj.errors.full_messages, status: :unprocessable_entity)
|
38
|
-
end
|
39
|
-
rescue StandardError => e
|
40
|
-
render_error(error: e.message)
|
41
|
-
end
|
42
|
-
|
43
14
|
private
|
44
15
|
|
45
16
|
def model_params
|
@@ -50,26 +21,28 @@ module Dscf::Credit
|
|
50
21
|
:code,
|
51
22
|
:description,
|
52
23
|
:status,
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
24
|
+
:reviewed_by_type,
|
25
|
+
:reviewed_by_id,
|
26
|
+
:review_date,
|
27
|
+
:document_reference,
|
28
|
+
:review_feedback
|
56
29
|
)
|
57
30
|
end
|
58
31
|
|
59
32
|
def eager_loaded_associations
|
60
|
-
[ :bank, :category, :created_by, :
|
33
|
+
[ :bank, :category, :created_by, :reviewed_by, :credit_line_specs, :loans, :eligible_credit_lines ]
|
61
34
|
end
|
62
35
|
|
63
36
|
def allowed_order_columns
|
64
|
-
%w[id name code status
|
37
|
+
%w[id name code status review_date created_at updated_at category_id]
|
65
38
|
end
|
66
39
|
|
67
40
|
def default_serializer_includes
|
68
41
|
{
|
69
42
|
index: [ :bank, :category ],
|
70
|
-
show: [ :bank, :category, :created_by, :
|
71
|
-
create: [ :bank, :category, :created_by, :
|
72
|
-
update: [ :bank, :category, :created_by, :
|
43
|
+
show: [ :bank, :category, :created_by, :reviewed_by, :credit_line_specs, :loans, :eligible_credit_lines ],
|
44
|
+
create: [ :bank, :category, :created_by, :reviewed_by ],
|
45
|
+
update: [ :bank, :category, :created_by, :reviewed_by, :credit_line_specs, :loans, :eligible_credit_lines ]
|
73
46
|
}
|
74
47
|
end
|
75
48
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Dscf::Credit
|
2
|
+
class DisbursementsController < ApplicationController
|
3
|
+
def create
|
4
|
+
credit_line = Dscf::Credit::CreditLine.find(params[:credit_line_id])
|
5
|
+
payment_request = Dscf::Credit::PaymentRequest.find(params[:payment_request_id])
|
6
|
+
|
7
|
+
service = Dscf::Credit::DisbursementService.new(credit_line, payment_request, current_user)
|
8
|
+
result = service.process_disbursement
|
9
|
+
|
10
|
+
if result[:success]
|
11
|
+
render_success(
|
12
|
+
"disbursement.success.create",
|
13
|
+
data: {
|
14
|
+
loan: result[:loan],
|
15
|
+
disbursement_details: result[:disbursement_details],
|
16
|
+
credit_line: credit_line,
|
17
|
+
payment_request: payment_request
|
18
|
+
},
|
19
|
+
serializer_options: {
|
20
|
+
include: [
|
21
|
+
:loan_profile,
|
22
|
+
:credit_line,
|
23
|
+
:payment_request,
|
24
|
+
:loan_transactions
|
25
|
+
]
|
26
|
+
}
|
27
|
+
)
|
28
|
+
else
|
29
|
+
render_error(
|
30
|
+
"disbursement.errors.create",
|
31
|
+
errors: [ result[:error] ],
|
32
|
+
status: :unprocessable_entity
|
33
|
+
)
|
34
|
+
end
|
35
|
+
rescue ActiveRecord::RecordNotFound => e
|
36
|
+
render_error(
|
37
|
+
"disbursement.errors.not_found",
|
38
|
+
errors: [ e.message ],
|
39
|
+
status: :not_found
|
40
|
+
)
|
41
|
+
rescue StandardError => e
|
42
|
+
render_error(
|
43
|
+
"disbursement.errors.create",
|
44
|
+
errors: [ e.message ],
|
45
|
+
status: :internal_server_error
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def model_params
|
52
|
+
params.permit(:credit_line_id, :payment_request_id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -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
|
@@ -2,36 +2,14 @@ module Dscf::Credit
|
|
2
2
|
class FacilitatorsController < ApplicationController
|
3
3
|
include Dscf::Core::Common
|
4
4
|
|
5
|
-
skip_before_action :authenticate_user, only: [ :submit_additional_info ]
|
6
5
|
before_action :set_facilitator_for_approval, only: [ :approve, :reject, :set_limit ]
|
7
6
|
|
8
7
|
def create
|
9
8
|
super do
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
return render_error("facilitator.errors.no_business",
|
15
|
-
errors: "User must have a business associated to become a facilitator")
|
16
|
-
end
|
17
|
-
|
18
|
-
facilitator_data = model_params.to_h.merge(
|
19
|
-
name: business.name,
|
20
|
-
type: business.business_type.name.downcase
|
21
|
-
)
|
22
|
-
|
23
|
-
facilitator = @clazz.new(facilitator_data)
|
24
|
-
facilitator.kyc_approved_by = current_user
|
25
|
-
if facilitator.save
|
26
|
-
facilitator.facilitator_performances.create!(
|
27
|
-
total_outstanding_loans: 0,
|
28
|
-
total_outstanding_amount: 0.0,
|
29
|
-
approval_required: false,
|
30
|
-
created_by: current_user
|
31
|
-
)
|
32
|
-
facilitator
|
33
|
-
else
|
34
|
-
facilitator
|
9
|
+
begin
|
10
|
+
FacilitatorCreationService.new(current_user, @clazz).create_single(model_params)
|
11
|
+
rescue StandardError => e
|
12
|
+
return render_error(errors: e.message)
|
35
13
|
end
|
36
14
|
end
|
37
15
|
end
|
@@ -39,27 +17,27 @@ module Dscf::Credit
|
|
39
17
|
def approve
|
40
18
|
begin
|
41
19
|
FacilitatorApprovalService.new(@facilitator, current_user).approve(params[:total_limit])
|
42
|
-
render_success(
|
20
|
+
render_success(data: @facilitator)
|
43
21
|
rescue StandardError => e
|
44
|
-
render_error(
|
22
|
+
render_error(errors: e.message)
|
45
23
|
end
|
46
24
|
end
|
47
25
|
|
48
26
|
def reject
|
49
27
|
begin
|
50
|
-
FacilitatorApprovalService.new(@facilitator, current_user).reject(params[:
|
51
|
-
render_success(
|
28
|
+
FacilitatorApprovalService.new(@facilitator, current_user).reject(params[:review_feedback])
|
29
|
+
render_success(data: @facilitator)
|
52
30
|
rescue StandardError => e
|
53
|
-
render_error(
|
31
|
+
render_error(errors: e.message)
|
54
32
|
end
|
55
33
|
end
|
56
34
|
|
57
35
|
def set_limit
|
58
36
|
begin
|
59
37
|
FacilitatorApprovalService.new(@facilitator, current_user).set_limit(params[:total_limit])
|
60
|
-
render_success(
|
38
|
+
render_success(data: @facilitator)
|
61
39
|
rescue StandardError => e
|
62
|
-
render_error(
|
40
|
+
render_error(errors: e.message)
|
63
41
|
end
|
64
42
|
end
|
65
43
|
|
@@ -69,123 +47,32 @@ module Dscf::Credit
|
|
69
47
|
|
70
48
|
begin
|
71
49
|
result = FacilitatorAdditionalInfoService.new.submit_info(facilitator_id, additional_info)
|
72
|
-
render_success(
|
50
|
+
render_success(data: result)
|
73
51
|
rescue StandardError => e
|
74
|
-
render_error(
|
52
|
+
render_error(errors: e.message)
|
75
53
|
end
|
76
54
|
end
|
77
55
|
|
78
56
|
def batch_create
|
79
57
|
facilitators_params = params[:facilitators]
|
80
58
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
if facilitators_params.empty?
|
87
|
-
return render_error("facilitator.errors.empty_batch",
|
88
|
-
errors: "At least one facilitator is required")
|
89
|
-
end
|
90
|
-
|
91
|
-
if facilitators_params.length > 100
|
92
|
-
return render_error("facilitator.errors.batch_too_large",
|
93
|
-
errors: "Maximum 100 facilitators allowed per batch")
|
94
|
-
end
|
95
|
-
|
96
|
-
results = {
|
97
|
-
successful: [],
|
98
|
-
failed: [],
|
99
|
-
total_count: facilitators_params.length,
|
100
|
-
success_count: 0,
|
101
|
-
failure_count: 0
|
102
|
-
}
|
59
|
+
begin
|
60
|
+
service = FacilitatorCreationService.new(current_user, @clazz)
|
61
|
+
results = service.create_batch(facilitators_params)
|
103
62
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
business = user.businesses.first
|
115
|
-
|
116
|
-
unless business
|
117
|
-
results[:failed] << {
|
118
|
-
index: index,
|
119
|
-
user_id: permitted_attrs[:user_id],
|
120
|
-
errors: [ "User must have a business associated to become a facilitator" ]
|
121
|
-
}
|
122
|
-
results[:failure_count] += 1
|
123
|
-
next
|
124
|
-
end
|
125
|
-
|
126
|
-
facilitator_data = permitted_attrs.to_h.merge(
|
127
|
-
name: business.name,
|
128
|
-
type: business.business_type.name.downcase
|
129
|
-
)
|
130
|
-
|
131
|
-
facilitator = @clazz.new(facilitator_data)
|
132
|
-
facilitator.kyc_approved_by = current_user
|
133
|
-
|
134
|
-
if facilitator.save
|
135
|
-
facilitator.facilitator_performances.create!(
|
136
|
-
total_outstanding_loans: 0,
|
137
|
-
total_outstanding_amount: 0.0,
|
138
|
-
approval_required: false,
|
139
|
-
created_by: current_user
|
140
|
-
)
|
141
|
-
|
142
|
-
results[:successful] << {
|
143
|
-
index: index,
|
144
|
-
id: facilitator.id,
|
145
|
-
name: facilitator.name,
|
146
|
-
message: "Successfully created"
|
147
|
-
}
|
148
|
-
results[:success_count] += 1
|
149
|
-
else
|
150
|
-
results[:failed] << {
|
151
|
-
index: index,
|
152
|
-
name: business&.name || "Unknown Business",
|
153
|
-
errors: facilitator.errors.full_messages
|
154
|
-
}
|
155
|
-
results[:failure_count] += 1
|
156
|
-
end
|
157
|
-
rescue StandardError => e
|
158
|
-
user_id = permitted_attrs&.[](:user_id) || facilitator_attrs.try(:[], :user_id)
|
159
|
-
business_name = "Unknown Business"
|
160
|
-
|
161
|
-
if user_id
|
162
|
-
begin
|
163
|
-
user = Dscf::Core::User.find(user_id)
|
164
|
-
business_name = user.businesses.first&.name || "No Business Associated"
|
165
|
-
rescue
|
166
|
-
business_name = "Invalid User ID"
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
results[:failed] << {
|
171
|
-
index: index,
|
172
|
-
name: business_name,
|
173
|
-
errors: [ e.message ]
|
174
|
-
}
|
175
|
-
results[:failure_count] += 1
|
63
|
+
if results[:failure_count] == 0
|
64
|
+
render_success("facilitator.success.batch_created_all", data: results)
|
65
|
+
elsif results[:success_count] == 0
|
66
|
+
render_error("facilitator.errors.batch_failed_all", errors: results)
|
67
|
+
else
|
68
|
+
render json: {
|
69
|
+
success: true,
|
70
|
+
message: I18n.t("facilitator.success.batch_created_partial"),
|
71
|
+
data: results
|
72
|
+
}, status: :multi_status
|
176
73
|
end
|
177
|
-
|
178
|
-
|
179
|
-
if results[:failure_count] == 0
|
180
|
-
render_success("facilitator.success.batch_created_all", data: results)
|
181
|
-
elsif results[:success_count] == 0
|
182
|
-
render_error("facilitator.errors.batch_failed_all", errors: results)
|
183
|
-
else
|
184
|
-
render json: {
|
185
|
-
success: true,
|
186
|
-
message: I18n.t("facilitator.success.batch_created_partial"),
|
187
|
-
data: results
|
188
|
-
}, status: :multi_status
|
74
|
+
rescue StandardError => e
|
75
|
+
render_error("facilitator.errors.batch_creation_failed", errors: e.message)
|
189
76
|
end
|
190
77
|
end
|
191
78
|
|
@@ -196,7 +83,9 @@ module Dscf::Credit
|
|
196
83
|
:user_id,
|
197
84
|
:bank_id,
|
198
85
|
:kyc_status,
|
199
|
-
:
|
86
|
+
:kyc_review_date,
|
87
|
+
:additional_info,
|
88
|
+
:review_feedback
|
200
89
|
)
|
201
90
|
end
|
202
91
|
|
@@ -205,22 +94,22 @@ module Dscf::Credit
|
|
205
94
|
end
|
206
95
|
|
207
96
|
def eager_loaded_associations
|
208
|
-
[ :user, :bank, :
|
97
|
+
[ :user, :bank, :kyc_reviewed_by, :facilitator_performances ]
|
209
98
|
end
|
210
99
|
|
211
100
|
def allowed_order_columns
|
212
|
-
%w[id name type kyc_status total_limit created_at updated_at]
|
101
|
+
%w[id name type kyc_status total_limit kyc_review_date created_at updated_at]
|
213
102
|
end
|
214
103
|
|
215
104
|
def default_serializer_includes
|
216
105
|
{
|
217
106
|
index: [ :user, :bank ],
|
218
|
-
show: [ :user, :bank, :
|
219
|
-
create: [ :user, :bank, :
|
220
|
-
update: [ :user, :bank, :
|
221
|
-
approve: [ :user, :bank, :
|
222
|
-
reject: [ :user, :bank, :
|
223
|
-
set_limit: [ :user, :bank, :
|
107
|
+
show: [ :user, :bank, :kyc_reviewed_by, :facilitator_performances ],
|
108
|
+
create: [ :user, :bank, :kyc_reviewed_by ],
|
109
|
+
update: [ :user, :bank, :kyc_reviewed_by, :facilitator_performances ],
|
110
|
+
approve: [ :user, :bank, :kyc_reviewed_by, :facilitator_performances ],
|
111
|
+
reject: [ :user, :bank, :kyc_reviewed_by ],
|
112
|
+
set_limit: [ :user, :bank, :kyc_reviewed_by ]
|
224
113
|
}
|
225
114
|
end
|
226
115
|
end
|
@@ -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
|