dscf-credit 0.1.4 → 0.1.5

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/dscf/core/copilot-instructions.md +683 -0
  3. data/app/controllers/concerns/dscf/core/reviewable_controller.rb +347 -0
  4. data/app/controllers/dscf/credit/categories_controller.rb +3 -3
  5. data/app/controllers/dscf/credit/credit_lines_controller.rb +21 -13
  6. data/app/controllers/dscf/credit/eligible_credit_lines_controller.rb +50 -8
  7. data/app/controllers/dscf/credit/facilitator_applications_controller.rb +35 -0
  8. data/app/controllers/dscf/credit/facilitators_controller.rb +8 -96
  9. data/app/controllers/dscf/credit/loan_applications_controller.rb +252 -0
  10. data/app/controllers/dscf/credit/loan_profiles_controller.rb +61 -68
  11. data/app/controllers/dscf/credit/payment_requests_controller.rb +3 -5
  12. data/app/controllers/dscf/credit/scoring_parameters_controller.rb +59 -13
  13. data/app/controllers/dscf/credit/system_configs_controller.rb +30 -12
  14. data/app/models/concerns/core/reviewable_model.rb +31 -0
  15. data/app/models/dscf/credit/bank.rb +3 -3
  16. data/app/models/dscf/credit/bank_branch.rb +1 -1
  17. data/app/models/dscf/credit/category.rb +1 -2
  18. data/app/models/dscf/credit/credit_line.rb +4 -10
  19. data/app/models/dscf/credit/eligible_credit_line.rb +2 -2
  20. data/app/models/dscf/credit/facilitator.rb +6 -17
  21. data/app/models/dscf/credit/facilitator_application.rb +20 -0
  22. data/app/models/dscf/credit/loan_application.rb +30 -0
  23. data/app/models/dscf/credit/loan_profile.rb +10 -30
  24. data/app/models/dscf/credit/parameter_normalizer.rb +1 -1
  25. data/app/models/dscf/credit/scoring_parameter.rb +5 -7
  26. data/app/models/dscf/credit/system_config.rb +4 -9
  27. data/app/serializers/dscf/credit/category_serializer.rb +0 -1
  28. data/app/serializers/dscf/credit/credit_line_serializer.rb +2 -2
  29. data/app/serializers/dscf/credit/facilitator_application_serializer.rb +7 -0
  30. data/app/serializers/dscf/credit/facilitator_serializer.rb +3 -6
  31. data/app/serializers/dscf/credit/loan_application_serializer.rb +12 -0
  32. data/app/serializers/dscf/credit/loan_profile_serializer.rb +3 -6
  33. data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +3 -4
  34. data/app/serializers/dscf/credit/system_config_serializer.rb +2 -2
  35. data/app/services/dscf/credit/credit_scoring_engine.rb +258 -0
  36. data/app/services/dscf/credit/facility_limit_calculation_engine.rb +159 -0
  37. data/app/services/dscf/credit/loan_profile_creation_service.rb +91 -0
  38. data/app/services/dscf/credit/risk_application_service.rb +61 -11
  39. data/config/locales/en.yml +63 -48
  40. data/config/routes.rb +31 -17
  41. data/db/migrate/20250822091131_create_dscf_credit_credit_lines.rb +1 -8
  42. data/db/migrate/20250822091820_create_dscf_credit_system_configs.rb +0 -7
  43. data/db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb +2 -6
  44. data/db/migrate/20250822092225_create_dscf_credit_parameter_normalizers.rb +1 -1
  45. data/db/migrate/20250822092236_create_dscf_credit_loan_applications.rb +20 -0
  46. data/db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb +7 -19
  47. data/db/migrate/20250822092426_create_dscf_credit_facilitator_applications.rb +10 -0
  48. data/db/migrate/20250822092436_create_dscf_credit_facilitators.rb +1 -16
  49. data/db/seeds.rb +316 -203
  50. data/lib/dscf/credit/version.rb +1 -1
  51. data/spec/factories/dscf/credit/banks.rb +1 -1
  52. data/spec/factories/dscf/credit/credit_lines.rb +0 -23
  53. data/spec/factories/dscf/credit/facilitator_applications.rb +37 -0
  54. data/spec/factories/dscf/credit/facilitators.rb +8 -30
  55. data/spec/factories/dscf/credit/loan_applications.rb +42 -0
  56. data/spec/factories/dscf/credit/loan_profiles.rb +20 -34
  57. data/spec/factories/dscf/credit/parameter_normalizers.rb +4 -4
  58. data/spec/factories/dscf/credit/scoring_parameters.rb +14 -11
  59. data/spec/factories/dscf/credit/system_configs.rb +21 -5
  60. metadata +20 -10
  61. data/app/controllers/concerns/dscf/credit/reviewable.rb +0 -112
  62. data/app/controllers/dscf/credit/scoring_tables_controller.rb +0 -63
  63. data/app/models/dscf/credit/scoring_table.rb +0 -24
  64. data/app/serializers/dscf/credit/scoring_table_serializer.rb +0 -9
  65. data/db/migrate/20250901172842_create_dscf_credit_scoring_tables.rb +0 -18
  66. data/spec/factories/dscf/credit/scoring_tables.rb +0 -25
