dscf-credit 0.1.5 → 0.1.6

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: 6914417c9552b795ef7f98f17f2ffbd9854c52230e2a7dcc972e2f62d48e43f1
4
- data.tar.gz: 27aa925e0fa5dbe419c9df8f54d77c0a03b94101e81c9a61ce3daeddac1bd0a6
3
+ metadata.gz: 447633e68d0087ea77917d1630137c52d138096a52039af3c67c577ce3c5edd9
4
+ data.tar.gz: 5f4b38640b2adf043787cca20d5138b3e40185333f04bb9d747203bd14abf575
5
5
  SHA512:
6
- metadata.gz: c7f0f12531ba3b30ec55c527f0091bd49bbb4606218a6ddbe03d45793c16a10d22e02258f6a3ea821fde1812c386fafe3f98b64590c9c074960c4cc22c0d4883
7
- data.tar.gz: ac5a855d4322ba647a1c9439b32600eaf736253dc6397ed5eb333339e9bd901550f73fe326c4a24e87a1fd77b70cdde9558fd41bb146c7bdd779602703c18e41
6
+ metadata.gz: 02ad7c0149c073efb33bbc55e318d6cee9f6096aa29af50ef7316f3caf80ee7396c5cd3fdc7387287e88152e5af9b5854b90620b436fc6487c8c241ec8c1a98a
7
+ data.tar.gz: '099dd81a6c2f8f788fb38fb6c4f2d8b7f636a0853fbbed972b1078e0da5ef8e6cfe4c7b7fdec5d4bd1d5965e68f93cc8861a3122b20c1f670ebea186dba7c67b'
@@ -1,10 +1,15 @@
1
1
  module Dscf::Credit
2
2
  class DisbursementsController < ApplicationController
3
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])
4
+ amount = disbursement_params[:amount]
5
+ loan_profile = Dscf::Credit::LoanProfile.find(disbursement_params[:loan_profile_id])
6
+ eligible_credit_line = Dscf::Credit::EligibleCreditLine.find(disbursement_params[:eligible_credit_line_id])
6
7
 
7
- service = Dscf::Credit::DisbursementService.new(credit_line, payment_request, current_user)
8
+ service = Dscf::Credit::DisbursementService.new(
9
+ amount: amount,
10
+ loan_profile: loan_profile,
11
+ eligible_credit_line: eligible_credit_line
12
+ )
8
13
  result = service.process_disbursement
9
14
 
10
15
  if result[:success]
@@ -12,17 +17,7 @@ module Dscf::Credit
12
17
  "disbursement.success.create",
13
18
  data: {
14
19
  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
- ]
20
+ disbursement_details: result[:disbursement_details]
26
21
  }
27
22
  )
28
23
  else
@@ -48,8 +43,12 @@ module Dscf::Credit
48
43
 
49
44
  private
50
45
 
51
- def model_params
52
- params.permit(:credit_line_id, :payment_request_id)
46
+ def disbursement_params
47
+ params.require(:disbursement).permit(
48
+ :amount,
49
+ :loan_profile_id,
50
+ :eligible_credit_line_id
51
+ )
53
52
  end
54
53
  end
55
54
  end
@@ -8,7 +8,6 @@ module Dscf::Credit
8
8
  params.require(:loan).permit(
9
9
  :loan_profile_id,
10
10
  :credit_line_id,
11
- :payment_request_id,
12
11
  :status,
13
12
  :principal_amount,
14
13
  :accrued_interest,
@@ -17,12 +16,13 @@ module Dscf::Credit
17
16
  :total_loan_amount,
18
17
  :remaining_amount,
19
18
  :due_date,
20
- :disbursed_at
19
+ :disbursed_at,
20
+ :active
21
21
  )
22
22
  end
23
23
 
24
24
  def eager_loaded_associations
25
- [ :loan_profile, :credit_line, :payment_request, :loan_transactions ]
25
+ [ :loan_profile, :credit_line, :loan_transactions ]
26
26
  end
27
27
 
28
28
  def allowed_order_columns
@@ -31,10 +31,10 @@ module Dscf::Credit
31
31
 
32
32
  def default_serializer_includes
33
33
  {
34
- index: [ :loan_profile, :credit_line, :payment_request ],
35
- show: [ :loan_profile, :credit_line, :payment_request, :loan_transactions ],
36
- create: [ :loan_profile, :credit_line, :payment_request ],
37
- update: [ :loan_profile, :credit_line, :payment_request, :loan_transactions ]
34
+ index: [ :loan_profile, :credit_line ],
35
+ show: [ :loan_profile, :credit_line, :loan_transactions ],
36
+ create: [ :loan_profile, :credit_line ],
37
+ update: [ :loan_profile, :credit_line, :loan_transactions ]
38
38
  }
39
39
  end
40
40
  end
@@ -4,7 +4,6 @@ module Dscf::Credit
4
4
 
5
5
  belongs_to :loan_profile, class_name: "Dscf::Credit::LoanProfile", foreign_key: "loan_profile_id"
6
6
  belongs_to :credit_line, class_name: "Dscf::Credit::CreditLine", foreign_key: "credit_line_id"
7
- belongs_to :payment_request, class_name: "Dscf::Credit::PaymentRequest", foreign_key: "payment_request_id"
8
7
  has_many :loan_transactions, class_name: "Dscf::Credit::LoanTransaction", foreign_key: "loan_id", dependent: :destroy
