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.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/dscf/core/authenticatable.rb +81 -0
  3. data/app/controllers/concerns/dscf/core/common.rb +200 -0
  4. data/app/controllers/concerns/dscf/core/filterable.rb +12 -0
  5. data/app/controllers/concerns/dscf/core/json_response.rb +77 -0
  6. data/app/controllers/concerns/dscf/core/pagination.rb +71 -0
  7. data/app/controllers/concerns/dscf/core/token_authenticatable.rb +53 -0
  8. data/app/controllers/concerns/dscf/credit/reviewable.rb +112 -0
  9. data/app/controllers/dscf/credit/categories_controller.rb +6 -5
  10. data/app/controllers/dscf/credit/credit_limit_calculations_controller.rb +50 -0
  11. data/app/controllers/dscf/credit/credit_line_specs_controller.rb +2 -1
  12. data/app/controllers/dscf/credit/credit_lines_controller.rb +11 -38
  13. data/app/controllers/dscf/credit/disbursements_controller.rb +55 -0
  14. data/app/controllers/dscf/credit/eligible_credit_lines_controller.rb +50 -0
  15. data/app/controllers/dscf/credit/facilitators_controller.rb +39 -150
  16. data/app/controllers/dscf/credit/loan_profiles_controller.rb +138 -0
  17. data/app/controllers/dscf/credit/payment_requests_controller.rb +54 -5
  18. data/app/controllers/dscf/credit/repayments_controller.rb +53 -0
  19. data/app/controllers/dscf/credit/scoring_param_types_controller.rb +31 -0
  20. data/app/controllers/dscf/credit/scoring_parameters_controller.rb +13 -8
  21. data/app/controllers/dscf/credit/scoring_tables_controller.rb +8 -8
  22. data/app/controllers/dscf/credit/system_configs_controller.rb +10 -7
  23. data/app/models/dscf/credit/bank_branch.rb +2 -1
  24. data/app/models/dscf/credit/category.rb +4 -2
  25. data/app/models/dscf/credit/credit_line.rb +9 -6
  26. data/app/models/dscf/credit/credit_line_spec.rb +3 -3
  27. data/app/models/dscf/credit/eligible_credit_line.rb +28 -0
  28. data/app/models/dscf/credit/facilitator.rb +5 -4
  29. data/app/models/dscf/credit/facilitator_performance.rb +1 -2
  30. data/app/models/dscf/credit/loan_profile.rb +8 -4
  31. data/app/models/dscf/credit/loan_profile_scoring_spec.rb +4 -6
  32. data/app/models/dscf/credit/scoring_param_type.rb +17 -0
  33. data/app/models/dscf/credit/scoring_parameter.rb +8 -7
  34. data/app/models/dscf/credit/scoring_table.rb +4 -4
  35. data/app/models/dscf/credit/system_config.rb +5 -4
  36. data/app/serializers/dscf/credit/bank_branch_serializer.rb +1 -0
  37. data/app/serializers/dscf/credit/category_serializer.rb +3 -1
  38. data/app/serializers/dscf/credit/credit_line_serializer.rb +4 -2
  39. data/app/serializers/dscf/credit/credit_line_spec_serializer.rb +1 -1
  40. data/app/serializers/dscf/credit/daily_routine_transaction_serializer.rb +8 -0
  41. data/app/serializers/dscf/credit/eligible_credit_line_serializer.rb +8 -0
  42. data/app/serializers/dscf/credit/facilitator_performance_serializer.rb +1 -1
  43. data/app/serializers/dscf/credit/facilitator_serializer.rb +2 -2
  44. data/app/serializers/dscf/credit/loan_profile_scoring_spec_serializer.rb +8 -0
  45. data/app/serializers/dscf/credit/loan_profile_serializer.rb +15 -0
  46. data/app/serializers/dscf/credit/loan_serializer.rb +12 -0
  47. data/app/serializers/dscf/credit/loan_transaction_serializer.rb +8 -0
  48. data/app/serializers/dscf/credit/payment_request_serializer.rb +10 -0
  49. data/app/serializers/dscf/credit/payment_serializer.rb +8 -0
  50. data/app/serializers/dscf/credit/scoring_param_type_serializer.rb +7 -0
  51. data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +6 -3
  52. data/app/serializers/dscf/credit/scoring_table_serializer.rb +1 -1
  53. data/app/serializers/dscf/credit/system_config_serializer.rb +2 -2
  54. data/app/services/dscf/credit/credit_limit_calculation_service.rb +153 -0
  55. data/app/services/dscf/credit/disbursement_service.rb +180 -0
  56. data/app/services/dscf/credit/facilitator_approval_service.rb +4 -3
  57. data/app/services/dscf/credit/facilitator_creation_service.rb +157 -0
  58. data/app/services/dscf/credit/repayment_service.rb +216 -0
  59. data/app/services/dscf/credit/risk_application_service.rb +27 -0
  60. data/app/services/dscf/credit/scoring_service.rb +297 -0
  61. data/config/locales/en.yml +125 -8
  62. data/config/routes.rb +42 -11
  63. data/db/migrate/20250822091011_create_dscf_credit_categories.rb +2 -0
  64. data/db/migrate/20250822091131_create_dscf_credit_credit_lines.rb +7 -4
  65. data/db/migrate/20250822091527_create_dscf_credit_credit_line_specs.rb +1 -0
  66. data/db/migrate/20250822091820_create_dscf_credit_system_configs.rb +5 -2
  67. data/db/migrate/20250822092040_create_dscf_credit_scoring_param_types.rb +12 -0
  68. data/db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb +11 -6
  69. data/db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb +6 -3
  70. data/db/migrate/20250822092417_create_dscf_credit_loan_profile_scoring_specs.rb +5 -7
  71. data/db/migrate/20250822092436_create_dscf_credit_facilitators.rb +5 -2
  72. data/db/migrate/20250822092528_create_dscf_credit_facilitator_performances.rb +0 -3
  73. data/db/migrate/20250901172842_create_dscf_credit_scoring_tables.rb +2 -2
  74. data/db/migrate/20250917120000_create_dscf_credit_eligible_credit_lines.rb +18 -0
  75. data/db/seeds.rb +134 -40
  76. data/lib/dscf/credit/version.rb +1 -1
  77. data/spec/factories/dscf/credit/categories.rb +1 -0
  78. data/spec/factories/dscf/credit/credit_line_specs.rb +1 -0
  79. data/spec/factories/dscf/credit/credit_lines.rb +9 -7
  80. data/spec/factories/dscf/credit/eligible_credit_lines.rb +33 -0
  81. data/spec/factories/dscf/credit/facilitator_performances.rb +0 -5
  82. data/spec/factories/dscf/credit/facilitators.rb +6 -1
  83. data/spec/factories/dscf/credit/loan_profile_scoring_specs.rb +1 -7
  84. data/spec/factories/dscf/credit/loan_profiles.rb +11 -6
  85. data/spec/factories/dscf/credit/scoring_param_types.rb +31 -0
  86. data/spec/factories/dscf/credit/scoring_parameters.rb +26 -4
  87. data/spec/factories/dscf/credit/scoring_tables.rb +1 -1
  88. data/spec/factories/dscf/credit/system_configs.rb +8 -2
  89. 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