@@ -0,0 +1,252 @@
1
+ module Dscf::Credit
2
+ class LoanApplicationsController < ApplicationController
3
+ include Dscf::Core::Common
4
+ include Dscf::Core::ReviewableController
5
+
6
+ def create
7
+ super do
8
+ loan_application = @clazz.new(model_params)
9
+ loan_application.user = current_user
10
+ loan_application.reviews.build(context: "default", status: "pending")
11
+
12
+ loan_application
13
+ end
14
+ end
15
+
16
+ def update_bank_info
17
+ loan_application = @clazz.find(params[:id])
18
+ if loan_application.update(bank_info: bank_info_params)
19
+ render_success("loan_application.success.update_bank_info", data: loan_application, serializer_options: { include: [ :bank, :user ] })
20
+ else
21
+ render_error("loan_application.errors.update_bank_info", errors: loan_application.errors.full_messages[0], status: :unprocessable_entity)
22
+ end
23
+ rescue => e
24
+ if Rails.env.development? || Rails.env.test?
25
+ render_error(errors: e.message, status: :unprocessable_entity)
26
+ else
27
+ render_error(status: :unprocessable_entity)
28
+ end
29
+ Rails.logger.error("Unexpected error: #{e.class} - #{e.message}")
30
+ end
31
+
32
+ def update_facilitator_info
33
+ loan_application = @clazz.find(params[:id])
34
+ if loan_application.update(facilitator_info: facilitator_info_params)
35
+ render_success("loan_application.success.update_facilitator_info", data: loan_application, serializer_options: { include: [ :bank, :user ] })
36
+ else
37
+ render_error("loan_application.errors.update_facilitator_info", errors: loan_application.errors.full_messages[0], status: :unprocessable_entity)
38
+ end
39
+ rescue => e
40
+ if Rails.env.development? || Rails.env.test?
41
+ render_error(errors: e.message, status: :unprocessable_entity)
42
+ else
43
+ render_error(status: :unprocessable_entity)
44
+ end
45
+ Rails.logger.error("Unexpected error: #{e.class} - #{e.message}")
46
+ end
47
+
48
+ def update_field_assessment
49
+ loan_application = @clazz.find(params[:id])
50
+ if loan_application.update(field_assessment: field_assessment_params)
51
+ render_success("loan_application.success.update_field_assessment", data: loan_application, serializer_options: { include: [ :bank, :user ] })
52
+ else
53
+ render_error("loan_application.errors.update_field_assessment", errors: loan_application.errors.full_messages[0], status: :unprocessable_entity)
54
+ end
55
+ rescue => e
56
+ if Rails.env.development? || Rails.env.test?
57
+ render_error(errors: e.message, status: :unprocessable_entity)
58
+ else
59
+ render_error(status: :unprocessable_entity)
60
+ end
61
+ end
62
+
63
+ def calculate_credit_score
64
+ loan_application = @clazz.find(params[:id])
65
+ category_id = score_params[:category_id]
66
+
67
+ unless category_id
68
+ return render_error(
69
+ "loan_application.errors.calculate_credit_score",
70
+ errors: [ "Category ID is required for credit scoring" ],
71
+ status: :unprocessable_entity
72
+ )
73
+ end
74
+
75
+ scoring_engine = CreditScoringEngine.new(loan_application.id, category_id)
76
+ result = scoring_engine.calculate_score
77
+
78
+ if result[:success]
79
+ ActiveRecord::Base.transaction do
80
+ loan_application.update!(score: result[:score])
81
+
82
+ update_review_status(loan_application, result[:status])
83
+
84
+ if result[:status] == "approved"
85
+ create_loan_profile_for_approved_application(loan_application, result[:score])
86
+ end
87
+
88
+ loan_application.reload
89
+ end
90
+
91
+ render_success(
92
+ "loan_application.success.calculate_credit_score",
93
+ data: result.merge(loan_application: loan_application),
94
+ status: :ok
95
+ )
96
+ else
97
+ render_error(
98
+ "loan_application.errors.calculate_credit_score",
99
+ errors: result[:errors] || [ result[:error] ],
100
+ status: :unprocessable_entity
101
+ )
102
+ end
103
+ rescue ActiveRecord::RecordNotFound
104
+ render_error(
105
+ "loan_application.errors.not_found",
106
+ errors: [ "Loan application not found" ],
107
+ status: :not_found
108
+ )
109
+ rescue => e
110
+ Rails.logger.error("Credit scoring error: #{e.class} - #{e.message}")
111
+ Rails.logger.error(e.backtrace.join("\n"))
112
+
113
+ if Rails.env.development? || Rails.env.test?
114
+ render_error(
115
+ "loan_application.errors.calculate_credit_score",
116
+ errors: [ e.message ],
117
+ status: :unprocessable_entity
118
+ )
119
+ else
120
+ render_error(
121
+ "loan_application.errors.calculate_credit_score",
122
+ errors: [ "An error occurred while calculating credit score" ],
123
+ status: :unprocessable_entity
124
+ )
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def update_review_status(loan_application, status)
131
+ current_review = loan_application.current_review_for(:default)
132
+
133
+ if current_review
134
+ current_review.update!(
135
+ status: status,
136
+ reviewed_by: current_user,
137
+ reviewed_at: Time.current,
138
+ feedback: build_review_feedback(status, loan_application.score)
139
+ )
140
+ else
141
+ loan_application.reviews.create!(
142
+ context: "default",
143
+ status: status,
144
+ reviewed_by: current_user,
145
+ reviewed_at: Time.current,
146
+ feedback: build_review_feedback(status, loan_application.score)
147
+ )
148
+ end
149
+ end
150
+
151
+ # Build feedback message based on status and score
152
+ def build_review_feedback(status, score)
153
+ case status
154
+ when "approved"
155
+ {
156
+ message: "Automatically approved based on credit score: #{score}% (>60%)"
157
+ }
158
+ when "rejected"
159
+ {
160
+ message: "Automatically rejected based on credit score: #{score}% (<50%)"
161
+ }
162
+ when "pending_review"
163
+ {
164
+ message: "Manual review required based on credit score: #{score}% (50%-60%)"
165
+ }
166
+ else
167
+ {
168
+ message: "Status determined by credit scoring system"
169
+ }
170
+ end
171
+ end
172
+
173
+ # Create loan profile for approved applications
174
+ def create_loan_profile_for_approved_application(loan_application, score)
175
+ profile_service = LoanProfileCreationService.new(loan_application, score)
176
+ profile_result = profile_service.create_loan_profile
177
+
178
+ unless profile_result[:success]
179
+ error_message = "Failed to create loan profile for approved application #{loan_application.id}: #{profile_result[:error]}"
180
+ Rails.logger.error error_message
181
+ # Raise error to rollback the entire transaction including score and review status
182
+ raise StandardError, error_message
183
+ else
184
+ Rails.logger.info "Loan profile created successfully for approved application #{loan_application.id}"
185
+ end
186
+
187
+ profile_result
188
+ end
189
+
190
+ def model_params
191
+ params.require(:loan_application).permit(
192
+ :bank_id,
193
+ :backer_type,
194
+ :backer_id,
195
+ :review_branch_id,
196
+ :bank_statement_source,
197
+ user_info: {},
198
+ facilitator_info: {},
199
+ bank_info: {},
200
+ field_assessment: {},
201
+ bank_statement_attachments: []
202
+ )
203
+ end
204
+
205
+ def bank_info_params
206
+ params.require(:loan_application).require(:bank_info)
207
+ end
208
+
209
+ def facilitator_info_params
210
+ params.require(:loan_application).require(:facilitator_info)
211
+ end
212
+
213
+ def field_assessment_params
214
+ params.require(:loan_application).require(:field_assessment)
215
+ end
216
+
217
+ def score_params
218
+ params.permit(:category_id)
219
+ end
220
+
221
+ def eager_loaded_associations
222
+ [
223
+ :bank, :user, :backer, :review_branch, :loan_profile,
224
+ reviews: { reviewed_by: :user_profile }
225
+ ]
226
+ end
227
+
228
+ def allowed_order_columns
229
+ %w[id bank_statement_source created_at updated_at]
230
+ end
231
+
232
+ def default_serializer_includes
233
+ {
234
+ index: [
235
+ :bank, :user, :backer, :review_branch, :loan_profile,
236
+ reviews: { reviewed_by: :user_profile }
237
+ ],
238
+ show: [
239
+ :bank, :user, :backer, :review_branch, :loan_profile,
240
+ reviews: { reviewed_by: :user_profile }
241
+ ],
242
+ create: [
243
+ :bank, :user, :backer, :review_branch, :loan_profile, :reviews
244
+ ],
245
+ update: [
246
+ :bank, :user, :backer, :review_branch, :loan_profile,
247
+ reviews: { reviewed_by: :user_profile }
248
+ ]
249
+ }
250
+ end
251
+ end
252
+ end
@@ -1,78 +1,46 @@
1
1
  module Dscf::Credit
