dscf-credit 0.2.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6af7d6aeb34ec3aa7d630f441bdfc54657b07640135bb862cc3d9b0eccd03c9
4
- data.tar.gz: 15b37f5d6cfc2462b31267c16e5d85e738326d1479861abedf127362bfd7d42f
3
+ metadata.gz: c03116320f239a1f9f953742c429298bf629e0731e1938dc1870107bd2e3a74f
4
+ data.tar.gz: da5c7c4a877ce2c82cd0300c25c9686f8ea1254bcfd9b82c856ecd99fb8ef5a7
5
5
  SHA512:
6
- metadata.gz: 7815d27fc10a061438a37b46a77506a83ef5fab2c08bf5f04fe4b517b52ed7354b0e7a08ba292803a95f79b991a13f1ff8ac966aeb121b2118c40ccfdaa0c493
7
- data.tar.gz: fa2684a66f074ab72be71ed458d717b4fd8e04d920440808ce0b3638b914fc2bf3bcc2fdc80881acb7596e1a3fbd0c70b4d0905b2674c677abbe2838eda713cf
6
+ metadata.gz: 0ae96ca00a9c2cef5bb46b59a58e0b4cf13263cb89903a19bbf8c1a03c709d2a75ac59e4ea394668c4a5f2b2ea298cbfd963c22eb1af810a5fb4c6ed9c266bb3
7
+ data.tar.gz: 879a870adbb92ac789ed00096d658e1c729107b94572c4ee66df10c31fcf123b549ceec50499b295cb16e94f446f0218ad7f0c6430fd5839c9c91f31e4ff19fb
@@ -2,6 +2,26 @@ module Dscf::Credit
2
2
  class LoanApplicationsController < ApplicationController
3
3
  include Dscf::Core::Common
4
4
  include Dscf::Core::ReviewableController
5
+ reviewable_context :default,
6
+ statuses: %w[pending approved rejected modify],
7
+ initial_status: "pending",
8
+ transitions: {
9
+ "pending" => %w[approved rejected modify],
10
+ "modify" => [ "pending" ]
11
+ },
12
+ actions: {
13
+ approve: {
14
+ status: "approved",
15
+ after: ->(review) {
16
+ loan_application = review.reviewable
17
+ score = loan_application.score
18
+ LoanApplicationsController.new.create_loan_profile_for_approved_application(loan_application, score)
19
+ }
20
+ },
21
+ reject: { status: "rejected", require_feedback: true },
22
+ request_modification: { status: "modify", require_feedback: true },
23
+ resubmit: { status: "pending", update_model: true }
24
+ }
5
25
 
6
26
  def create
7
27
  super do
@@ -187,6 +207,27 @@ module Dscf::Credit
187
207
  profile_result
188
208
  end
189
209
 
210
+ def create_loan_profile_from_review(review)
211
+ loan_application = review.reviewable # Assumes Review belongs_to :reviewable (polymorphic or direct association to LoanApplication)
212
+ score = loan_application.score # Assumes score is already set on the loan application
213
+
214
+ unless score
215
+ Rails.logger.warn "No score available for loan application #{loan_application.id} during approve callback"
216
+ return
217
+ end
218
+
219
+ profile_service = LoanProfileCreationService.new(loan_application, score)
220
+ profile_result = profile_service.create_loan_profile
221
+
222
+ unless profile_result[:success]
223
+ error_message = "Failed to create loan profile for approved application #{loan_application.id}: #{profile_result[:error]}"
224
+ Rails.logger.error error_message
225
+ raise StandardError, error_message # This will rollback the transaction in perform_review_action
226
+ else
227
+ Rails.logger.info "Loan profile created successfully for approved application #{loan_application.id} via review callback"
228
+ end
229
+ end
230
+
190
231
  def model_params