- :approved_by_type,
54
- :approved_by_id,
55
- :approval_date
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, :approved_by, :credit_line_specs ]
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 approval_date created_at updated_at category_id]
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, :approved_by, :credit_line_specs ],
71
- create: [ :bank, :category, :created_by, :approved_by ],
72
- update: [ :bank, :category, :created_by, :approved_by, :credit_line_specs ]
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
- user = Dscf::Core::User.find(model_params[:user_id])
11
- business = user.businesses.first
12
-
13
- unless business
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("facilitator.success.approved", data: @facilitator)
20
+ render_success(data: @facilitator)
43
21
  rescue StandardError => e
44
- render_error("facilitator.errors.approval_failed", errors: e.message)
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[:rejection_reason])
51
- render_success("facilitator.success.rejected", data: @facilitator)
28
+ FacilitatorApprovalService.new(@facilitator, current_user).reject(params[:review_feedback])
29
+ render_success(data: @facilitator)
52
30
  rescue StandardError => e
53
- render_error("facilitator.errors.rejection_failed", errors: e.message)
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("facilitator.success.limit_updated", data: @facilitator)
38
+ render_success(data: @facilitator)
61
39
  rescue StandardError => e
62
- render_error("facilitator.errors.limit_update_failed", errors: e.message)
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("facilitator.success.additional_info_submitted", data: result)
50
+ render_success(data: result)
73
51
  rescue StandardError => e
74
- render_error("facilitator.errors.additional_info_failed", errors: e.message)
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
- unless facilitators_params.is_a?(Array)
82
- return render_error("facilitator.errors.invalid_batch_format",
83
- errors: "Expected an array of facilitator objects")
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
- facilitators_params.each_with_index do |facilitator_attrs, index|
105
- begin
106
- if facilitator_attrs.is_a?(ActionController::Parameters)
107
- permitted_attrs = facilitator_attrs.permit(:user_id, :bank_id, :kyc_status)
108
- else
109
- permitted_attrs = ActionController::Parameters.new(facilitator_attrs)
110
- .permit(:user_id, :bank_id, :kyc_status)
111
- end
112
-
113
- user = Dscf::Core::User.find(permitted_attrs[:user_id])
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
- end
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
- :additional_info
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, :kyc_approved_by, :facilitator_performances ]
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, :kyc_approved_by, :facilitator_performances ],
219
- create: [ :user, :bank, :kyc_approved_by ],
220
- update: [ :user, :bank, :kyc_approved_by, :facilitator_performances ],
221
- approve: [ :user, :bank, :kyc_approved_by, :facilitator_performances ],
222
- reject: [ :user, :bank, :kyc_approved_by ],
223
- set_limit: [ :user, :bank, :kyc_approved_by ]
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
- :initiated_at,
17
- :approved_at
18
- )
66
+ :failure_reason
67
+ )
19
68
  end
20
69
 
21
70
  def eager_loaded_associations