2
2
  class LoanProfilesController < ApplicationController
3
3
  include Dscf::Core::Common
4
- include Dscf::Credit::Reviewable
4
+ include Dscf::Core::ReviewableController
5
5
 
6
6
  def create
7
7
  super do
8
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
-
9
+ loan_profile.reviews.build(
10
+ status: "pending",
11
+ context: "default",
12
+ )
20
13
  loan_profile
21
14
  end
22
15
  end
23
16
 
24
- def calculate_score
17
+
18
+ def calculate_facility_limits
25
19
  loan_profile = @clazz.find(params[:id])
26
- scoring_service = Dscf::Credit::ScoringService.new(loan_profile)
20
+ category_id = facility_params[:category_id]
27
21
 
28
- external_scoring_data = params[:scoring_input_data]
22
+ return render_error(
23
+ "loan_profile.errors.calculate_facility_limits",
24
+ errors: [ "Category ID is required" ],
25
+ status: :unprocessable_entity
26
+ ) unless category_id
29
27
 
30
- result = scoring_service.calculate_credit_score(external_scoring_data)
28
+ facility_engine = FacilityLimitCalculationEngine.new(loan_profile.id, category_id)
29
+ result = facility_engine.calculate_facility_limits
31
30
 
32
31
  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
32
  loan_profile.reload
