dscf-credit 0.1.7 → 0.1.8

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: b98c5581c2ba2394b35bee75cce5a9f8de59cf7c94c12f1b8d162988a2e8a285
4
- data.tar.gz: 6a377eaa47bd1b079f77fb24b408ebdbaf37e498703d85e3f39fbe5b0d233779
3
+ metadata.gz: 1e5ce384f2229c93db978e4eff5599a0d0fb9fe74e586da8bab5209a63dcb9f6
4
+ data.tar.gz: 889029fa9ee854b75198eed3a539215139d59914f09f8337674583ea153b3703
5
5
  SHA512:
6
- metadata.gz: 8ddcceea25d14c51c2af4e3ec279751f8a69193afa3a0e8713de2fc6acb9470f0691067e3c3cbbf34b4727acb905714402c22abbc4e0e0b8bdc20bfdf16ef6a5
7
- data.tar.gz: 620cd65a6e50a64c084aeba071ba854bc10ee9a01fd8acccb3b740c51245fc0b0138fcb249e80261fbf305c1ebe6572841d89bc882d442f9e2e09c6add0bf16e
6
+ metadata.gz: b0772af1d0a922a4826de14d34c8d18f78480fe17a8c7db23d22e1e2e463ccb8e95cc7a6db8d6ff8ffb178036239316c26b8e2b8c18c2808e03f2639bae028b2
7
+ data.tar.gz: 27587c70165aeda76c4577ef9e109b03539f0a5188681a3108408724e84fa39a681e7ba699cc6e213c886df5344f8cd196717947b83a882f7db43e49c80909e2
@@ -0,0 +1,113 @@
1
+ module Dscf::Credit
2
+ class LoanAccrualsController < ApplicationController
3
+ include Dscf::Core::Common
4
+
5
+ def generate
6
+ service = LoanAccrualGeneratorService.new(
7
+ loan_ids: params[:loan_ids],
8
+ accrual_date: parse_accrual_date,
9
+ force_regenerate: ActiveModel::Type::Boolean.new.cast(params[:force_regenerate])
10
+ )
11
+
12
+ result = service.generate_daily_accruals
13
+
14
+ if result[:success]
15
+ render_success(
16
+ result[:message],
17
+ data: result,
18
+ status: :created
19
+ )
20
+ else
21
+ render_error(
22
+ result[:message],
23
+ errors: result[:errors],
24
+ status: :unprocessable_entity
25
+ )
26
+ end
27
+ end
28
+
29
+ def statistics
30
+ loan = Loan.find(params[:loan_id])
31
+
32
+ stats = {
33
+ loan_id: loan.id,
34
+ principal_amount: loan.principal_amount,
35
+ remaining_amount: loan.remaining_amount,
36
+ total_facilitation_fee: loan.total_facilitation_fee,
37
+ total_interest: loan.total_interest,
38
+ total_penalty: loan.total_penalty,
39
+ total_vat: loan.total_vat,
40
+ total_outstanding: loan.total_outstanding,
41
+ accrual_breakdown: {
42
+ facilitation_fee: {
43
+ pending: loan.loan_accruals.where(accrual_type: 'facilitation_fee', status: 'pending').sum(:amount),
44
+ paid: loan.loan_accruals.where(accrual_type: 'facilitation_fee', status: 'paid').sum(:amount)
45
+ },
46
+ tax: {
47
+ pending: loan.loan_accruals.where(accrual_type: 'tax', status: 'pending').sum(:amount),
48
+ paid: loan.loan_accruals.where(accrual_type: 'tax', status: 'paid').sum(:amount)
49
+ },
50
+ interest: {
51
+ pending: loan.loan_accruals.where(accrual_type: 'interest', status: 'pending').sum(:amount),
52
+ paid: loan.loan_accruals.where(accrual_type: 'interest', status: 'paid').sum(:amount)
53
+ },
54
+ penalty: {
55
+ pending: loan.loan_accruals.where(accrual_type: 'penalty', status: 'pending').sum(:amount),
56
+ paid: loan.loan_accruals.where(accrual_type: 'penalty', status: 'paid').sum(:amount)
57
+ }
58
+ },
59
+ accrual_count: {
60
+ total: loan.loan_accruals.count,
61
+ pending: loan.loan_accruals.where(status: 'pending').count,
62
+ paid: loan.loan_accruals.where(status: 'paid').count,
63
+ cancelled: loan.loan_accruals.where(status: 'cancelled').count
64
+ }
65
+ }
66
+
67
+ render_success(
68
+ 'Loan accrual statistics retrieved successfully',
69
+ data: stats
70
+ )
71
+ end
72
+
73
+ private
74
+
75
+ # Parse accrual_date parameter
76
+ #
77
+ # @return [Date] Parsed date or Date.current
78
+ def parse_accrual_date
79
+ return Date.current unless params[:accrual_date].present?
80
+
81
+ Date.parse(params[:accrual_date].to_s)
82
+ rescue ArgumentError
83
+ Date.current
84
+ end
85
+
86
+ def model_params
87
+ params.require(:loan_accrual).permit(
88
+ :loan_id,
89
+ :accrual_type,
90
+ :amount,
91
+ :applied_on,
92
+ :status
93
+ )
94
+ end
95
+
96
+ def eager_loaded_associations
97
+ [:loan]
98
+ end
99
+
100
+ def allowed_order_columns
101
+ %w[id accrual_type amount applied_on status created_at updated_at]
102
+ end
103
+
104
+ def default_serializer_includes
105
+ {
106
+ index: [:loan],
107
+ show: [:loan],
108
+ create: [:loan],
109
+ update: [:loan]
110
+ }
111
+ end
112
+ end
113
+ end
@@ -62,17 +62,17 @@ module Dscf::Credit
62
62
 