9
8
  has_many :daily_routine_transactions, class_name: "Dscf::Credit::DailyRoutineTransaction", foreign_key: "loan_id", dependent: :destroy
10
9
 
@@ -13,18 +12,19 @@ module Dscf::Credit
13
12
  validates :accrued_interest, :accrued_penalty, :facilitation_fee, numericality: { greater_than_or_equal_to: 0 }
14
13
  validates :status, inclusion: { in: %w[pending approved disbursed active overdue paid closed] }
15
14
 
16
- scope :active, -> { where(status: "active") }
15
+ scope :active, -> { where(active: true) }
16
+ scope :inactive, -> { where(active: false) }
17
17
  scope :overdue, -> { where(status: "overdue") }
18
18
  scope :paid, -> { where(status: "paid") }
19
19
  scope :due_today, -> { where(due_date: Date.current) }
20
20
  scope :past_due, -> { where("due_date < ? AND status IN (?)", Date.current, [ "active", "overdue" ]) }
21
21
 
22
22
  def self.ransackable_attributes(auth_object = nil)
23
- %w[id status principal_amount accrued_interest accrued_penalty facilitation_fee total_loan_amount remaining_amount due_date disbursed_at created_at updated_at]
23
+ %w[id status principal_amount accrued_interest accrued_penalty facilitation_fee total_loan_amount remaining_amount due_date disbursed_at active created_at updated_at]
24
24
  end
25
25
 
26
26
  def self.ransackable_associations(auth_object = nil)
27
- %w[loan_profile credit_line payment_request loan_transactions daily_routine_transactions]
27
+ %w[loan_profile credit_line loan_transactions daily_routine_transactions]
28
28
  end
29
29
 
30
30
  delegate :user, to: :loan_profile, allow_nil: true
@@ -1,11 +1,10 @@
1
1
  module Dscf::Credit
2
2
  class LoanSerializer < ActiveModel::Serializer
3
3
  attributes :id, :status, :principal_amount, :accrued_interest, :accrued_penalty, :facilitation_fee,
4
- :total_loan_amount, :remaining_amount, :due_date, :disbursed_at, :created_at, :updated_at
4
+ :total_loan_amount, :remaining_amount, :due_date, :disbursed_at, :active, :created_at, :updated_at
5
5
 
6
6
  belongs_to :loan_profile, serializer: LoanProfileSerializer
7
7
  belongs_to :credit_line, serializer: CreditLineSerializer
8
- belongs_to :payment_request, serializer: PaymentRequestSerializer
9
8
  has_many :loan_transactions, serializer: LoanTransactionSerializer
10
9
  has_many :daily_routine_transactions, serializer: DailyRoutineTransactionSerializer
11
10
  end
@@ -1,34 +1,35 @@
1
1
  module Dscf::Credit
2
2
  class DisbursementService
3
- attr_reader :credit_line, :payment_request, :current_user
3
+ attr_reader :amount, :loan_profile, :eligible_credit_line
4
4
 
5
- def initialize(credit_line, payment_request, current_user)
6
- @credit_line = credit_line
7
- @payment_request = payment_request
8
- @current_user = current_user
5
+ def initialize(amount:, loan_profile:, eligible_credit_line:)
6
+ @amount = amount.to_f
7
+ @loan_profile = loan_profile
8
+ @eligible_credit_line = eligible_credit_line
9
9
  end
10
10
 
11
- # Process disbursement by creating a loan record from selected credit line
11
+ # Process disbursement by creating a loan record from eligible credit line
12
12
  # @return [Hash] Result containing created loan and disbursement details
13
13
  def process_disbursement
14
+ return error_result("Loan profile not found") unless loan_profile
15
+ return error_result("Eligible credit line not found") unless eligible_credit_line
16
+ return error_result("Invalid amount") unless amount > 0
17
+
18
+ credit_line = eligible_credit_line.credit_line
14
19
  return error_result("Credit line not found") unless credit_line
15
- return error_result("Payment request not found") unless payment_request
16
- return error_result("Credit line is not approved") unless credit_line.status == "approved"
17
20
 
18
- eligible_credit_line = find_eligible_credit_line
19
- return error_result("No eligible credit line found") unless eligible_credit_line
21
+ credit_line_status = credit_line.current_status_for(:default)
22
+ return error_result("Credit line is not approved") unless credit_line_status == "approved"
20
23
 
21
- loan_profile = eligible_credit_line.loan_profile
22
- return error_result("Loan profile not approved") unless loan_profile.status == "approved"
24
+ loan_profile_status = loan_profile.current_status_for(:default)
25
+ return error_result("Loan profile not approved") unless loan_profile_status == "approved"
23
26
 
24
- validation_result = validate_disbursement_amount(eligible_credit_line)
27
+ validation_result = validate_disbursement_amount
25
28
  return validation_result unless validation_result[:success]
26
29
 
27
30
  ActiveRecord::Base.transaction do
28
- loan = create_loan_record(loan_profile, eligible_credit_line)
29
- process_payment_and_charges(loan)
30
- update_credit_line_limits(eligible_credit_line, loan)
31
- lock_other_credit_lines(loan_profile, credit_line)
31
+ loan = create_loan_record(credit_line)
32
+ update_credit_line_limits(loan)
32
33
 