191
232
  params.require(:loan_application).permit(
192
233
  :bank_id,
@@ -107,41 +107,47 @@ module Dscf::Credit
107
107
  end
108
108
 
109
109
  def extract_parameter_value(parameter)
110
- param_type_name = parameter.scoring_param_type.name
111
110
  parameter_id = parameter.id.to_s
112
111
 
113
- # Get the appropriate JSON field based on parameter type
114
- json_field = get_json_field_for_param_type(param_type_name)
115
- return nil unless json_field
112
+ all_json_fields = {
113
+ user_info: loan_application.user_info || {},
114
+ bank_info: loan_application.bank_info || {},
115
+ facilitator_info: loan_application.facilitator_info || {},
116
+ field_assessment: loan_application.field_assessment || {}
117
+ }
118
+
119
+ param_data = nil
120
+ found_in_field = nil
121
+
122
+ all_json_fields.each do |field_name, field_data|
123
+ if field_data.key?(parameter_id)
124
+ param_data = field_data[parameter_id]
125
+ found_in_field = field_name
126
+ break # Stop at first match (IDs should be unique across fields)
127
+ end
128
+ end
129
+
130
+ if param_data.nil?
131
+ Rails.logger.warn "Parameter #{parameter_id} (#{parameter.name}) not found in any JSON field for loan_app #{loan_application.id}"
132
+ return nil
133
+ end
116
134
 
117
- # Extract value using parameter ID as key
118
- param_data = json_field[parameter_id]
119
- return nil unless param_data.is_a?(Hash)
135
+ unless param_data.is_a?(Hash)
136
+ Rails.logger.warn "Invalid param_data for #{parameter_id} in #{found_in_field}: expected Hash, got #{param_data.class}"
137
+ return nil
138
+ end
120
139
 
121
- # Validate that the param_name matches (optional safety check)
140
+ # Validate param_name matches (optional safety check)
122
141
  expected_param_name = param_data["param_name"]
123
142
  if expected_param_name && expected_param_name != parameter.name
124
- Rails.logger.warn "Parameter name mismatch for ID #{parameter_id}: expected '#{parameter.name}', got '#{expected_param_name}'"
143
+ Rails.logger.warn "Parameter name mismatch for ID #{parameter_id} in #{found_in_field}: expected '#{parameter.name}', got '#{expected_param_name}'"
125
144
  end
126
145
 
127
- param_data["value"]
146
+ raw_value = param_data["value"]
147
+ Rails.logger.debug "Extracted #{parameter.name} (ID #{parameter_id}) from #{found_in_field}: value=#{raw_value}"
148
+ raw_value
128
149
  end
129
150
 
130
- def get_json_field_for_param_type(param_type_name)
131
- case param_type_name.downcase
132
- when "user_info"
133
- loan_application.user_info || {}
134
- when "bank_info"
135
- loan_application.bank_info || {}
136
- when "facilitator_info"
137
- loan_application.facilitator_info || {}
138
- when "field_assessment"
139
- loan_application.field_assessment || {}
140
- else
141
- Rails.logger.warn "Unknown parameter type: #{param_type_name}"
142
- nil
143
- end
144
- end
145
151
 
146
152
  def normalize_parameter_value(parameter, raw_value)
147
153
  return 0.0 if raw_value.nil?
@@ -249,7 +255,7 @@ module Dscf::Credit
249
255
  when 0...50
250
256
  "rejected"
251
257
  when 50..60
252
- "pending_review"
258
+ "pending"
253
259
  else # score > 60
254
260
  "approved"
255
261
  end
data/db/seeds.rb CHANGED
@@ -568,99 +568,6 @@ puts "Seeding loan profile scoring specs..."
568
568
  # Note: This will be moved after loan profiles are created
569
569
 
570
570
  # 10.5. Loan Applications (depends on banks, users, and bank branches)
571
- puts "Seeding loan applications..."
572
- loan_application1 = Dscf::Credit::LoanApplication.find_or_create_by(
573
- bank: bunna_bank,
574
- user_id: user2.id,
575
- review_branch: bunna_head_office
576
- ) do |application|
577
- application.backer = user1
578
- application.bank_statement_source = "internal"
579
- application.user_info = {
580
- name: "Jane Smith",
581
- phone: "+251922334455",
582
- email: "jane.smith@example.com",
583
- address: "123 Main St, Addis Ababa",
584
- id_number: "ID123456789",
585
- date_of_birth: "1985-05-15"
586
- }
587
- application.facilitator_info = {
588
- business_name: "ABC Trading Company",
589
- license_number: "LIC001234",
590
- business_type: "retail",
591
- years_in_business: 5,
592
- monthly_turnover: 150000.00
593
- }
594
- application.bank_info = {
595
- account_number: "123456789012",
596
- account_holder: "Jane Smith",
597
- bank_name: "Bunna Bank",
598
- branch_name: "Head Office"
599
- }
600
- application.field_assessment = {
601
- credit_score: 720,
602
- risk_level: "medium",
603
- recommendation: "approved",
604
- assessed_by: "Field Officer A",
605
- assessment_date: Time.current.iso8601
606
- }
607
- end
608
-
609
- # Create approved review for loan_application1
610
- Dscf::Core::Review.find_or_create_by(
611
- reviewable: loan_application1,
612
- reviewed_by: admin_user
613
- ) do |review|
614
- review.status = 'approved'
615
- review.reviewed_at = Time.current
616
- end
617
-
618
- loan_application2 = Dscf::Credit::LoanApplication.find_or_create_by(
619
- bank: bunna_bank,
620
- user_id: user3.id,
621
- review_branch: bunna_merkato_branch
622
- ) do |application|
623
- application.backer = user1
624
- application.bank_statement_source = "external"
625
- application.user_info = {
626
- name: "Mike Johnson",
627
- phone: "+251933445566",
628
- email: "mike.johnson@example.com",
629
- address: "456 Oak Ave, Addis Ababa",
630
- id_number: "ID987654321",
631
- date_of_birth: "1980-12-10"
632
- }
633
- application.facilitator_info = {
634
- business_name: "XYZ Wholesale",
635
- license_number: "LIC005678",
636
- business_type: "wholesale",
637
- years_in_business: 8,
638
- monthly_turnover: 500000.00
639
- }
640
- application.bank_info = {
641
- account_number: "987654321098",
642
- account_holder: "Mike Johnson",
643
- bank_name: "Commercial Bank",
644
- branch_name: "Main Branch"
645
- }
646
- application.field_assessment = {
647
- credit_score: 650,
648
- risk_level: "high",
649
- recommendation: "conditional_approval",
650
- assessed_by: "Field Officer B",
651
- assessment_date: Time.current.iso8601
652
- }
653
- application.score = 65.0
654
- end
655
-
656
- # Create pending review for loan_application2
657
- Dscf::Core::Review.find_or_create_by(
658
- reviewable: loan_application2,
659
- reviewed_by: bank_admin
660
- ) do |review|
661
- review.status = 'pending'
662
- review.reviewed_at = Time.current
663
- end
664
571
 
665
572
  # 10.6. Facilitator Applications (depends on users and banks)
666
573
  puts "Seeding facilitator applications..."
@@ -750,119 +657,6 @@ Dscf::Core::Review.find_or_create_by(
750
657
  end
751
658
 
752
659
  # 11. Loan Profiles (depends on loan applications)
753
- puts "Seeding loan profiles..."
754
- loan_profile1 = Dscf::Credit::LoanProfile.find_or_create_by(
755
- loan_application: loan_application1
756
- ) do |profile|
757
- profile.code = "BB000001"
758
- profile.score = 78.5
759
- profile.total_limit = 100000.00
760
- end
761
-
762
- # Create approved review for loan_profile1
763
- Dscf::Core::Review.find_or_create_by(
764
- reviewable: loan_profile1,
765
- reviewed_by: admin_user
766
- ) do |review|
767
- review.status = 'approved'
768
- review.reviewed_at = Time.current
769
- end
770
-
771
- # 12. Loan Profile Scoring Specs (depends on loan profiles)
772
- puts "Seeding loan profile scoring specs..."
773
- Dscf::Credit::LoanProfileScoringSpec.create!(
774
- loan_profile: loan_profile1,
775
- scoring_input_data: {
776
- 'monthly_income' => 45000,
777
- 'credit_history_score' => 720,
778
- 'employment_type' => 'permanent',
779
- 'years_at_job' => 3,
780
- 'existing_debt' => 15000,
781
- 'average_daily_purchase' => 15000 # Wholesaler's average daily purchase volume
782
- },
783
- score: 78.5,
784
- total_limit: 100000.00,
785
- active: true,
786
- created_by: admin_user
787
- )
788
-
789
- # 11. Eligible Credit Lines (depends on loan profiles and credit lines)
790
- puts "Seeding eligible credit lines..."
791
- Dscf::Credit::EligibleCreditLine.create!(
792
- loan_profile: loan_profile1,
793
- credit_line: credit_line1,
794
- credit_limit: 75000.00,
795
- available_limit: 67500.00,
796
- risk: 0.35
797
- )
798
-
799
- Dscf::Credit::EligibleCreditLine.create!(
800
- loan_profile: loan_profile1,
801
- credit_line: credit_line2,
802
- credit_limit: 50000.00,
803
- available_limit: 42500.00,
804
- risk: 0.45
805
- )
806
-
807
- # 12. Loans (depends on loan profiles and credit lines)
808
- puts "Seeding loans..."
809
- loan1 = Dscf::Credit::Loan.create!(
810
- loan_profile: loan_profile1,
811
- credit_line: credit_line1,
812
- status: 'active',
813
- principal_amount: 50000.00,
814
- remaining_amount: 50000.00,
815
- due_date: 3.months.from_now.to_date,
816
- disbursed_at: 1.day.ago,
817
- active: true
818
- )
819
-
820
- # 12.5. Loan Accruals (depends on loans)
821
- puts "Seeding loan accruals..."
822
- Dscf::Credit::LoanAccrual.create!(
823
- loan: loan1,
824
- accrual_type: 'interest',
825
- amount: 250.00,
826
- applied_on: 1.month.ago.to_date,
827
- status: 'pending'
828
- )
829
-
830
- Dscf::Credit::LoanAccrual.create!(
831
- loan: loan1,
832
- accrual_type: 'facilitation_fee',
833
- amount: 1000.00,
834
- applied_on: 1.day.ago.to_date,
835
- status: 'paid'
836
- )
837
-
838
- # 13. Loan Transactions (depends on loans)
839
- puts "Seeding loan transactions..."
840
- Dscf::Credit::LoanTransaction.create!(
841
- loan: loan1,
842
- transaction_type: 'disbursement',
843
- amount: 50000.00,
844
- transaction_reference: "TXN-DISB-#{Time.current.to_i}",
845
- status: 'completed'
846
- )
847
-
848
- Dscf::Credit::LoanTransaction.create!(
849
- loan: loan1,
850
- transaction_type: 'interest_accrual',
851
- amount: 2500.00,
852
- transaction_reference: "TXN-INT-#{Time.current.to_i}",
853
- status: 'completed'
854
- )
855
-
856
- # 14. Daily Routine Transactions (depends on loans)
857
- puts "Seeding daily routine transactions..."
858
- Dscf::Credit::DailyRoutineTransaction.create!(
859
- loan: loan1,
860
- routine_type: 'interest_calculation',
861
- amount: 83.33,
862
- status: 'completed',
863
- initiated_at: Date.current.beginning_of_day,
864
- approved_at: Date.current.beginning_of_day + 1.hour
865
- )
866
660
 
867
661
  puts "DSCF Credit Engine seed data completed successfully!"
868
662
  puts "Created sample data for all models with proper relationships."
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Credit
3
- VERSION = "0.2.1"
3
+ VERSION = "0.2.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-credit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adoniyas
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-10-06 00:00:00.000000000 Z
10
+ date: 2025-10-07 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dscf-core