dscf-credit 0.3.7 → 0.3.9

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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/dscf/credit/credit_lines_controller.rb +20 -6
  3. data/app/controllers/dscf/credit/facilitator_applications_controller.rb +19 -3
  4. data/app/controllers/dscf/credit/facilitators_controller.rb +18 -0
  5. data/app/controllers/dscf/credit/loan_applications_controller.rb +25 -5
  6. data/app/controllers/dscf/credit/loan_profiles_controller.rb +11 -1
  7. data/app/controllers/dscf/credit/loan_transactions_controller.rb +34 -0
  8. data/app/controllers/dscf/credit/loans_controller.rb +1 -1
  9. data/app/controllers/dscf/credit/scoring_parameters_controller.rb +17 -3
  10. data/app/controllers/dscf/credit/system_configs_controller.rb +8 -1
  11. data/app/models/dscf/credit/credit_line.rb +1 -0
  12. data/app/models/dscf/credit/facilitator_application.rb +2 -1
  13. data/app/models/dscf/credit/loan_application.rb +2 -1
  14. data/app/models/dscf/credit/loan_transaction.rb +11 -1
  15. data/app/models/dscf/credit/scoring_parameter.rb +2 -1
  16. data/app/serializers/dscf/credit/credit_line_serializer.rb +1 -0
  17. data/app/serializers/dscf/credit/facilitator_application_serializer.rb +1 -0
  18. data/app/serializers/dscf/credit/loan_application_serializer.rb +1 -0
  19. data/app/serializers/dscf/credit/scoring_parameter_serializer.rb +1 -0
  20. data/app/services/dscf/credit/disbursement_service.rb +15 -3
  21. data/app/services/dscf/credit/loan_transaction_creator_service.rb +71 -0
  22. data/app/services/dscf/credit/repayment_service.rb +21 -3
  23. data/config/locales/en.yml +26 -1
  24. data/config/routes.rb +10 -0
  25. data/db/migrate/20250822091527_create_dscf_credit_credit_line_specs.rb +3 -1
  26. data/lib/dscf/credit/version.rb +1 -1
  27. metadata +8 -9
  28. data/db/migrate/20251011202425_add_base_scoring_parameter_to_dscf_credit_credit_line_specs.rb +0 -5
  29. data/db/migrate/20251012100039_add_credit_line_divider_to_dscf_credit_credit_line_specs.rb +0 -5
  30. data/db/migrate/20251012115159_remove_default_from_credit_line_multiplier_in_credit_line_specs.rb +0 -5
  31. /data/db/migrate/{20250822092040_create_dscf_credit_scoring_param_types.rb → 20250822091525_create_dscf_credit_scoring_param_types.rb} +0 -0
  32. /data/db/migrate/{20250822092050_create_dscf_credit_scoring_parameters.rb → 20250822091526_create_dscf_credit_scoring_parameters.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1443779ba67dff25e894c91b282acabdd8e6872728ced4a0dd9066a935da2c12
4
- data.tar.gz: 141228623b227b54ac146125203dde6bb4adcb40a1c3e39e81062b5e64f50aa9
3
+ metadata.gz: 0755d5b8fef09d0ef9bb85ab34185c0a528b889b61ad3304a8023a14d2c4d1de
4
+ data.tar.gz: 2c423be84c80ecb384fd156c2b55aad8dac759f4a4d963894a7e95a79d071e7d
5
5
  SHA512:
6
- metadata.gz: 5991cb4fb11b1adbd0ca101d7ed8a27cf6f3f91f634f56aeb800477d1f275c586916d47bf5c5a0a70f657f331d68fdd1c5a77564aa6047103d71d5468eb31ce8
7
- data.tar.gz: 60d7fec2c04ceb1f5485f4b8cbcf39375219be4ae6e5c79a21ea204203eb6ade319a1709dd90eacc62ec362baf8ed25bb376341aae929c2cadab8de04a4e217b
6
+ metadata.gz: ad7f84d0d0953170499c1bb4a772f1ebfe0a7e1eea63fc0dc9bb5c5a5ea70479fea83a78ebc692f2abe3103d14c011bc7a66f732e4f90f1b6849eb326e07d78f
7
+ data.tar.gz: ac88a42b8d1278b0d7c269f045c18f256be65a529f63bd58fae632484b3cba7a1282aa95ed8d28892ae9bde24370a2115eff98f4f60ba14516b5fc758e7c5600
@@ -2,19 +2,31 @@ module Dscf::Credit
2
2
  class CreditLinesController < ApplicationController
3
3
  include Dscf::Core::Common
4
4
  include Dscf::Core::ReviewableController
5
+ include Dscf::Core::AuditableController
6
+
7
+ auditable associated: :reviews,
8
+ only: [ :status ],
9
+ on: %i[approve reject request_modification resubmit]
5
10
 
6
11
  def create
7
12
  super do
8
13
  credit_line = @clazz.new(model_params)
9
14
  credit_line.created_by = current_user
10
- credit_line.reviews.build(
11
- status: "pending",
12
- context: "default",
13
- )
15
+ credit_line.reviews.build(status: "draft", context: "default")
14
16
  credit_line
15
17
  end
16
18
  end
17
19
 
20
+ def update
21
+ unless @obj.editable?
22
+ return render_error(
23
+ errors: [ "Cannot update credit line after submission. Use modification request workflow instead." ],
24
+ status: :unprocessable_entity
25
+ )
26
+ end
27
+ super
28
+ end
29
+
18
30
  private
19
31
 
20
32
  def model_params
@@ -32,7 +44,8 @@ module Dscf::Credit
32
44
  [
33
45
  :bank, :category, :created_by, :loans, :eligible_credit_lines,
34
46
  credit_line_specs: :base_scoring_parameter,
35
- reviews: { reviewed_by: :user_profile }
47
+ reviews: { reviewed_by: :user_profile },
48
+ audit_logs: :actor
36
49
  ]
37
50
  end
38
51
 
@@ -46,7 +59,8 @@ module Dscf::Credit
46
59
  show: [
47
60
  :bank, :category, :created_by, :loans, :eligible_credit_lines,
48
61
  credit_line_specs: :base_scoring_parameter,
49
- reviews: { reviewed_by: :user_profile }
62
+ reviews: { reviewed_by: :user_profile },
63
+ audit_logs: :actor
50
64
  ],
51
65
  create: [ :bank, :category, :created_by, :reviews ],
52
66
  update: [
@@ -2,6 +2,11 @@ module Dscf::Credit
2
2
  class FacilitatorApplicationsController < ApplicationController
3
3
  include Dscf::Core::Common
4
4
  include Dscf::Core::ReviewableController
5
+ include Dscf::Core::AuditableController
6
+
7
+ auditable associated: [ :reviews ],
8
+ only: [ :status ],
9
+ on: %i[approve reject request_modification resubmit]
5
10
 
6
11
  def create
7
12
  super do
@@ -9,12 +14,22 @@ module Dscf::Credit
9
14
 
10
15
  user = Dscf::Core::User.find(model_params[:user_id])
11
16
  facilitator_application.user = user
12
- facilitator_application.reviews.build(context: "default", status: "pending")
17
+ facilitator_application.reviews.build(context: "default", status: "draft")
13
18
 
14
19
  facilitator_application
15
20
  end
16
21
  end
17
22
 
23
+ def update
24
+ unless @obj.editable?
25
+ return render_error(
26
+ errors: [ "Cannot update facilitator application after submission. Use modification request workflow instead." ],
27
+ status: :unprocessable_entity
28
+ )
29
+ end
30
+ super
31
+ end
32
+
18
33
  def bulk_create
19
34
  if params[:facilitator_applications].blank?
20
35
  return render_error(
@@ -102,7 +117,7 @@ module Dscf::Credit
102
117
  end
103
118
 
104
119
  def eager_loaded_associations
105
- [ :user, :bank, { user: { businesses: :business_type } }, reviews: { reviewed_by: :user_profile } ]
120
+ [ :user, :bank, { user: { businesses: :business_type } }, reviews: { reviewed_by: :user_profile }, audit_logs: :actor ]
106
121
  end
107
122
 
108
123
  def allowed_order_columns
@@ -111,7 +126,8 @@ module Dscf::Credit
111
126
 
112
127
  def default_serializer_includes
113
128
  {
114
- default: [ :user, :bank, { user: { businesses: :business_type } }, reviews: { reviewed_by: :user_profile } ]
129
+ default: [ :user, :bank, { user: { businesses: :business_type } }, reviews: { reviewed_by: :user_profile } ],
130
+ show: [ :user, :bank, { user: { businesses: :business_type } }, reviews: { reviewed_by: :user_profile }, audit_logs: :actor ]
115
131
  }
116
132
  end
117
133
  end
@@ -3,6 +3,24 @@ module Dscf::Credit
3
3
  include Dscf::Core::Common
4
4
  include Dscf::Core::ReviewableController
5
5
 
6
+ def create
7
+ super do
8
+ facilitator = @clazz.new(model_params)
9
+ facilitator.reviews.build(status: "draft", context: "default")
10
+ facilitator
11
+ end
12
+ end
13
+
14
+ def update
15
+ unless @obj.editable?
16
+ return render_error(
17
+ errors: [ "Cannot update facilitator after submission. Use modification request workflow instead." ],
18
+ status: :unprocessable_entity
19
+ )
20
+ end
21
+ super
22
+ end
23
+
6
24
  private
7
25
 
8
26
  def model_params
@@ -2,14 +2,22 @@ module Dscf::Credit
2
2
  class LoanApplicationsController < ApplicationController
3
3
  include Dscf::Core::Common
4
4
  include Dscf::Core::ReviewableController
5
+ include Dscf::Core::AuditableController
6
+
7
+ auditable associated: [ :reviews ],
8
+ only: [ :status ],
9
+ on: %i[approve reject request_modification resubmit]
10
+
5
11
  reviewable_context :default,
6
- statuses: %w[pending approved rejected modify],
7
- initial_status: "pending",
12
+ statuses: %w[draft pending approved rejected modify],
13
+ initial_status: "draft",
8
14
  transitions: {
15
+ "draft" => [ "pending" ],
9
16
  "pending" => %w[approved rejected modify],
10
17
  "modify" => [ "pending" ]
11
18
  },
12
19
  actions: {
20
+ submit: { status: "pending" },
13
21
  approve: {
14
22
  status: "approved",
15
23
  after: ->(review) {
@@ -27,12 +35,22 @@ module Dscf::Credit
27
35
  super do
28
36
  loan_application = @clazz.new(model_params)
29
37
  loan_application.user = current_user
30
- loan_application.reviews.build(context: "default", status: "pending")
38
+ loan_application.reviews.build(context: "default", status: "draft")
31
39
 
32
40
  loan_application
33
41
  end
34
42
  end
35
43
 
44
+ def update
45
+ unless @obj.editable?
46
+ return render_error(
47
+ errors: [ "Cannot update loan application after submission. Use modification request workflow instead." ],
48
+ status: :unprocessable_entity
49
+ )
50
+ end
51
+ super
52
+ end
53
+
36
54
  def update_bank_info
37
55
  loan_application = @clazz.find(params[:id])
38
56
  if loan_application.update(bank_info: bank_info_params)
@@ -264,7 +282,8 @@ module Dscf::Credit
264
282
  def eager_loaded_associations
265
283
  [
266
284
  :bank, :user, :backer, :review_branch, :loan_profile,
267
- reviews: { reviewed_by: :user_profile }
285
+ reviews: { reviewed_by: :user_profile },
286
+ audit_logs: :actor
268
287
  ]
269
288
  end
270
289
 
@@ -280,7 +299,8 @@ module Dscf::Credit
280
299
  ],
281
300
  show: [
282
301
  :bank, :user, :backer, :review_branch, :loan_profile,
283
- reviews: { reviewed_by: :user_profile }
302
+ reviews: { reviewed_by: :user_profile },
303
+ audit_logs: :actor
284
304
  ],
285
305
  create: [
286
306
  :bank, :user, :backer, :review_branch, :loan_profile, :reviews
@@ -7,13 +7,23 @@ module Dscf::Credit
7
7
  super do
8
8
  loan_profile = @clazz.new(model_params)
9
9
  loan_profile.reviews.build(
10
- status: "pending",
10
+ status: "draft",
11
11
  context: "default",
12
12
  )
13
13
  loan_profile
14
14
  end
15
15
  end
16
16
 
17
+ def update
18
+ unless @obj.editable?
19
+ return render_error(
20
+ errors: [ "Cannot update loan profile after submission. Use modification request workflow instead." ],
21
+ status: :unprocessable_entity
22
+ )
23
+ end
24
+ super
25
+ end
26
+
17
27
 
18
28
  def calculate_facility_limits
19
29
  loan_profile = @clazz.find(params[:id])
@@ -0,0 +1,34 @@
1
+ module Dscf::Credit
2
+ class LoanTransactionsController < ApplicationController
3
+ include Dscf::Core::Common
4
+
5
+ private
6
+
7
+ def model_params
8
+ params.require(:loan_transaction).permit(
9
+ :loan_id,
10
+ :transaction_type,
11
+ :amount,
12
+ :transaction_reference,
13
+ :status
14
+ )
15
+ end
16
+
17
+ def eager_loaded_associations
18
+ [ :loan ]
19
+ end
20
+
21
+ def allowed_order_columns
22
+ %w[id transaction_type amount transaction_reference status created_at updated_at]
23
+ end
24
+
25
+ def default_serializer_includes
26
+ {
27
+ index: [ :loan ],
28
+ show: [ :loan ],
29
+ create: [ :loan ],
30
+ update: [ :loan ]
31
+ }
32
+ end
33
+ end
34
+ end
@@ -28,7 +28,7 @@ module Dscf::Credit
28
28
  def default_serializer_includes
29
29
  {
30
30
  index: [ :loan_profile, :credit_line ],
31
- show: [ :loan_profile, :credit_line, :loan_accruals ],
31
+ show: [ :loan_profile, :credit_line, :loan_accruals, :loan_transactions ],
32
32
  create: [ :loan_profile, :credit_line ],
33
33
  update: [ :loan_profile, :credit_line ]
34
34
  }
@@ -2,12 +2,17 @@ module Dscf::Credit
2
2
  class ScoringParametersController < ApplicationController
3
3
  include Dscf::Core::Common
4
4
  include Dscf::Core::ReviewableController
5
+ include Dscf::Core::AuditableController
6
+
7
+ auditable associated: [ :reviews ],
8
+ only: [ :status ],
9
+ on: %i[approve reject request_modification resubmit]
5
10
 
6
11
  def create
7
12
  super do
8
13
  scoring_parameter = @clazz.new(model_params)
9
14
  scoring_parameter.created_by = current_user
10
- scoring_parameter.reviews.build(context: "default", status: "pending")
15
+ scoring_parameter.reviews.build(context: "default", status: "draft")
11
16
 
12
17
  if validate_scoring_param_type_weight_limit(scoring_parameter)
13
18
  scoring_parameter
@@ -20,6 +25,13 @@ module Dscf::Credit
20
25
  end
21
26
 
22
27
  def update
28
+ unless @obj.editable?
29
+ return render_error(
30
+ errors: [ "Cannot update scoring parameter after submission. Use modification request workflow instead." ],
31
+ status: :unprocessable_entity
32
+ )
33
+ end
34
+
23
35
  unless validate_scoring_param_type_weight_limit(@obj, exclude_current: true)
24
36
  @obj.errors.add(:weight, "would exceed the total weight limit of 1.0 for this scoring parameter type")
25
37
  render_error(errors: @obj.errors.full_messages, status: :unprocessable_entity)
@@ -52,7 +64,8 @@ module Dscf::Credit
52
64
  def eager_loaded_associations
53
65
  [
54
66
  :bank, :category, :created_by, :scoring_param_type, :previous_version, :parameter_normalizers,
55
- reviews: { reviewed_by: :user_profile }
67
+ reviews: { reviewed_by: :user_profile },
68
+ audit_logs: :actor
56
69
  ]
57
70
  end
58
71
 
@@ -65,7 +78,8 @@ module Dscf::Credit
65
78
  index: [ :bank, :category, :parameter_normalizers, :scoring_param_type, reviews: { reviewed_by: :user_profile } ],
66
79
  show: [
67
80
  :bank, :category, :created_by, :scoring_param_type, :previous_version, :parameter_normalizers,
68
- reviews: { reviewed_by: :user_profile }
81
+ reviews: { reviewed_by: :user_profile },
82
+ audit_logs: :actor
69
83
  ],
70
84
  create: [ :bank, :category, :created_by, :scoring_param_type ],
71
85
  update: [
@@ -7,13 +7,20 @@ module Dscf::Credit
7
7
  super do
8
8
  obj = @clazz.new(model_params)
9
9
  obj.last_updated_by = current_user
10
- obj.reviews.build(context: "default", status: "pending")
10
+ obj.reviews.build(context: "default", status: "draft")
11
11
 
12
12
  obj
13
13
  end
14
14
  end
15
15
 
16
16
  def update
17
+ unless @obj.editable?
18
+ return render_error(
19
+ errors: [ "Cannot update system config after submission. Use modification request workflow instead." ],
20
+ status: :unprocessable_entity
21
+ )
22
+ end
23
+
17
24
  super do
18
25
  obj = set_object
19
26
  obj.last_updated_by = current_user
@@ -3,6 +3,7 @@ module Dscf::Credit
3
3
  self.table_name = "dscf_credit_credit_lines"
4
4
 
5
5
  include Dscf::Core::ReviewableModel
6
+ include Dscf::Core::AuditableModel
6
7
 
7
8
  belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
8
9
  belongs_to :category, class_name: "Dscf::Credit::Category", foreign_key: "category_id"
@@ -3,6 +3,7 @@ module Dscf::Credit
3
3
  self.table_name = "dscf_credit_facilitator_applications"
4
4
 
5
5
  include Dscf::Core::ReviewableModel
6
+ include Dscf::Core::AuditableModel
6
7
 
7
8
  belongs_to :user, class_name: "Dscf::Core::User", foreign_key: "user_id"
8
9
  belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
@@ -13,7 +14,7 @@ module Dscf::Credit
13
14
  end
14
15
 
15
16
  def self.ransackable_associations(auth_object = nil)
16
- %w[user bank reviews]
17
+ %w[user bank reviews audit_logs]
17
18
  end
18
19
  end
19
20
  end
@@ -3,6 +3,7 @@ module Dscf::Credit
3
3
  self.table_name = "dscf_credit_loan_applications"
4
4
 
5
5
  include Dscf::Core::ReviewableModel
6
+ include Dscf::Core::AuditableModel
6
7
 
7
8
  belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
8
9
  if defined?(Dscf::Core::User)
@@ -24,7 +25,7 @@ module Dscf::Credit
24
25
  end
25
26
 
26
27
  def self.ransackable_associations(auth_object = nil)
27
- %w[bank user backer review_branch loan_profile reviews]
28
+ %w[bank user backer review_branch loan_profile reviews audit_logs]
28
29
  end
29
30
  end
30
31
  end
@@ -6,12 +6,22 @@ module Dscf::Credit
6
6
 
7
7
  validates :transaction_type, :amount, :transaction_reference, :status, presence: true
8
8
  validates :amount, numericality: { greater_than: 0 }
9
- validates :transaction_type, inclusion: { in: %w[disbursement repayment interest_accrual penalty fee_charge] }
9
+ validates :transaction_type, inclusion: { in: %w[disbursement repayment interest_accrual penalty fee_charge reversal adjustment] }
10
10
  validates :status, inclusion: { in: %w[pending processing completed failed reversed] }
11
11
  validates :transaction_reference, uniqueness: true
12
12
 
13
13
  scope :by_status, ->(status) { where(status: status) }
14
+ scope :completed, -> { where(status: "completed") }
15
+ scope :pending, -> { where(status: "pending") }
14
16
  scope :disbursements, -> { where(transaction_type: "disbursement") }
15
17
  scope :repayments, -> { where(transaction_type: "repayment") }
18
+
19
+ def self.ransackable_attributes(auth_object = nil)
20
+ %w[id transaction_type amount transaction_reference status created_at updated_at]
21
+ end
22
+
23
+ def self.ransackable_associations(auth_object = nil)
24
+ %w[loan]
25
+ end
16
26
  end
17
27
  end
@@ -3,6 +3,7 @@ module Dscf::Credit
3
3
  self.table_name = "dscf_credit_scoring_parameters"
4
4
 
5
5
  include Dscf::Core::ReviewableModel
6
+ include Dscf::Core::AuditableModel
6
7
 
7
8
  belongs_to :bank, class_name: "Dscf::Credit::Bank", foreign_key: "bank_id"
8
9
  belongs_to :category, class_name: "Dscf::Credit::Category", foreign_key: "category_id"
@@ -23,7 +24,7 @@ module Dscf::Credit
23
24
  end
24
25
 
25
26
  def self.ransackable_associations(auth_object = nil)
26
- %w[bank created_by scoring_param_type previous_version parameter_normalizers categories reviews]
27
+ %w[bank created_by scoring_param_type previous_version parameter_normalizers categories reviews audit_logs]
27
28
  end
28
29
  end
29
30
  end
@@ -10,5 +10,6 @@ module Dscf::Credit
10
10
  has_many :loans, serializer: Dscf::Credit::LoanSerializer
11
11
  has_many :eligible_credit_lines, serializer: Dscf::Credit::EligibleCreditLineSerializer
12
12
  has_many :reviews, serializer: Dscf::Core::ReviewSerializer
13
+ has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
13
14
  end
14
15
  end
@@ -4,4 +4,5 @@ class Dscf::Credit::FacilitatorApplicationSerializer < ActiveModel::Serializer
4
4
  belongs_to :user, serializer: Dscf::Core::UserSerializer
5
5
  belongs_to :bank, serializer: Dscf::Credit::BankSerializer
6
6
  has_many :reviews, serializer: Dscf::Core::ReviewSerializer
7
+ has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
7
8
  end
@@ -8,5 +8,6 @@ module Dscf::Credit
8
8
  belongs_to :backer, polymorphic: true
9
9
  belongs_to :review_branch, serializer: Dscf::Credit::BankBranchSerializer
10
10
  has_many :reviews, serializer: Dscf::Core::ReviewSerializer
11
+ has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
11
12
  end
12
13
  end
@@ -10,5 +10,6 @@ module Dscf::Credit
10
10
  belongs_to :previous_version, serializer: Dscf::Credit::ScoringParameterSerializer
11
11
  has_many :parameter_normalizers, serializer: Dscf::Credit::ParameterNormalizerSerializer
12
12
  has_many :reviews, serializer: Dscf::Core::ReviewSerializer
13
+ has_many :audit_logs, serializer: Dscf::Core::AuditLogSerializer
13
14
  end
14
15
  end
@@ -31,8 +31,9 @@ module Dscf::Credit
31
31
  loan = create_loan_record(credit_line)
32
32
  update_credit_line_limits(loan)
33
33
  lock_other_credit_lines(loan_profile, credit_line)
34
+ loan_transaction = create_disbursement_transaction(loan)
34
35
 
35
- success_result(loan)
36
+ success_result(loan, loan_transaction)
36
37
  end
37
38
  rescue StandardError => e
38
39
  error_result("Disbursement processing failed: #{e.message}")
@@ -132,7 +133,16 @@ module Dscf::Credit
132
133
  eligible_credit_line.update!(available_limit: [ new_available_limit, 0 ].max)
133
134
  end
134
135
 
135
- def success_result(loan)
136
+ def create_disbursement_transaction(loan)
137
+ transaction_service = LoanTransactionCreatorService.new(loan)
138
+ transaction_service.create_transaction(
139
+ transaction_type: "disbursement",
140
+ amount: loan.principal_amount,
141
+ status: "completed"
142
+ )
143
+ end
144
+
145
+ def success_result(loan, loan_transaction)
136
146
  # Reload to get associated accruals
137
147
  loan.reload
138
148
 
@@ -144,12 +154,14 @@ module Dscf::Credit
144
154
  {
145
155
  success: true,
146
156
  loan: loan,
157
+ loan_transaction: loan_transaction,
147
158
  disbursement_details: {
148
159
  principal_amount: loan.principal_amount.to_f,
149
160
  facilitation_fee: facilitation_fee,
150
161
  total_loan_amount: total_amount,
151
162
  due_date: loan.due_date,
152
- disbursed_at: loan.disbursed_at
163
+ disbursed_at: loan.disbursed_at,
164
+ transaction_reference: loan_transaction.transaction_reference
153
165
  },
154
166
  message: "Disbursement processed successfully"
155
167
  }
@@ -0,0 +1,71 @@
1
+ module Dscf::Credit
2
+ # Service for creating loan transaction records
3
+ # Handles transaction creation with automatic reference generation
4
+ #
5
+ # @example Disbursement transaction
6
+ # service = LoanTransactionCreatorService.new(loan)
7
+ # transaction = service.create_transaction(
8
+ # transaction_type: "disbursement",
9
+ # amount: 50000.00,
10
+ # status: "completed"
11
+ # )
12
+ #
13
+ # @example Repayment transaction
14
+ # service = LoanTransactionCreatorService.new(loan)
15
+ # transaction = service.create_transaction(
16
+ # transaction_type: "repayment",
17
+ # amount: 5000.00,
18
+ # status: "completed"
19
+ # )
20
+ class LoanTransactionCreatorService
21
+ attr_reader :loan
22
+
23
+ # Transaction type prefixes for reference generation
24
+ TRANSACTION_PREFIXES = {
25
+ "disbursement" => "DSB",
26
+ "repayment" => "RPY",
27
+ "interest_accrual" => "INT",
28
+ "penalty" => "PEN",
29
+ "fee_charge" => "FEE",
30
+ "reversal" => "REV",
31
+ "adjustment" => "ADJ"
32
+ }.freeze
33
+
34
+ def initialize(loan)
35
+ @loan = loan
36
+ end
37
+
38
+ # Create a loan transaction record
39
+ #
40
+ # @param transaction_type [String] Type of transaction (disbursement, repayment, etc.)
41
+ # @param amount [Numeric] Transaction amount
42
+ # @param status [String] Transaction status (pending, completed, failed, etc.)
43
+ # @return [Dscf::Credit::LoanTransaction] Created transaction record
44
+ def create_transaction(transaction_type:, amount:, status: "completed")
45
+ transaction_reference = generate_transaction_reference(transaction_type)
46
+
47
+ Dscf::Credit::LoanTransaction.create!(
48
+ loan: loan,
49
+ transaction_type: transaction_type,
50
+ amount: amount,
51
+ transaction_reference: transaction_reference,
52
+ status: status
53
+ )
54
+ end
55
+
56
+ private
57
+
58
+ # Generate unique transaction reference
59
+ # Format: PREFIX-LOAN_ID-TIMESTAMP-RANDOM
60
+ #
61
+ # @param transaction_type [String] Type of transaction
62
+ # @return [String] Generated reference
63
+ def generate_transaction_reference(transaction_type)
64
+ prefix = TRANSACTION_PREFIXES[transaction_type]
65
+ timestamp = Time.current.to_i
66
+ random = SecureRandom.hex(4)
67
+
68
+ "#{prefix}-#{loan.id}-#{timestamp}-#{random}"
69
+ end
70
+ end
71
+ end
@@ -85,7 +85,9 @@ module Dscf::Credit
85
85
 
86
86
  reactivate_facilities_if_paid_off
87
87
 
88
- success_result(payment_allocation)
88
+ loan_transaction = create_repayment_transaction(payment_allocation)
89
+
90
+ success_result(payment_allocation, loan_transaction)
89
91
  end
90
92
  rescue StandardError => e
91
93
  error_result("Repayment processing failed: #{e.message}")
@@ -345,11 +347,25 @@ module Dscf::Credit
345
347
  Rails.logger.info "Updated loan profile #{loan_profile.id} total limit to #{total_available_limit}"
346
348
  end
347
349
 
350
+ # Create a loan transaction record for the repayment
351
+ #
352
+ # @param allocation [Hash] The allocation hash with payment details
353
+ # @return [Dscf::Credit::LoanTransaction] The created transaction record
354
+ def create_repayment_transaction(allocation)
355
+ transaction_service = LoanTransactionCreatorService.new(loan)
356
+ transaction_service.create_transaction(
357
+ transaction_type: "repayment",
358
+ amount: payment_amount,
359
+ status: "completed"
360
+ )
361
+ end
362
+
348
363
  # Build success result hash
349
364
  #
350
365
  # @param allocation [Hash] The allocation hash with payment details
366
+ # @param loan_transaction [Dscf::Credit::LoanTransaction] The created transaction record
351
367
  # @return [Hash] Success response with loan, payment details, and message
352
- def success_result(allocation)
368
+ def success_result(allocation, loan_transaction)
353
369
  loan.reload
354
370
  pending_facilitation_fee = loan.loan_accruals.pending.by_type("facilitation_fee").sum(:amount)
355
371
  pending_penalty = loan.loan_accruals.pending.by_type("penalty").sum(:amount)
@@ -358,6 +374,7 @@ module Dscf::Credit
358
374
  {
359
375
  success: true,
360
376
  loan: loan,
377
+ loan_transaction: loan_transaction,
361
378
  payment_details: {
362
379
  payment_amount: payment_amount,
363
380
  allocation: allocation,
@@ -366,7 +383,8 @@ module Dscf::Credit
366
383
  remaining_balance: pending_facilitation_fee +
367
384
  pending_penalty +
368
385
  pending_interest +
369
- (loan.remaining_amount || 0)
386
+ (loan.remaining_amount || 0),
387
+ transaction_reference: loan_transaction.transaction_reference
370
388
  },
371
389
  message: loan.status == "paid" ? "Loan fully paid off" : "Payment processed successfully"
372
390
  }
@@ -61,6 +61,7 @@ en:
61
61
  show: "Scoring parameter details retrieved successfully"
62
62
  create: "Scoring parameter created successfully"
63
63
  update: "Scoring parameter updated successfully"
64
+ submit: "Scoring parameter submitted for review successfully"
64
65
  approve: "Scoring parameter approved successfully"
65
66
  reject: "Scoring parameter rejected successfully"
66
67
  request_modification: "Modification requested for scoring parameter successfully"
@@ -71,6 +72,7 @@ en:
71
72
  show: "Failed to retrieve scoring parameter details"
72
73
  create: "Failed to create scoring parameter"
73
74
  update: "Failed to update scoring parameter"
75
+ submit: "Failed to submit scoring parameter for review"
74
76
  approve: "Failed to approve scoring parameter"
75
77
  reject: "Failed to reject scoring parameter"
76
78
  request_modification: "Failed to request modification for scoring parameter"
@@ -83,6 +85,7 @@ en:
83
85
  show: "Loan profile details retrieved successfully"
84
86
  create: "Loan profile created successfully"
85
87
  update: "Loan profile updated successfully"
88
+ submit: "Loan profile submitted for review successfully"
86
89
  approve: "Loan profile approved successfully"
87
90
  reject: "Loan profile rejected successfully"
88
91
  request_modification: "Modification requested for loan profile successfully"
@@ -94,6 +97,7 @@ en:
94
97
  show: "Failed to retrieve loan profile details"
95
98
  create: "Failed to create loan profile"
96
99
  update: "Failed to update loan profile"
100
+ submit: "Failed to submit loan profile for review"
97
101
  approve: "Failed to approve loan profile"
98
102
  reject: "Failed to reject loan profile"
99
103
  request_modification: "Failed to request modification for loan profile"
@@ -124,6 +128,18 @@ en:
124
128
  generate: "Failed to generate loan accruals"
125
129
  statistics: "Failed to retrieve loan accrual statistics"
126
130
 
131
+ loan_transaction:
132
+ success:
133
+ index: "Loan transactions retrieved successfully"
134
+ show: "Loan transaction details retrieved successfully"
135
+ create: "Loan transaction created successfully"
136
+ update: "Loan transaction updated successfully"
137
+ errors:
138
+ index: "Failed to retrieve loan transactions"
139
+ show: "Failed to retrieve loan transaction details"
140
+ create: "Failed to create loan transaction"
141
+ update: "Failed to update loan transaction"
142
+
127
143
  loan:
128
144
  success:
129
145
  index: "Loans retrieved successfully"
@@ -156,6 +172,7 @@ en:
156
172
  show: "Facilitator details retrieved successfully"
157
173
  create: "Facilitator created successfully"
158
174
  update: "Facilitator updated successfully"
175
+ submit: "Facilitator submitted for review successfully"
159
176
  approve: "Facilitator approved successfully"
160
177
  reject: "Facilitator rejected successfully"
161
178
  request_modification: "Modification requested for facilitator successfully"
@@ -165,6 +182,7 @@ en:
165
182
  show: "Failed to retrieve facilitator details"
166
183
  create: "Failed to create facilitator"
167
184
  update: "Failed to update facilitator"
185
+ submit: "Failed to submit facilitator for review"
168
186
  approve: "Failed to approve facilitator"
169
187
  reject: "Failed to reject facilitator"
170
188
  request_modification: "Failed to request modification for facilitator"
@@ -191,6 +209,7 @@ en:
191
209
  show: "Credit line details retrieved successfully"
192
210
  create: "Credit line created successfully"
193
211
  update: "Credit line updated successfully"
212
+ submit: "Credit line submitted for review successfully"
194
213
  approve: "Credit line approved successfully"
195
214
  reject: "Credit line rejected successfully"
196
215
  destroy: "Credit line deleted successfully"
@@ -201,6 +220,7 @@ en:
201
220
  show: "Failed to retrieve credit line details"
202
221
  create: "Failed to create credit line"
203
222
  update: "Failed to update credit line"
223
+ submit: "Failed to submit credit line for review"
204
224
  approve: "Failed to approve credit line"
205
225
  reject: "Failed to reject credit line"
206
226
  destroy: "Failed to delete credit line"
@@ -242,6 +262,7 @@ en:
242
262
  show: "System config details retrieved successfully"
243
263
  create: "System config created successfully"
244
264
  update: "System config updated successfully"
265
+ submit: "System config submitted for review successfully"
245
266
  approve: "System config approved successfully"
246
267
  reject: "System config rejected successfully"
247
268
  request_modification: "Modification requested for system config successfully"
@@ -252,6 +273,7 @@ en:
252
273
  show: "Failed to retrieve system config details"
253
274
  create: "Failed to create system config"
254
275
  update: "Failed to update system config"
276
+ submit: "Failed to submit system config for review"
255
277
  approve: "Failed to approve system config"
256
278
  reject: "Failed to reject system config"
257
279
  request_modification: "Failed to request modification for system config"
@@ -299,6 +321,7 @@ en:
299
321
  update_bank_info: "Bank information updated successfully"
300
322
  update_facilitator_info: "Facilitator information updated successfully"
301
323
  update_field_assessment: "Field assessment updated successfully"
324
+ submit: "Loan application submitted for review successfully"
302
325
  calculate_credit_score: "Credit score calculated successfully"
303
326
  apply_risk_factor: "Risk factor applied successfully"
304
327
  approve: "Loan application approved successfully"
@@ -313,10 +336,10 @@ en:
313
336
  update_bank_info: "Failed to update bank information"
314
337
  update_facilitator_info: "Failed to update facilitator information"
315
338
  update_field_assessment: "Failed to update field assessment"
339
+ submit: "Failed to submit loan application for review"
316
340
  calculate_credit_score: "Failed to calculate credit score"
317
341
  apply_risk_factor: "Failed to apply risk factor"
318
342
  invalid_risk_percentage: "Invalid risk percentage provided"
319
- no_loan_profile: "No loan profile found for this application"
320
343
  approve: "Failed to approve loan application"
321
344
  reject: "Failed to reject loan application"
322
345
  request_modification: "Failed to request modification for loan application"
@@ -329,6 +352,7 @@ en:
329
352
  show: "Facilitator application details retrieved successfully"
330
353
  create: "Facilitator application created successfully"
331
354
  update: "Facilitator application updated successfully"
355
+ submit: "Facilitator application submitted for review successfully"
332
356
  approve: "Facilitator application approved successfully"
333
357
  reject: "Facilitator application rejected successfully"
334
358
  request_modification: "Modification requested for facilitator application successfully"
@@ -339,6 +363,7 @@ en:
339
363
  show: "Failed to retrieve facilitator application details"
340
364
  create: "Failed to create facilitator application"
341
365
  update: "Failed to update facilitator application"
366
+ submit: "Failed to submit facilitator application for review"
342
367
  approve: "Failed to approve facilitator application"
343
368
  reject: "Failed to reject facilitator application"
344
369
  request_modification: "Failed to request modification for facilitator application"
data/config/routes.rb CHANGED
@@ -13,6 +13,7 @@ Dscf::Credit::Engine.routes.draw do
13
13
 
14
14
  resources :facilitators do
15
15
  member do
16
+ patch "submit"
16
17
  patch "approve"
17
18
  patch "reject"
18
19
  patch "request_modification"
@@ -22,8 +23,11 @@ Dscf::Credit::Engine.routes.draw do
22
23
 
23
24
  resources :loan_profiles do
24
25
  member do
26
+ patch "submit"
25
27
  patch "approve"
26
28
  patch "reject"
29
+ patch "request_modification"
30
+ patch "resubmit"
27
31
  post "calculate_facility_limits"
28
32
  end
29
33
  end
@@ -35,6 +39,7 @@ Dscf::Credit::Engine.routes.draw do
35
39
  end
36
40
  resources :scoring_parameters do
37
41
  member do
42
+ patch "submit"
38
43
  patch "approve"
39
44
  patch "reject"
40
45
  patch "request_modification"
@@ -44,6 +49,7 @@ Dscf::Credit::Engine.routes.draw do
44
49
  resources :parameter_normalizers
45
50
  resources :system_configs do
46
51
  member do
52
+ patch "submit"
47
53
  patch "approve"
48
54
  patch "reject"
49
55
  patch "request_modification"
@@ -53,6 +59,7 @@ Dscf::Credit::Engine.routes.draw do
53
59
  resources :system_config_definitions
54
60
  resources :credit_lines do
55
61
  member do
62
+ patch "submit"
56
63
  patch "approve"
57
64
  patch "reject"
58
65
  patch "request_modification"
@@ -73,8 +80,10 @@ Dscf::Credit::Engine.routes.draw do
73
80
  resources :credit_limit_calculations, only: [ :create ]
74
81
  resources :disbursements, only: [ :create ]
75
82
  resources :repayments, only: [ :create ]
83
+ resources :loan_transactions
76
84
  resources :loan_applications do
77
85
  member do
86
+ patch "submit"
78
87
  patch "approve"
79
88
  patch "reject"
80
89
  patch "request_modification"
@@ -92,6 +101,7 @@ Dscf::Credit::Engine.routes.draw do
92
101
  end
93
102
 
94
103
  member do
104
+ patch "submit"
95
105
  patch "approve"
96
106
  patch "reject"
97
107
  patch "request_modification"
@@ -8,7 +8,9 @@ class CreateDscfCreditCreditLineSpecs < ActiveRecord::Migration[8.0]
8
8
  t.decimal :penalty_rate, precision: 5, scale: 4, null: false
9
9
  t.decimal :facilitation_fee_rate, precision: 5, scale: 4, null: false
10
10
  t.decimal :tax_rate, precision: 5, scale: 4, null: false
11
- t.decimal :credit_line_multiplier, precision: 5, scale: 2, null: false, default: 30.0
11
+ t.decimal :credit_line_multiplier, precision: 5, scale: 2, null: false
12
+ t.references :base_scoring_parameter, foreign_key: { to_table: :dscf_credit_scoring_parameters }
13
+ t.integer :credit_line_divider
12
14
  t.integer :max_penalty_days, null: false
13
15
  t.integer :loan_duration, null: false
14
16
  t.string :interest_frequency, null: false
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Credit
3
- VERSION = "0.3.7"
3
+ VERSION = "0.3.9"
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.3.7
4
+ version: 0.3.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adoniyas
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-10-16 00:00:00.000000000 Z
10
+ date: 2025-11-06 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dscf-core
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.2.0
18
+ version: 0.2.6
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.2.0
25
+ version: 0.2.6
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: active_model_serializers
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -328,6 +328,7 @@ files:
328
328
  - app/controllers/dscf/credit/loan_accruals_controller.rb
329
329
  - app/controllers/dscf/credit/loan_applications_controller.rb
330
330
  - app/controllers/dscf/credit/loan_profiles_controller.rb
331
+ - app/controllers/dscf/credit/loan_transactions_controller.rb
331
332
  - app/controllers/dscf/credit/loans_controller.rb
332
333
  - app/controllers/dscf/credit/parameter_normalizers_controller.rb
333
334
  - app/controllers/dscf/credit/repayments_controller.rb
@@ -398,6 +399,7 @@ files:
398
399
  - app/services/dscf/credit/facility_limit_calculation_engine.rb
399
400
  - app/services/dscf/credit/loan_accrual_generator_service.rb
400
401
  - app/services/dscf/credit/loan_profile_creation_service.rb
402
+ - app/services/dscf/credit/loan_transaction_creator_service.rb
401
403
  - app/services/dscf/credit/repayment_service.rb
402
404
  - app/services/dscf/credit/risk_application_service.rb
403
405
  - app/services/dscf/credit/scoring_service.rb
@@ -419,11 +421,11 @@ files:
419
421
  - db/migrate/20250822091015_create_dscf_credit_banks.rb
420
422
  - db/migrate/20250822091055_create_dscf_credit_bank_branches.rb
421
423
  - db/migrate/20250822091131_create_dscf_credit_credit_lines.rb
424
+ - db/migrate/20250822091525_create_dscf_credit_scoring_param_types.rb
425
+ - db/migrate/20250822091526_create_dscf_credit_scoring_parameters.rb
422
426
  - db/migrate/20250822091527_create_dscf_credit_credit_line_specs.rb
423
427
  - db/migrate/20250822091744_create_dscf_credit_system_config_definitions.rb
424
428
  - db/migrate/20250822091820_create_dscf_credit_system_configs.rb
425
- - db/migrate/20250822092040_create_dscf_credit_scoring_param_types.rb
426
- - db/migrate/20250822092050_create_dscf_credit_scoring_parameters.rb
427
429
  - db/migrate/20250822092225_create_dscf_credit_parameter_normalizers.rb
428
430
  - db/migrate/20250822092236_create_dscf_credit_loan_applications.rb
429
431
  - db/migrate/20250822092246_create_dscf_credit_loan_profiles.rb
@@ -440,9 +442,6 @@ files:
440
442
  - db/migrate/20250825231109_create_dscf_credit_bank_staff.rb
441
443
  - db/migrate/20250917120000_create_dscf_credit_eligible_credit_lines.rb
442
444
  - db/migrate/20251003132939_create_dscf_credit_loan_accruals.rb
443
- - db/migrate/20251011202425_add_base_scoring_parameter_to_dscf_credit_credit_line_specs.rb
444
- - db/migrate/20251012100039_add_credit_line_divider_to_dscf_credit_credit_line_specs.rb
445
- - db/migrate/20251012115159_remove_default_from_credit_line_multiplier_in_credit_line_specs.rb
446
445
  - db/seeds.rb
447
446
  - lib/dscf/credit.rb
448
447
  - lib/dscf/credit/engine.rb
@@ -1,5 +0,0 @@
1
- class AddBaseScoringParameterToDscfCreditCreditLineSpecs < ActiveRecord::Migration[8.0]
2
- def change
3
- add_reference :dscf_credit_credit_line_specs, :base_scoring_parameter, null: true, foreign_key: { to_table: :dscf_credit_scoring_parameters }, index: true
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- class AddCreditLineDividerToDscfCreditCreditLineSpecs < ActiveRecord::Migration[8.0]
2
- def change
3
- add_column :dscf_credit_credit_line_specs, :credit_line_divider, :integer, null: true
4
- end
5
- end
@@ -1,5 +0,0 @@
1
- class RemoveDefaultFromCreditLineMultiplierInCreditLineSpecs < ActiveRecord::Migration[8.0]
2
- def change
3
- change_column_default :dscf_credit_credit_line_specs, :credit_line_multiplier, from: 30.0, to: nil
4
- end
5
- end