64
-
65
33
  render_success(
66
- "loan_profile.success.calculate_score",
34
+ "loan_profile.success.calculate_facility_limits",
67
35
  data: {
68
36
  loan_profile: loan_profile,
69
- scoring_result: result
37
+ facility_result: result
70
38
  },
71
- serializer_options: { include: [ :loan_profile_scoring_specs ] }
39
+ serializer_options: { include: [ :eligible_credit_lines ] }
72
40
  )
73
41
  else
74
42
  render_error(
75
- "loan_profile.errors.calculate_score",
43
+ "loan_profile.errors.calculate_facility_limits",
76
44
  errors: [ result[:error] ],
77
45
  status: :unprocessable_entity
78
46
  )
@@ -82,6 +50,23 @@ module Dscf::Credit
82
50
  "loan_profile.errors.not_found",
83
51
  status: :not_found
84
52
  )
53
+ rescue => e
54
+ Rails.logger.error("Facility limit calculation error: #{e.class} - #{e.message}")
55
+ Rails.logger.error(e.backtrace.join("\n"))
56
+
57
+ if Rails.env.development? || Rails.env.test?
58
+ render_error(
59
+ "loan_profile.errors.calculate_facility_limits",
60
+ errors: [ e.message ],
61
+ status: :unprocessable_entity
62
+ )
63
+ else
64
+ render_error(
65
+ "loan_profile.errors.calculate_facility_limits",
66
+ errors: [ "An error occurred while calculating facility limits" ],
67
+ status: :unprocessable_entity
68
+ )
69
+ end
85
70
  end