63
63
  def calculate_credit_score
64
64
  loan_application = @clazz.find(params[:id])
65
- category_id = score_params[:category_id]
65
+ scoring_param_type_id = score_params[:scoring_param_type_id]
66
66
 
67
- unless category_id
67
+ unless scoring_param_type_id
68
68
  return render_error(
69
69
  "loan_application.errors.calculate_credit_score",
70
- errors: [ "Category ID is required for credit scoring" ],
70
+ errors: [ "Scoring parameter type ID is required for credit scoring" ],
71
71
  status: :unprocessable_entity
72
72
  )
73
73
  end
74
74
 
75
- scoring_engine = CreditScoringEngine.new(loan_application.id, category_id)
75
+ scoring_engine = CreditScoringEngine.new(loan_application.id, scoring_param_type_id)
76
76
  result = scoring_engine.calculate_score
77
77
 
78
78
  if result[:success]
@@ -215,7 +215,7 @@ module Dscf::Credit
215
215
  end
216
216
 
217
217
  def score_params
218
- params.permit(:category_id)
218
+ params.permit(:scoring_param_type_id)
219
219
  end
220
220
 
221
221
  def eager_loaded_associations
@@ -105,7 +105,7 @@ module Dscf::Credit
105
105
 
106
106
  def eager_loaded_associations
107
107
  [
108
- :loan_application, :loan_profile_scoring_specs, :loans, :eligible_credit_lines,
108
+ :loan_application, :loan_profile_scoring_specs, :loans, eligible_credit_lines: { credit_line: :credit_line },
109
109
  reviews: { reviewed_by: :user_profile }
110
110
  ]
111
111
  end
@@ -118,7 +118,7 @@ module Dscf::Credit
118
118
  {
119
119
  index: [ :loan_application, reviews: { reviewed_by: :user_profile } ],
120
120
  show: [
121
- :loan_application, :loan_profile_scoring_specs, :loans, :eligible_credit_lines, reviews: { reviewed_by: :user_profile }
121
+ :loan_application, :loan_profile_scoring_specs, :loans, eligible_credit_lines: { credit_line: :credit_line }, reviews: { reviewed_by: :user_profile }
122
122
  ],
123
123
  create: [ :loan_application, :loan_profile_scoring_specs, :reviews ],
124
124
  update: [
@@ -10,10 +10,6 @@ module Dscf::Credit
10
10
  :credit_line_id,
11
11
  :status,
12
12
  :principal_amount,
13
- :accrued_interest,
14
- :accrued_penalty,
15
- :facilitation_fee,
16
- :total_loan_amount,
17
13
  :remaining_amount,
18
14
  :due_date,
19
15
  :disbursed_at,
@@ -22,11 +18,11 @@ module Dscf::Credit
22
18
  end
23
19
 
24
20
  def eager_loaded_associations
25
- [ :loan_profile, :credit_line, :loan_transactions ]
21
+ [ :loan_profile, :credit_line, :loan_transactions, :loan_accruals ]
26
22
  end
27
23
 
28
24
  def allowed_order_columns
29
- %w[id status principal_amount total_loan_amount remaining_amount due_date disbursed_at created_at updated_at]
25
+ %w[id status principal_amount remaining_amount due_date disbursed_at created_at updated_at]
30
26
  end
31
27
 
32
28
  def default_serializer_includes
@@ -1,10 +1,11 @@
1
1
  module Dscf::Credit
2
2
  class RepaymentsController < ApplicationController
3
+
3
4
  def create
4
5
  loan = Dscf::Credit::Loan.find(params[:loan_id])
5
6
  payment_amount = params[:amount].to_f
6
7
 
7
- service = Dscf::Credit::RepaymentService.new(loan, payment_amount, current_user)
8
+ service = Dscf::Credit::RepaymentService.new(loan, payment_amount)
8
9
  result = service.process_repayment
9
10
 
10
11
  if result[:success]
@@ -18,8 +19,7 @@ module Dscf::Credit
18
19
  include: [
19
20
  :loan_profile,
20
21
  :credit_line,
21
- :payment_request,
22
- :loan_transactions
22
+ :loan_accruals
23
23
  ]
24
24
  }
25
25
  )