33
34
  success_result(loan)
34
35
  end
@@ -38,36 +39,27 @@ module Dscf::Credit
38
39
 
39
40
  private
40
41
 
41
- def find_eligible_credit_line
42
- credit_line.eligible_credit_lines
43
- .joins(:loan_profile)
44
- .where(dscf_credit_loan_profiles: { status: "approved" })
45
- .first
46
- end
47
-
48
- def validate_disbursement_amount(eligible_credit_line)
49
- requested_amount = payment_request.amount
42
+ def validate_disbursement_amount
50
43
  available_limit = eligible_credit_line.available_limit
51
44
 
52
- if requested_amount <= 0
45
+ if amount <= 0
53
46
  return error_result("Invalid disbursement amount")
54
47
  end
55
48
 
56
- if requested_amount > available_limit
49
+ if amount > available_limit
57
50
  return error_result("Requested amount exceeds available credit limit")
58
51
  end
59
52
 
60
53
  { success: true }
61
54
  end
62
55
 
63
- def create_loan_record(loan_profile, eligible_credit_line)
64
- loan_terms = calculate_loan_terms(eligible_credit_line)
56
+ def create_loan_record(credit_line)
57
+ loan_terms = calculate_loan_terms(credit_line)
65
58
 
66
59
  loan = Dscf::Credit::Loan.new(
67
60
  loan_profile: loan_profile,
68
61
  credit_line: credit_line,
69
- payment_request: payment_request,
70
- principal_amount: payment_request.amount,
62
+ principal_amount: amount,
71
63
  facilitation_fee: loan_terms[:facilitation_fee],
72
64
  total_loan_amount: loan_terms[:total_amount],
73
65
  remaining_amount: loan_terms[:total_amount],
@@ -75,7 +67,8 @@ module Dscf::Credit
75
67
  accrued_penalty: 0,
76
68
  status: "disbursed",
77
69
  due_date: loan_terms[:due_date],
78
- disbursed_at: Time.current
70
+ disbursed_at: Time.current,
71
+ active: true
79
72
  )
80
73
 
81
74
  raise "Failed to create loan: #{loan.errors.full_messages.join(', ')}" unless loan.save
@@ -83,21 +76,20 @@ module Dscf::Credit
83
76
  loan
84
77
  end
85
78
 
86
- def calculate_loan_terms(eligible_credit_line)
87
- principal = payment_request.amount
79
+ def calculate_loan_terms(credit_line)
80
+ principal = amount
88
81
 
89
82
  credit_line_spec = credit_line.credit_line_specs.active.first
90
83
 
91
- if credit_line_spec
92
- facilitation_rate = credit_line_spec.facilitation_fee_rate
93
- vat_rate = credit_line_spec.vat
94
- loan_duration = credit_line_spec.loan_duration
95
- else
96
- facilitation_rate = 0.02 # 2%
97
- vat_rate = 0.15 # 15%
98
- loan_duration = 30 # 30 days
84
+ unless credit_line_spec
85
+ raise "No active credit line specification found for credit line #{credit_line.id}. " \
86
+ "Please configure a CreditLineSpec before processing disbursements."
99
87
  end
100
88
 
89
+ facilitation_rate = credit_line_spec.facilitation_fee_rate
90
+ vat_rate = credit_line_spec.vat
91
+ loan_duration = credit_line_spec.loan_duration
92
+
101
93
  facilitation_fee = principal * facilitation_rate
102
94
  vat_amount = facilitation_fee * vat_rate
103
95
  total_amount = principal + facilitation_fee + vat_amount
@@ -112,46 +104,9 @@ module Dscf::Credit
112
104
  }
113
105
  end
114
106
 
115
- def process_payment_and_charges(loan)
116
- payment_request.update!(
117
- status: "processed",
118
- approved_at: Time.current
119
- )
120
-
121
- Dscf::Credit::LoanTransaction.create!(
122
- loan: loan,
123
- transaction_type: "disbursement",
124
- amount: loan.principal_amount,
125
- transaction_reference: "DISB-#{payment_request.order_id}-#{Time.current.to_i}",
126
- status: "completed"
127
- )
128
-
129
- if loan.facilitation_fee > 0
130
- Dscf::Credit::LoanTransaction.create!(
131
- loan: loan,
132
- transaction_type: "fee_charge",
133
- amount: loan.facilitation_fee,
134
- transaction_reference: "FEE-#{loan.id}-#{Time.current.to_i}",
135
- status: "completed"
136
- )
137
- end
138
- end
139
-
140
- def update_credit_line_limits(eligible_credit_line, loan)
107
+ def update_credit_line_limits(loan)
141
108
  new_available_limit = eligible_credit_line.available_limit - loan.total_loan_amount
142
109
  eligible_credit_line.update!(available_limit: [ new_available_limit, 0 ].max)
143
-
144
- # Update loan profile available amount
145
- loan_profile = eligible_credit_line.loan_profile
146
- new_profile_available = loan_profile.available_amount - loan.total_loan_amount
147
- loan_profile.update!(available_amount: [ new_profile_available, 0 ].max)
148
- end
149
-
150
- def lock_other_credit_lines(loan_profile, current_credit_line)
151
- other_eligible_lines = loan_profile.eligible_credit_lines
152
- .where.not(credit_line: current_credit_line)
153
-
154
- other_eligible_lines.update_all(available_limit: 0)
155
110
  end