86
71
 
87
72
  private
@@ -96,10 +81,10 @@ module Dscf::Credit
96
81
  "approved"
97
82
  end
98
83
 
99
- loan_profile.update!(
84
+ loan_profile.reviews.create!(
100
85
  status: new_status,
101
- reviewed_by: current_user,
102
- review_date: Time.current
86
+ context: "default",
87
+ reviewed_by: current_user
103
88
  )
104
89
  end
105
90
 
@@ -107,31 +92,39 @@ module Dscf::Credit
107
92
 
108
93
  def model_params
109
94
  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
95
+ :loan_application_id,
96
+ :code,
97
+ :score,
98
+ :total_limit
118
99
  )
119
100
  end
120
101
 
102
+ def facility_params
103
+ params.permit(:category_id)
104
+ end
105
+
121
106
  def eager_loaded_associations
122
- [ :bank, :review_branch, :reviewed_by, :user, :backer, :loan_profile_scoring_specs, :loans, :eligible_credit_lines ]
107
+ [
108
+ :loan_application, :loan_profile_scoring_specs, :loans, :eligible_credit_lines,
109
+ reviews: { reviewed_by: :user_profile }
110
+ ]
123
111
  end
124
112
 
125
113
  def allowed_order_columns
126
- %w[id status total_amount available_amount review_date created_at updated_at]
114
+ %w[id code score total_limit created_at updated_at]
127
115
  end
128
116
 
129
117
  def default_serializer_includes
130
118
  {
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 ]
119
+ index: [ :loan_application, reviews: { reviewed_by: :user_profile } ],
120
+ show: [
121
+ :loan_application, :loan_profile_scoring_specs, :loans, :eligible_credit_lines, reviews: { reviewed_by: :user_profile }
122
+ ],
123
+ create: [ :loan_application, :loan_profile_scoring_specs, :reviews ],
124
+ update: [
125
+ :loan_application, :loan_profile_scoring_specs, :loans, :eligible_credit_lines,
126
+ reviews: { reviewed_by: :user_profile }
127
+ ]
135
128
  }
136
129
  end
137
130
  end
@@ -36,11 +36,9 @@ module Dscf::Credit
36
36
  # Find all eligible credit lines for the user through their approved loan profiles
37
37
  # Only include credit lines with available credit
38
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
- })
39
+ .joins(loan_profile: [ { loan_application: :user }, :reviews ])
40
+ .where(dscf_credit_loan_applications: { user_id: payment_request.user_id })
41
+ .where(dscf_core_reviews: { status: "approved" }) # Only approved loan profiles via reviews
44
42
  .where("dscf_credit_eligible_credit_lines.available_limit > 0") # Only with available credit
45
43
  .includes(:loan_profile, :credit_line)
46
44
  .order("dscf_credit_eligible_credit_lines.available_limit DESC") # Order by available limit
@@ -1,23 +1,40 @@
1
1
  module Dscf::Credit
2
2
  class ScoringParametersController < ApplicationController
3
3
  include Dscf::Core::Common
4
- include Dscf::Credit::Reviewable
4
+ include Dscf::Core::ReviewableController
5
5
 
6
6
  def create
7
7
  super do
8
8
  scoring_parameter = @clazz.new(model_params)
9
9
  scoring_parameter.created_by = current_user
10
- scoring_parameter.reviewed_by = current_user
10
+ scoring_parameter.reviews.build(context: "default", status: "pending")
11
11
 