@@ -62,7 +62,7 @@ module Dscf::Credit
62
62
 
63
63
  def default_serializer_includes
64
64
  {
65
- index: [ :bank, :category, reviews: { reviewed_by: :user_profile } ],
65
+ index: [ :bank, :category, :parameter_normalizers, reviews: { reviewed_by: :user_profile } ],
66
66
  show: [
67
67
  :bank, :category, :created_by, :scoring_param_type, :previous_version, :parameter_normalizers,
68
68
  reviews: { reviewed_by: :user_profile }
@@ -16,8 +16,14 @@ module Dscf::Credit
16
16
  )
17
17
  end
18
18
 
19
+ rejected_user_ids = Dscf::Credit::FacilitatorApplication.joins(:reviews)
20
+ .where(dscf_core_reviews: { status: 'rejected' })
21
+ .pluck(:user_id)
22
+ .uniq
23
+
19
24
  @clazz.joins(businesses: :business_type)
20
25
  .where(dscf_core_business_types: { name: business_type_name })
26
+ .where(id: rejected_user_ids)
21
27
  .distinct
22
28
  else
23
29
  @clazz.all
@@ -0,0 +1,78 @@
1
+ module Dscf::Credit
2
+ # Background job to generate daily loan accruals
3
+ #
4
+ # This job should be scheduled to run daily (typically at midnight or early morning)
5
+ # to generate interest and penalty accruals for all active loans.
6
+ #
7
+ # @example Schedule with Sidekiq (config/sidekiq.yml)
8
+ # :schedule:
9
+ # generate_daily_accruals:
10
+ # cron: '0 1 * * *' # Run at 1 AM daily
11
+ # class: Dscf::Credit::GenerateDailyAccrualsJob
12
+ #
13
+ # @example Schedule with whenever gem (config/schedule.rb)
14
+ # every 1.day, at: '1:00 am' do
15
+ # runner "Dscf::Credit::GenerateDailyAccrualsJob.perform_later"
16
+ # end
17
+ #
18
+ # @example Manual execution
19
+ # Dscf::Credit::GenerateDailyAccrualsJob.perform_now
20
+ #
21
+ # @example With specific parameters
22
+ # Dscf::Credit::GenerateDailyAccrualsJob.perform_later(
23
+ # loan_ids: [1, 2, 3],
24
+ # accrual_date: Date.yesterday
25
+ # )
26
+ class GenerateDailyAccrualsJob < ApplicationJob
27
+ queue_as :default
28
+
29
+ # Perform the job to generate daily accruals
30
+ #
31
+ # @param loan_ids [Array<Integer>, nil] Optional specific loan IDs to process
32
+ # @param accrual_date [String, Date] Date for accrual generation (defaults to today)
33
+ # @param force_regenerate [Boolean] Whether to regenerate existing accruals
34
+ def perform(loan_ids: nil, accrual_date: nil, force_regenerate: false)
35
+ date = accrual_date ? Date.parse(accrual_date.to_s) : Date.current
36
+
37
+ service = LoanAccrualGeneratorService.new(
38
+ loan_ids: loan_ids,
39
+ accrual_date: date,
40
+ force_regenerate: force_regenerate
41
+ )
42
+
43
+ result = service.generate_daily_accruals
44
+
45
+ log_result(result)
46
+
47
+ result
48
+ end
49
+
50
+ private
51
+
52
+ # Log the result of accrual generation
53
+ #
54
+ # @param result [Hash] The result from the service
55
+ def log_result(result)
56
+ if result[:success]
57
+ Rails.logger.info(
58
+ "Loan Accrual Generation: #{result[:message]}"
59
+ )
60
+
61
+ if result[:errors].any?
62
+ Rails.logger.warn(
63
+ "Loan Accrual Generation Warnings: #{result[:errors].size} loans had errors"
64
+ )
65
+ result[:errors].each do |error|
66
+ Rails.logger.warn(
67
+ "Loan #{error[:loan_id]}: #{error[:error]}"
68
+ )
69
+ end
70
+ end
71
+ else
72
+ Rails.logger.error(
73
+ "Loan Accrual Generation Failed: #{result[:message]}"
74
+ )
75
+ end
76
+ end
77
+ end
78
+ end
@@ -6,10 +6,11 @@ module Dscf::Credit
6
6
  belongs_to :credit_line, class_name: "Dscf::Credit::CreditLine", foreign_key: "credit_line_id"
7
7
  has_many :loan_transactions, class_name: "Dscf::Credit::LoanTransaction", foreign_key: "loan_id", dependent: :destroy
8
8
  has_many :daily_routine_transactions, class_name: "Dscf::Credit::DailyRoutineTransaction", foreign_key: "loan_id", dependent: :destroy
9
+ has_many :loan_accruals, class_name: "Dscf::Credit::LoanAccrual", foreign_key: "loan_id", dependent: :destroy
9
10
 
10
- validates :principal_amount, :total_loan_amount, :remaining_amount, :due_date, presence: true
11
- validates :principal_amount, :total_loan_amount, :remaining_amount, numericality: { greater_than: 0 }
12
- validates :accrued_interest, :accrued_penalty, :facilitation_fee, numericality: { greater_than_or_equal_to: 0 }
11
+ validates :principal_amount, :remaining_amount, :due_date, presence: true
12
+ validates :principal_amount, numericality: { greater_than: 0 }
13
+ validates :remaining_amount, numericality: { greater_than_or_equal_to: 0 }
13
14
  validates :status, inclusion: { in: %w[pending approved disbursed active overdue paid closed] }
14
15
 
15
16
  scope :active, -> { where(active: true) }
@@ -20,14 +21,34 @@ module Dscf::Credit
20
21
  scope :past_due, -> { where("due_date < ? AND status IN (?)", Date.current, [ "active", "overdue" ]) }
21
22
 
22
23
  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 active created_at updated_at]