156
111
 
157
112
  def success_result(loan)
@@ -159,9 +114,9 @@ module Dscf::Credit
159
114
  success: true,
160
115
  loan: loan,
161
116
  disbursement_details: {
162
- principal_amount: loan.principal_amount,
163
- facilitation_fee: loan.facilitation_fee,
164
- total_loan_amount: loan.total_loan_amount,
117
+ principal_amount: loan.principal_amount.to_f,
118
+ facilitation_fee: loan.facilitation_fee.to_f,
119
+ total_loan_amount: loan.total_loan_amount.to_f,
165
120
  due_date: loan.due_date,
166
121
  disbursed_at: loan.disbursed_at
167
122
  },
@@ -328,10 +328,10 @@ en:
328
328
 
329
329
  disbursement:
330
330
  success:
331
- create: "Disbursement processed successfully"
331
+ create: "Disbursement processed successfully. Loan created and credit limit updated."
332
332
  errors:
333
333
  create: "Failed to process disbursement"
334
- not_found: "Credit line or payment request not found"
334
+ not_found: "Loan profile or eligible credit line not found"
335
335
 
336
336
  repayment:
337
337
  success:
data/config/routes.rb CHANGED
@@ -18,7 +18,6 @@ Dscf::Credit::Engine.routes.draw do
18
18
  member do
19
19
  patch "approve"
20
20
  patch "reject"
21
- post "calculate_score"
22
21
  post "calculate_facility_limits"
23
22
  end
24
23
  end
@@ -78,7 +77,6 @@ Dscf::Credit::Engine.routes.draw do
78
77
  patch "update_facilitator_info"
79
78
  patch "update_field_assessment"
80
79
  post "calculate_credit_score"
81
- patch "apply_risk_factor"
82
80
  end
83
81
  end
84
82
 
@@ -3,7 +3,6 @@ class CreateDscfCreditLoans < ActiveRecord::Migration[8.0]
3
3
  create_table :dscf_credit_loans do |t|
4
4
  t.references :loan_profile, null: false, foreign_key: { to_table: :dscf_credit_loan_profiles }
5
5
  t.references :credit_line, null: false, foreign_key: { to_table: :dscf_credit_credit_lines }
6
- t.references :payment_request, null: false, foreign_key: { to_table: :dscf_credit_payment_requests }
7
6
  t.string :status, default: 'pending'
8
7
  t.decimal :principal_amount, precision: 15, scale: 2, null: false
9
8
  t.decimal :accrued_interest, precision: 15, scale: 2, default: 0
@@ -13,6 +12,7 @@ class CreateDscfCreditLoans < ActiveRecord::Migration[8.0]
13
12
  t.decimal :remaining_amount, precision: 15, scale: 2, null: false
14
13
  t.date :due_date, null: false
15
14
  t.datetime :disbursed_at
15
+ t.boolean :active, default: true, null: false
16
16
 
17
17
  t.timestamps
18
18
  end
@@ -22,8 +22,10 @@ class CreateDscfCreditLoans < ActiveRecord::Migration[8.0]
22
22
  add_index :dscf_credit_loans, :disbursed_at
23
23
  add_index :dscf_credit_loans, :principal_amount
24
24
  add_index :dscf_credit_loans, :remaining_amount
25
+ add_index :dscf_credit_loans, :active
25
26
  add_index :dscf_credit_loans, [ :loan_profile_id, :status ]
26
27
  add_index :dscf_credit_loans, [ :credit_line_id, :status ]
27
28
  add_index :dscf_credit_loans, [ :status, :due_date ]
29
+ add_index :dscf_credit_loans, [ :active, :status ]
28
30
  end
29
31
  end
data/db/seeds.rb CHANGED
@@ -804,35 +804,11 @@ Dscf::Credit::EligibleCreditLine.create!(
804
804
  risk: 0.45
805
805
  )
806
806
 
807
- # 12. Payment Requests (depends on users)
808
- puts "Seeding payment requests..."
809
- payment_request1 = Dscf::Credit::PaymentRequest.create!(
810
- user: user2,
811
- order_id: "ORD-#{Time.current.to_i}-001",
812
- request_type: 'loan_disbursement',
813
- amount: 50000.00,
814
- receiver_account_reference: 'ACC-BORROWER-001',
815
- status: 'approved',
816
- initiated_at: 2.days.ago,
817
- approved_at: 1.day.ago
818
- )
819
-
820
- payment_request2 = Dscf::Credit::PaymentRequest.create!(
821
- user: user2,
822
- order_id: "ORD-#{Time.current.to_i}-002",
823
- request_type: 'loan_repayment',
824
- amount: 15000.00,
825
- receiver_account_reference: 'ACC-BANK-001',
826
- status: 'pending',
827
- initiated_at: 1.hour.ago
828
- )
829
-
830
- # 13. Loans (depends on loan profiles, credit lines, and payment requests)
807
+ # 12. Loans (depends on loan profiles and credit lines)
831
808
  puts "Seeding loans..."