12
- scoring_parameter
12
+ if validate_category_weight_limit(scoring_parameter)
13
+ scoring_parameter
14
+ else
15
+ scoring_parameter.errors.add(:weight, "would exceed the total weight limit of 1.0 for this category")
16
+ render_error(errors: scoring_parameter.errors.full_messages, status: :unprocessable_entity)
17
+ return
18
+ end
13
19
  end
14
20
  end
15
21
 
22
+ def update
23
+ unless validate_category_weight_limit(@obj, exclude_current: true)
24
+ @obj.errors.add(:weight, "would exceed the total weight limit of 1.0 for this category")
25
+ render_error(errors: @obj.errors.full_messages, status: :unprocessable_entity)
26
+ return
27
+ end
28
+
29
+ super
30
+ end
31
+
16
32
  private
17
33
 
18
34
  def model_params
19
35
  params.require(:scoring_parameter).permit(
20
36
  :bank_id,
37
+ :category_id,
21
38
  :name,
22
39
  :description,
23
40
  :data_type,
@@ -26,30 +43,59 @@ module Dscf::Credit
26
43
  :max_value,
27
44
  :active,
28
45
  :previous_version_id,
29
- :review_date,
30
46
  :source,
31
47
  :scoring_param_type_id,
32
- :document_reference,
33
- :status,
34
- :review_feedback
48
+ :document_reference
35
49
  )
36
50
  end
37
51
 
38
52
  def eager_loaded_associations
39
- [ :bank, :created_by, :reviewed_by, :scoring_param_type, :previous_version, :parameter_normalizers, :scoring_tables, :categories ]
53
+ [
54
+ :bank, :category, :created_by, :scoring_param_type, :previous_version, :parameter_normalizers,
55
+ reviews: { reviewed_by: :user_profile }
56
+ ]
40
57
  end
41
58
 
42
59
  def allowed_order_columns
43
- %w[id name data_type weight active review_date created_at updated_at]
60
+ %w[id name data_type weight active created_at updated_at]
44
61
  end
45
62
 
46
63
  def default_serializer_includes
47
64
  {
48
- index: [ :bank ],
49
- show: [ :bank, :created_by, :reviewed_by, :scoring_param_type, :previous_version, :parameter_normalizers, :scoring_tables, :categories ],
50
- create: [ :bank, :created_by, :reviewed_by, :scoring_param_type ],
51
- update: [ :bank, :created_by, :reviewed_by, :scoring_param_type, :parameter_normalizers, :scoring_tables, :categories ]
65
+ index: [ :bank, :category, reviews: { reviewed_by: :user_profile } ],
66
+ show: [
67
+ :bank, :category, :created_by, :scoring_param_type, :previous_version, :parameter_normalizers,
68
+ reviews: { reviewed_by: :user_profile }
69
+ ],
70
+ create: [ :bank, :category, :created_by, :scoring_param_type ],
71
+ update: [
72
+ :bank, :category, :created_by, :scoring_param_type, :parameter_normalizers,
73
+ reviews: { reviewed_by: :user_profile }
74
+ ]
52
75
  }
53
76
  end
77
+
78
+ def validate_category_weight_limit(scoring_parameter, exclude_current: false)
79
+ return true unless scoring_parameter.category_id && scoring_parameter.weight
80
+
81
+ # Get existing parameters in the same category for the same bank
82
+ existing_params = ScoringParameter.where(
83
+ bank_id: scoring_parameter.bank_id,
84
+ category_id: scoring_parameter.category_id,
85
+ active: true
86
+ )
87
+
88
+ # Exclude current parameter from the calculation when updating
89
+ if exclude_current && scoring_parameter.persisted?
90
+ existing_params = existing_params.where.not(id: scoring_parameter.id)
91
+ end
92
+
93
+ # Calculate total weight
94
+ current_total_weight = existing_params.sum(:weight)
95
+ new_total_weight = current_total_weight + scoring_parameter.weight.to_f
96
+
97
+ # Allow small floating point tolerance (e.g., 1.0001 should be acceptable)
98
+ new_total_weight <= 1.001
99
+ end
54
100
  end
55
101
  end
@@ -1,13 +1,14 @@
1
1
  module Dscf::Credit