24
+ %w[id status principal_amount remaining_amount due_date disbursed_at active created_at updated_at]
24
25
  end
25
26
 
26
27
  def self.ransackable_associations(auth_object = nil)
27
- %w[loan_profile credit_line loan_transactions daily_routine_transactions]
28
+ %w[loan_profile credit_line loan_transactions daily_routine_transactions loan_accruals]
28
29
  end
29
30
 
30
31
  delegate :user, to: :loan_profile, allow_nil: true
31
32
  delegate :bank, to: :credit_line, allow_nil: true
33
+
34
+ def total_facilitation_fee
35
+ loan_accruals.where(accrual_type: 'facilitation_fee', status: 'pending').sum(:amount)
36
+ end
37
+
38
+ def total_interest
39
+ loan_accruals.where(accrual_type: 'interest', status: 'pending').sum(:amount)
40
+ end
41
+
42
+ def total_penalty
43
+ loan_accruals.where(accrual_type: 'penalty', status: 'pending').sum(:amount)
44
+ end
45
+
46
+ def total_vat
47
+ loan_accruals.where(accrual_type: 'tax', status: 'pending').sum(:amount)
48
+ end
49
+
50
+ def total_outstanding
51
+ remaining_amount + loan_accruals.where(status: 'pending').sum(:amount)
52
+ end
32
53
  end
33
54
  end