832
809
  loan1 = Dscf::Credit::Loan.create!(
833
810
  loan_profile: loan_profile1,
834
811
  credit_line: credit_line1,
835
- payment_request: payment_request1,
836
812
  status: 'active',
837
813
  principal_amount: 50000.00,
838
814
  accrued_interest: 2500.00,
@@ -841,10 +817,11 @@ loan1 = Dscf::Credit::Loan.create!(
841
817
  total_loan_amount: 53500.00,
842
818
  remaining_amount: 53500.00,
843
819
  due_date: 3.months.from_now.to_date,
844
- disbursed_at: 1.day.ago
820
+ disbursed_at: 1.day.ago,
821
+ active: true
845
822
  )
846
823
 
847
- # 14. Loan Transactions (depends on loans)
824
+ # 13. Loan Transactions (depends on loans)
848
825
  puts "Seeding loan transactions..."
849
826
  Dscf::Credit::LoanTransaction.create!(
850
827
  loan: loan1,
@@ -862,7 +839,7 @@ Dscf::Credit::LoanTransaction.create!(
862
839
  status: 'completed'
863
840
  )
864
841
 
865
- # 15. Daily Routine Transactions (depends on loans)
842
+ # 14. Daily Routine Transactions (depends on loans)
866
843
  puts "Seeding daily routine transactions..."
867
844
  Dscf::Credit::DailyRoutineTransaction.create!(
868
845
  loan: loan1,
@@ -873,63 +850,6 @@ Dscf::Credit::DailyRoutineTransaction.create!(
873
850
  approved_at: Date.current.beginning_of_day + 1.hour
874
851
  )
875
852
 
876
- # 16. Payments (depends on payment requests)
877
- puts "Seeding payments..."
878
- Dscf::Credit::Payment.create!(
879
- payment_request: payment_request1,
880
- amount: 50000.00,
881
- receiver_account_reference: 'ACC-BORROWER-001',
882
- transaction_reference: "PAY-#{Time.current.to_i}-001",
883
- status: 'completed',
884
- processed_at: 1.day.ago
885
- )
886
-
887
- # 17. Accounting Audit Requests (depends on various entities)
888
- puts "Seeding accounting audit requests..."
889
- audit_request1 = Dscf::Credit::AccountingAuditRequest.create!(
890
- source_entity: loan1,
891
- transaction_type: 'debit',
892
- amount: 50000.00,
893
- webhook_key: "WEBHOOK-#{SecureRandom.hex(16)}",
894
- status: 'completed'
895
- )
896
-
897
- Dscf::Credit::AccountingAuditRequest.create!(
898
- source_entity: payment_request1,
899
- transaction_type: 'transfer',
900
- amount: 50000.00,
901
- webhook_key: "WEBHOOK-#{SecureRandom.hex(16)}",
902
- status: 'pending'
903
- )
904
-
905
- # 18. Accounting Entries (depends on audit requests)
906
- puts "Seeding accounting entries..."
907
- Dscf::Credit::AccountingEntry.create!(
908
- audit_request: audit_request1,
909
- account_code: 'LOAN_ASSET_001',
910
- entry_type: 'debit',
911
- amount: 50000.00,
912
- status: 'completed'
913
- )
914
-
915
- Dscf::Credit::AccountingEntry.create!(
916
- audit_request: audit_request1,
917
- account_code: 'CASH_001',
918
- entry_type: 'credit',
919
- amount: 50000.00,
920
- status: 'completed'
921
- )
922
-
923
- # 19. Failed Operations Logs (depends on various entities)
924
- puts "Seeding failed operations logs..."
925
- Dscf::Credit::FailedOperationsLog.create!(
926
- entity: payment_request2,
927
- failure_type: 'payment_processing',
928
- reason: 'Payment gateway did not respond within timeout period',
929
- retry_count: 2,
930
- resolved: false
931
- )
932
-
933
853
  puts "DSCF Credit Engine seed data completed successfully!"
934
854
  puts "Created sample data for all models with proper relationships."
935
855
  puts "Summary:"
@@ -943,8 +863,6 @@ puts "- Categories: #{Dscf::Credit::Category.count}"
943
863
  puts "- Credit Lines: #{Dscf::Credit::CreditLine.count}"
944
864
  puts "- Loan Profiles: #{Dscf::Credit::LoanProfile.count}"
945
865
  puts "- Loans: #{Dscf::Credit::Loan.count}"
946
- puts "- Payment Requests: #{Dscf::Credit::PaymentRequest.count}"
947
- puts "- Payments: #{Dscf::Credit::Payment.count}"
948
866
  puts "- Facilitators: #{Dscf::Credit::Facilitator.count} (will be created via API)"
949
867
  puts "- System Configs: #{Dscf::Credit::SystemConfig.count}"
950
868
  puts "- Scoring Parameters: #{Dscf::Credit::ScoringParameter.count}"
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Credit
3
- VERSION = "0.1.5"
3
+ VERSION = "0.1.6"
4
4
  end
5
5
  end
@@ -2,7 +2,6 @@ FactoryBot.define do
2
2
  factory :loan, class: "Dscf::Credit::Loan" do
3
3
  association :loan_profile, factory: :loan_profile
4
4
  association :credit_line, factory: :credit_line
5
- association :payment_request, factory: :payment_request
6
5
  status { %w[pending approved disbursed active overdue paid closed].sample }
7
6
  principal_amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
8
7
  accrued_interest { Faker::Number.decimal(l_digits: 3, r_digits: 2) }
@@ -12,6 +11,7 @@ FactoryBot.define do
12
11
  remaining_amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
13
12
  due_date { Faker::Date.between(from: 1.month.from_now, to: 6.months.from_now) }
14
13
  disbursed_at { Faker::Time.between(from: 2.months.ago, to: Time.current) if status.in?(%w[disbursed active overdue paid closed]) }
14
+ active { true }
15
15
 
16
16
  trait :pending do
17
17
  status { "pending" }
@@ -33,5 +33,9 @@ FactoryBot.define do
33
33
  status { "paid" }
34
34
  remaining_amount { 0 }
35
35
  end
36
+
37
+ trait :inactive do
38
+ active { false }
39
+ end
36
40
  end
37
41
  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.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adoniyas
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-09-29 00:00:00.000000000 Z
10
+ date: 2025-10-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dscf-core
@@ -337,8 +337,6 @@ files:
337
337
  - app/controllers/dscf/credit/loan_profiles_controller.rb
338
338
  - app/controllers/dscf/credit/loans_controller.rb
339
339
  - app/controllers/dscf/credit/parameter_normalizers_controller.rb
340
- - app/controllers/dscf/credit/payment_requests_controller.rb
341
- - app/controllers/dscf/credit/payments_controller.rb
342
340
  - app/controllers/dscf/credit/repayments_controller.rb
343
341
  - app/controllers/dscf/credit/scoring_param_types_controller.rb
344
342
  - app/controllers/dscf/credit/scoring_parameters_controller.rb
@@ -371,8 +369,6 @@ files:
371
369
  - app/models/dscf/credit/loan_profile_scoring_spec.rb
372
370
  - app/models/dscf/credit/loan_transaction.rb
373
371
  - app/models/dscf/credit/parameter_normalizer.rb
374
- - app/models/dscf/credit/payment.rb
375
- - app/models/dscf/credit/payment_request.rb
376
372
  - app/models/dscf/credit/scoring_param_type.rb
377
373
  - app/models/dscf/credit/scoring_parameter.rb
378
374
  - app/models/dscf/credit/system_config.rb
@@ -400,8 +396,6 @@ files:
400
396
  - app/serializers/dscf/credit/loan_serializer.rb
401
397
  - app/serializers/dscf/credit/loan_transaction_serializer.rb
402
398
  - app/serializers/dscf/credit/parameter_normalizer_serializer.rb
403
- - app/serializers/dscf/credit/payment_request_serializer.rb
404
- - app/serializers/dscf/credit/payment_serializer.rb
405
399
  - app/serializers/dscf/credit/scoring_param_type_serializer.rb
406
400
  - app/serializers/dscf/credit/scoring_parameter_serializer.rb
407
401
  - app/serializers/dscf/credit/system_config_definition_serializer.rb
@@ -448,12 +442,10 @@ files:
448
442
  - db/migrate/20250822092426_create_dscf_credit_facilitator_applications.rb
449
443
  - db/migrate/20250822092436_create_dscf_credit_facilitators.rb
450
444
  - db/migrate/20250822092528_create_dscf_credit_facilitator_performances.rb
451
- - db/migrate/20250822092608_create_dscf_credit_payment_requests.rb
452
445
  - db/migrate/20250822092654_create_dscf_credit_loans.rb
453
446
  - db/migrate/20250822092717_create_dscf_credit_loan_transactions.rb
454
447
  - db/migrate/20250822092738_create_dscf_credit_daily_routine_transactions.rb
455
448
  - db/migrate/20250822092819_create_dscf_credit_accounting_audit_requests.rb
456
- - db/migrate/20250822092843_create_dscf_credit_payments.rb
457
449
  - db/migrate/20250822092908_create_dscf_credit_failed_operations_logs.rb
458
450
  - db/migrate/20250822092936_create_dscf_credit_accounting_entries.rb
459
451
  - db/migrate/20250825231109_create_dscf_credit_bank_staff.rb
@@ -483,8 +475,6 @@ files:
483
475
  - spec/factories/dscf/credit/loan_transactions.rb
484
476
  - spec/factories/dscf/credit/loans.rb
485
477
  - spec/factories/dscf/credit/parameter_normalizers.rb
486
- - spec/factories/dscf/credit/payment_requests.rb
487
- - spec/factories/dscf/credit/payments.rb
488
478
  - spec/factories/dscf/credit/scoring_param_types.rb
489
479
  - spec/factories/dscf/credit/scoring_parameters.rb
490
480
  - spec/factories/dscf/credit/system_config_definitions.rb
@@ -1,85 +0,0 @@
1
- module Dscf::Credit
2
- class PaymentRequestsController < ApplicationController
3
- include Dscf::Core::Common
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: [ { 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
42
- .where("dscf_credit_eligible_credit_lines.available_limit > 0") # Only with available credit
43
- .includes(:loan_profile, :credit_line)
44
- .order("dscf_credit_eligible_credit_lines.available_limit DESC") # Order by available limit
45
-
46
- render_success(
47
- data: eligible_credit_lines,
48
- )
49
- rescue ActiveRecord::RecordNotFound
50
- render_error(error: "Payment request not found", status: :not_found)
51
- rescue StandardError => e
52
- render_error(error: e.message)
53
- end
54
-
55
- private
56
-
57
- def model_params
58
- params.require(:payment_request).permit(
59
- :user_id,
60
- :order_id,
61
- :amount,
62
- :receiver_account_reference,
63
- :status,
64
- :failure_reason
65
- )
66
- end
67
-
68
- def eager_loaded_associations
69
- [ :user, :loans, :payments ]
70
- end
71
-
72
- def allowed_order_columns
73
- %w[id order_id request_type amount status initiated_at approved_at created_at updated_at]
74
- end
75
-
76
- def default_serializer_includes
77
- {
78
- index: [ :user ],
79
- show: [ :user, :loans, :payments ],
80
- create: [ :user ],
81
- update: [ :user, :loans, :payments ]
82
- }
83
- end
84
- end
85
- end
@@ -1,36 +0,0 @@
1
- module Dscf::Credit
2
- class PaymentsController < ApplicationController
3
- include Dscf::Core::Common
4
-
5
- private
6
-
7
- def model_params
8
- params.require(:payment).permit(
9
- :payment_request_id,
10
- :amount,
11
- :receiver_account_reference,
12
- :transaction_reference,
13
- :status,
14
- :failure_reason,
15
- :processed_at
16
- )
17
- end
18
-
19
- def eager_loaded_associations
20
- [ :payment_request ]
21
- end
22
-
23
- def allowed_order_columns
24
- %w[id amount status processed_at created_at updated_at]
25
- end
26
-
27
- def default_serializer_includes
28
- {
29
- index: [ :payment_request ],
30
- show: [ :payment_request ],
31
- create: [ :payment_request ],
32
- update: [ :payment_request ]
33
- }
34
- end
35
- end
36
- end
@@ -1,22 +0,0 @@
1
- module Dscf::Credit
2
- class Payment < ApplicationRecord
3
- self.table_name = "dscf_credit_payments"
4
-
5
- belongs_to :payment_request, class_name: "Dscf::Credit::PaymentRequest", foreign_key: "payment_request_id"
6
-
7
- validates :amount, :receiver_account_reference, :transaction_reference, :status, presence: true
8
- validates :amount, numericality: { greater_than: 0 }
9
- validates :status, inclusion: { in: %w[pending processing completed failed cancelled] }
10
- validates :transaction_reference, uniqueness: true
11
-
12
- scope :by_status, ->(status) { where(status: status) }
13
-
14
- def self.ransackable_attributes(auth_object = nil)
15
- %w[id amount receiver_account_reference transaction_reference status failure_reason processed_at created_at updated_at]
16
- end
17
-
18
- def self.ransackable_associations(auth_object = nil)
19
- %w[payment_request]
20
- end
21
- end
22
- end
@@ -1,29 +0,0 @@
1
- module Dscf::Credit
2
- class PaymentRequest < ApplicationRecord
3
- self.table_name = "dscf_credit_payment_requests"
4
-
5
- if defined?(Dscf::Core::User)
6
- belongs_to :user, class_name: "Dscf::Core::User", foreign_key: "user_id"
7
- end
8
- has_many :loans, class_name: "Dscf::Credit::Loan", foreign_key: "payment_request_id", dependent: :destroy
9
- has_many :payments, class_name: "Dscf::Credit::Payment", foreign_key: "payment_request_id", dependent: :destroy
10
-
11
- validates :order_id, presence: true, uniqueness: true
12
- validates :request_type, :amount, :receiver_account_reference, presence: true
13
- validates :amount, numericality: { greater_than: 0 }
14
- validates :status, inclusion: { in: %w[pending approved rejected processed failed] }
15
-
16
- scope :pending, -> { where(status: "pending") }
17
- scope :approved, -> { where(status: "approved") }
18
- scope :processed, -> { where(status: "processed") }
19
- scope :failed, -> { where(status: "failed") }
20
-
21
- def self.ransackable_attributes(auth_object = nil)
22
- %w[id order_id request_type amount receiver_account_reference status failure_reason initiated_at approved_at created_at updated_at]
23
- end
24
-
25
- def self.ransackable_associations(auth_object = nil)
26
- %w[user loans payments]
27
- end
28
- end
29
- end
@@ -1,10 +0,0 @@
1
- module Dscf::Credit
2
- class PaymentRequestSerializer < ActiveModel::Serializer
3
- attributes :id, :order_id, :request_type, :amount, :receiver_account_reference,
4
- :status, :failure_reason, :initiated_at, :approved_at, :created_at, :updated_at
5
-
6
- belongs_to :user, serializer: Dscf::Core::UserSerializer
7
- has_many :loans, serializer: Dscf::Credit::LoanSerializer
8
- has_many :payments, serializer: Dscf::Credit::PaymentSerializer
9
- end
10
- end
@@ -1,8 +0,0 @@
1
- module Dscf::Credit
2
- class PaymentSerializer < ActiveModel::Serializer
3
- attributes :id, :amount, :receiver_account_reference, :transaction_reference,
4
- :status, :failure_reason, :processed_at, :created_at, :updated_at
5
-
6
- belongs_to :payment_request, serializer: PaymentRequestSerializer
7
- end
8
- end
@@ -1,26 +0,0 @@
1
- class CreateDscfCreditPaymentRequests < ActiveRecord::Migration[8.0]
2
- def change
3
- create_table :dscf_credit_payment_requests do |t|
4
- t.references :user, null: false, foreign_key: { to_table: :dscf_core_users }
5
- t.string :order_id, null: false
6
- t.string :request_type, null: false
7
- t.decimal :amount, precision: 15, scale: 2, null: false
8
- t.string :receiver_account_reference, null: false
9
- t.string :status, default: 'pending'
10
- t.text :failure_reason
11
- t.datetime :initiated_at
12
- t.datetime :approved_at
13
-
14
- t.timestamps
15
- end
16
-
17
- add_index :dscf_credit_payment_requests, :order_id, unique: true
18
- add_index :dscf_credit_payment_requests, :request_type
19
- add_index :dscf_credit_payment_requests, :status
20
- add_index :dscf_credit_payment_requests, :receiver_account_reference
21
- add_index :dscf_credit_payment_requests, :initiated_at
22
- add_index :dscf_credit_payment_requests, :approved_at
23
- add_index :dscf_credit_payment_requests, [ :user_id, :status ]
24
- add_index :dscf_credit_payment_requests, [ :user_id, :request_type ]
25
- end
26
- end
@@ -1,23 +0,0 @@
1
- class CreateDscfCreditPayments < ActiveRecord::Migration[8.0]
2
- def change
3
- create_table :dscf_credit_payments do |t|
4
- t.references :payment_request, null: false, foreign_key: { to_table: :dscf_credit_payment_requests }
5
- t.decimal :amount, precision: 15, scale: 2, null: false
6
- t.string :receiver_account_reference, null: false
7
- t.string :transaction_reference, null: false
8
- t.string :status, default: 'pending'
9
- t.text :failure_reason
10
- t.datetime :processed_at
11
-
12
- t.timestamps
13
- end
14
-
15
- add_index :dscf_credit_payments, :transaction_reference, unique: true
16
- add_index :dscf_credit_payments, :receiver_account_reference
17
- add_index :dscf_credit_payments, :status
18
- add_index :dscf_credit_payments, :amount
19
- add_index :dscf_credit_payments, :processed_at
20
- add_index :dscf_credit_payments, [ :payment_request_id, :status ]
21
- add_index :dscf_credit_payments, :created_at
22
- end
23
- end
@@ -1,40 +0,0 @@
1
- FactoryBot.define do
2
- factory :payment_request, class: "Dscf::Credit::PaymentRequest" do
3
- association :user, factory: :user if defined?(Dscf::Core::User)
4
- order_id { Faker::Alphanumeric.alphanumeric(number: 16).upcase }
5
- request_type { %w[loan_disbursement loan_repayment interest_payment fee_payment].sample }
6
- amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
7
- receiver_account_reference { Faker::Bank.account_number }
8
- status { %w[pending approved rejected processed failed].sample }
9
- failure_reason { Faker::Lorem.sentence if status.in?(%w[rejected failed]) }
10
- initiated_at { Faker::Time.between(from: 1.month.ago, to: Time.current) }
11
- approved_at { initiated_at + rand(1..24).hours if status.in?(%w[approved processed]) }
12
-
13
- trait :pending do
14
- status { "pending" }
15
- approved_at { nil }
16
- failure_reason { nil }
17
- end
18
-
19
- trait :approved do
20
- status { "approved" }
21
- approved_at { initiated_at + rand(1..24).hours }
22
- failure_reason { nil }
23
- end
24
-
25
- trait :rejected do
26
- status { "rejected" }
27
- failure_reason { Faker::Lorem.sentence }
28
- end
29
-
30
- trait :processed do
31
- status { "processed" }
32
- approved_at { initiated_at + rand(1..24).hours }
33
- end
34
-
35
- trait :failed do
36
- status { "failed" }
37
- failure_reason { Faker::Lorem.sentence }
38
- end
39
- end
40
- end
@@ -1,39 +0,0 @@
1
- FactoryBot.define do
2
- factory :payment, class: "Dscf::Credit::Payment" do
3
- association :payment_request, factory: :payment_request
4
- amount { Faker::Number.decimal(l_digits: 5, r_digits: 2) }
5
- receiver_account_reference { Faker::Alphanumeric.alphanumeric(number: 20) }
6
- transaction_reference { Faker::Alphanumeric.alphanumeric(number: 16) }
7
- status { %w[pending processing completed failed cancelled].sample }
8
- failure_reason { Faker::Lorem.sentence if status.in?(%w[failed cancelled]) }
9
- processed_at { Faker::Time.between(from: 1.month.ago, to: Time.current) if status.in?(%w[completed failed]) }
10
-
11
- trait :pending do
12
- status { "pending" }
13
- processed_at { nil }
14
- failure_reason { nil }
15
- end
16
-
17
- trait :processing do
18
- status { "processing" }
19
- processed_at { nil }
20
- end
21
-
22
- trait :completed do
23
- status { "completed" }
24
- processed_at { Faker::Time.between(from: 1.month.ago, to: Time.current) }
25
- failure_reason { nil }
26
- end
27
-
28
- trait :failed do
29
- status { "failed" }
30
- processed_at { Faker::Time.between(from: 1.month.ago, to: Time.current) }
31
- failure_reason { Faker::Lorem.sentence }
32
- end
33
-
34
- trait :cancelled do
35
- status { "cancelled" }
36
- failure_reason { Faker::Lorem.sentence }
37
- end
38
- end
39
- end