2
2
  class SystemConfigsController < ApplicationController
3
3
  include Dscf::Core::Common
4
- include Dscf::Credit::Reviewable
4
+ include Dscf::Core::ReviewableController
5
5
 
6
6
  def create
7
7
  super do
8
8
  obj = @clazz.new(model_params)
9
- obj.reviewed_by = current_user
10
9
  obj.last_updated_by = current_user
10
+ obj.reviews.build(context: "default", status: "pending")
11
+
11
12
  obj
12
13
  end
13
14
  end
@@ -25,27 +26,44 @@ module Dscf::Credit
25
26
  def model_params
26
27
  params.require(:system_config).permit(
27
28
  :config_definition_id,
28
- :config_value,
29
- :status,
30
- :review_date,
31
- :review_feedback
29
+ :config_value
32
30
  )
33
31
  end
34
32
 
35
33
  def eager_loaded_associations
36
- [ :config_definition, :last_updated_by, :reviewed_by ]
34
+ [
35
+ :config_definition,
36
+ :last_updated_by,
37
+ reviews: { reviewed_by: :user_profile }
38
+ ]
37
39
  end
38
40
 
39
41
  def allowed_order_columns
40
- %w[id config_value status review_date created_at updated_at]
42
+ %w[id config_value created_at updated_at]
41
43
  end
42
44
 
43
45
  def default_serializer_includes
44
46
  {
45
- index: [ :config_definition ],
46
- show: [ :config_definition, :last_updated_by, :reviewed_by ],
47
- create: [ :config_definition, :last_updated_by, :reviewed_by ],
48
- update: [ :config_definition, :last_updated_by, :reviewed_by ]
47
+ index: [
48
+ :config_definition,
49
+ reviews: { reviewed_by: :user_profile }
50
+ ],
51
+ show: [
52
+ :config_definition,
53
+ :last_updated_by,
54
+ reviews: { reviewed_by: :user_profile }
55
+ ],
56
+ create: [
57
+ :config_definition,
58
+ :last_updated_by,
59
+ :reviews
60
+
61
+ ],
62
+ update: [
63
+ :config_definition,
64
+ :last_updated_by,
65
+ reviews: { reviewed_by: :user_profile }
66
+ ]
49
67
  }
50
68
  end
51
69
  end
@@ -0,0 +1,31 @@
1
+ module Dscf
2
+ module Core
3
+ module ReviewableModel
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_many :reviews, as: :reviewable, class_name: "Dscf::Core::Review", dependent: :destroy
8
+ end
9
+
10
+ def review_for(context = :default)
11
+ reviews.with_context(context).order(created_at: :desc)
12
+ end
13
+
14
+ def current_review_for(context = :default)
15
+ review_for(context).first
16
+ end
17
+
18
+ def current_status_for(context = :default)
19
+ current_review = current_review_for(context)
20
+ return current_review.status if current_review
21
+
22
+ # Return default initial status for zero-config
23
+ "pending"
24
+ end
25
+
26
+ def build_review_for(context = :default)
27
+ reviews.build(context: context.to_s)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -13,10 +13,10 @@ module Dscf::Credit
13
13
  foreign_key: "bank_id", dependent: :destroy
14
14
  has_many :scoring_parameters, class_name: "Dscf::Credit::ScoringParameter",
15
15
  foreign_key: "bank_id", dependent: :destroy
16
- has_many :loan_profiles, class_name: "Dscf::Credit::LoanProfile",
17
- foreign_key: "bank_id", dependent: :destroy
18
- has_many :facilitators, class_name: "Dscf::Credit::Facilitator",
16
+ has_many :loan_applications, class_name: "Dscf::Credit::LoanApplication",
19
17
  foreign_key: "bank_id", dependent: :destroy
18
+ has_many :facilitators, through: :facilitator_applications, class_name: "Dscf::Credit::Facilitator"
19
+ has_many :facilitator_applications, class_name: "Dscf::Credit::FacilitatorApplication"
20
20
  has_many :bank_staffs, through: :bank_branches, class_name: "Dscf::Credit::BankStaff"
21
21
 
22
22
  validates :name, presence: true