@@ -0,0 +1,25 @@
1
+ module Dscf::Credit
2
+ class LoanAccrual < ApplicationRecord
3
+ self.table_name = "dscf_credit_loan_accruals"
4
+
5
+ belongs_to :loan, class_name: "Dscf::Credit::Loan", foreign_key: "loan_id"
6
+
7
+ validates :accrual_type, :amount, :applied_on, presence: true
8
+ validates :amount, numericality: { greater_than: 0 }
9
+ validates :accrual_type, inclusion: { in: %w[interest penalty tax facilitation_fee late_fee other] }
10
+ validates :status, inclusion: { in: %w[pending paid cancelled] }
11
+
12
+ scope :pending, -> { where(status: "pending") }
13
+ scope :paid, -> { where(status: "paid") }
14
+ scope :by_type, ->(type) { where(accrual_type: type) }
15
+ scope :for_date_range, ->(start_date, end_date) { where(applied_on: start_date..end_date) }
16
+
17
+ def self.ransackable_attributes(auth_object = nil)
18
+ %w[id accrual_type amount applied_on status created_at updated_at]
19
+ end
20
+
21
+ def self.ransackable_associations(auth_object = nil)
22
+ %w[loan]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module Dscf::Credit
2
+ class LoanAccrualSerializer < ActiveModel::Serializer
3
+ attributes :id, :accrual_type, :amount, :applied_on, :status, :created_at, :updated_at
4
+
5
+ belongs_to :loan, serializer: LoanSerializer
6
+ end
7
+ end
@@ -1,11 +1,33 @@
1
1
  module Dscf::Credit
2
2
  class LoanSerializer < ActiveModel::Serializer
3
- attributes :id, :status, :principal_amount, :accrued_interest, :accrued_penalty, :facilitation_fee,
4
- :total_loan_amount, :remaining_amount, :due_date, :disbursed_at, :active, :created_at, :updated_at
3
+ attributes :id, :status, :principal_amount, :remaining_amount, :due_date, :disbursed_at, :total_interest, :total_penalty,
4
+ :total_vat, :total_facilitation_fee, :total_outstanding, :active, :created_at, :updated_at
5
5
 
6
6
  belongs_to :loan_profile, serializer: LoanProfileSerializer
7
7
  belongs_to :credit_line, serializer: CreditLineSerializer
8
8
  has_many :loan_transactions, serializer: LoanTransactionSerializer
9
9
  has_many :daily_routine_transactions, serializer: DailyRoutineTransactionSerializer
10
+ has_many :loan_accruals, serializer: LoanAccrualSerializer
11
+
12
+ def total_interest
13
+ object.total_interest
14
+ end
15
+
16
+ def total_penalty
17
+ object.total_penalty
18
+ end
19
+
20
+ def total_vat
21
+ object.total_vat
22
+ end
23
+
24
+ def total_facilitation_fee
25
+ object.total_facilitation_fee
26
+ end
27
+
28
+ def total_outstanding
29
+ object.total_outstanding
30
+ end
31
+
10
32
  end
11
33
  end
@@ -1,22 +1,22 @@
1
1
  module Dscf::Credit
2
2
  class CreditScoringEngine
3
- attr_reader :loan_application, :category_id, :errors
3
+ attr_reader :loan_application, :scoring_param_type_id, :errors
4
4
 
5
- def initialize(loan_application_id, category_id)
5
+ def initialize(loan_application_id, scoring_param_type_id)
6
6
  @loan_application = find_loan_application(loan_application_id)
7
- @category_id = category_id
7
+ @scoring_param_type_id = scoring_param_type_id
8
8
  @errors = []
9
9
  end
10
10
 
11
11
  def calculate_score
12
12
  return error_result("Loan application not found") unless loan_application
13
- return error_result("Category ID is required") unless category_id
13
+ return error_result("Scoring parameter type ID is required") unless scoring_param_type_id
14
14
 
15
15
  begin
16
16
  scoring_parameters = get_active_scoring_parameters
17
17
 
18
18
  if scoring_parameters.empty?
19
- return error_result("No active scoring parameters found for this bank for category ID #{category_id}")
19
+ return error_result("No active scoring parameters found for this bank for scoring parameter type ID #{scoring_param_type_id}")
20
20
  end
21
21
 
22
22
  # Calculate weighted score using the formula
@@ -45,7 +45,7 @@ module Dscf::Credit
45
45
  loan_application.bank
46
46
  .scoring_parameters
47
47
  .active
48
- .where(category_id: category_id)
48
+ .where(scoring_param_type_id: scoring_param_type_id)
49
49
  .includes(:scoring_param_type, :parameter_normalizers, :category)
50
50
  end
51
51
 
@@ -217,7 +217,7 @@ module Dscf::Credit
217
217
  success: true,
218
218
  loan_application_id: loan_application.id,
219
219
  bank_id: loan_application.bank_id,
220
- category_id: category_id,
220
+ scoring_param_type_id: scoring_param_type_id,
221
221
  calculated_at: Time.current,
222
222
  score: final_score,
223
223
  status: review_status,
@@ -235,7 +235,7 @@ module Dscf::Credit
235
235
  {
236
236
  success: false,
237
237
  loan_application_id: loan_application&.id,
238
- category_id: category_id,
238
+ scoring_param_type_id: scoring_param_type_id,
239
239
  error: message,
240
240
  errors: @errors,
241
241
  score: 0.0,
@@ -60,11 +60,7 @@ module Dscf::Credit
60
60
  loan_profile: loan_profile,
61
61
  credit_line: credit_line,
62
62
  principal_amount: amount,
63
- facilitation_fee: loan_terms[:facilitation_fee],
64
- total_loan_amount: loan_terms[:total_amount],
65
- remaining_amount: loan_terms[:total_amount],
66
- accrued_interest: 0,
67
- accrued_penalty: 0,
63
+ remaining_amount: amount, # Only principal amount, not total
68
64
  status: "disbursed",
69
65
  due_date: loan_terms[:due_date],
70
66
  disbursed_at: Time.current,
@@ -73,6 +69,12 @@ module Dscf::Credit
73
69
 
74
70
  raise "Failed to create loan: #{loan.errors.full_messages.join(', ')}" unless loan.save
75
71
 
72
+ # Create facilitator fee accrual
73
+ create_facilitator_fee_accrual(loan, loan_terms[:facilitation_fee])
74
+
75
+ # Create VAT accrual if applicable
76
+ create_vat_accrual(loan, loan_terms[:vat_amount]) if loan_terms[:vat_amount] > 0
77
+
76
78
  loan
77
79
  end
78
80
 
@@ -104,19 +106,59 @@ module Dscf::Credit
104
106
  }
105
107
  end
106
108
 
109
+ def create_facilitator_fee_accrual(loan, facilitation_fee)
110
+ return if facilitation_fee <= 0
111
+
112
+ Dscf::Credit::LoanAccrual.create!(
113
+ loan: loan,
114
+ accrual_type: "facilitation_fee",
115
+ amount: facilitation_fee,
116
+ applied_on: Date.current,
117
+ status: "pending"
118
+ )
119
+ end
120
+
121
+ def create_vat_accrual(loan, vat_amount)
122
+ return if vat_amount <= 0
123
+
124
+ Dscf::Credit::LoanAccrual.create!(
125
+ loan: loan,
126
+ accrual_type: "tax",
127
+ amount: vat_amount,
128
+ applied_on: Date.current,
129
+ status: "pending"
130
+ )
131
+ end
132
+
107
133
  def update_credit_line_limits(loan)
108
- new_available_limit = eligible_credit_line.available_limit - loan.total_loan_amount
134
+ # Calculate total amount from accruals: principal + facilitation_fee + vat
135
+ facilitation_fee = loan.loan_accruals.find_by(accrual_type: "facilitation_fee")&.amount || 0
136
+ vat = loan.loan_accruals.find_by(accrual_type: "tax")&.amount || 0
137
+ total_amount = loan.principal_amount + facilitation_fee + vat
138
+
139
+ new_available_limit = eligible_credit_line.available_limit - total_amount
109
140
  eligible_credit_line.update!(available_limit: [ new_available_limit, 0 ].max)
110
141
  end
111
142
 
112
143
  def success_result(loan)
144
+ # Reload to get associated accruals
145
+ loan.reload
146
+
147
+ facilitation_fee_accrual = loan.loan_accruals.find_by(accrual_type: "facilitation_fee")
148
+ vat_accrual = loan.loan_accruals.find_by(accrual_type: "tax")
149
+
150
+ facilitation_fee = facilitation_fee_accrual&.amount&.to_f || 0.0
151
+ vat_amount = vat_accrual&.amount&.to_f || 0.0
152
+ total_amount = loan.principal_amount.to_f + facilitation_fee + vat_amount
153
+
113
154
  {
114
155
  success: true,
115
156
  loan: loan,
116
157
  disbursement_details: {
117
158
  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,
159
+ facilitation_fee: facilitation_fee,
160
+ vat_amount: vat_amount,
161
+ total_loan_amount: total_amount,
120
162
  due_date: loan.due_date,
121
163
  disbursed_at: loan.disbursed_at
122
164
  },