dscf-credit 0.2.9 → 0.3.1

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: c914fd16b9fb0dd15339a3af9bf97e95d48d3e1775a729f4fefe00e6d097b070
4
- data.tar.gz: a22057ffd689e168026b01e279a5695af7ffb292bec64998db8d73266cfdb2d2
3
+ metadata.gz: 9e3b6a324773633a199dfe48d49baa98a8e410671da8ad4916f107ad38ad33fb
4
+ data.tar.gz: 5accc6330524779a8720ebc16b9db48bd8ee9673552216d8fce8d38ebd6e40cd
5
5
  SHA512:
6
- metadata.gz: 7d322610efcb2472084f0bdc462a76ee502da87f4d9be227ad66b4f8e38041b39b6c74690281ab407e50e38c445231cdc702a1e04c389c490d6d2189870ba096
7
- data.tar.gz: 0af0fb14a256dc42893fe5dc6b920107b0f104a6ebfc403de37467b36b64017f4684a64201024ea9766f563132c5de3a24b3df21ba4b47121c32cdcf0cfcba32
6
+ metadata.gz: 2979387747dc0f40646df26c2cdcd266443cf07fb1c417deed6f704b62292f650469a490149891f0594df3e245e8ec1e3884f65abab9af58692aae4dd71c13ce
7
+ data.tar.gz: 8263bd4bc902d6525d3674736fe6301e1d032f84e9c1db21dcc038b7c36c2049e532e23097b4d03532f99e6a8288caf186f9793a2189522e9ffe7eaa9a532e6b
@@ -13,13 +13,11 @@ module Dscf::Credit
13
13
 
14
14
  if result[:success]
15
15
  render_success(
16
- result[:message],
17
16
  data: result,
18
17
  status: :created
19
18
  )
20
19
  else
21
20
  render_error(
22
- result[:message],
23
21
  errors: result[:errors],
24
22
  status: :unprocessable_entity
25
23
  )
@@ -119,31 +119,45 @@ module Dscf::Credit
119
119
  # @param loan [Dscf::Credit::Loan] The loan to generate accruals for
120
120
  # @return [Array<Hash>] Array of accrual details created
121
121
  def generate_accruals_for_loan(loan)
122
- return [] if accrual_exists_for_date?(loan) && !force_regenerate
123
-
124
122
  accruals = []
125
123
 
126
124
  # Generate interest accrual
127
125
  if loan.remaining_amount > 0
128
- interest_accrual = generate_interest_accrual(loan)
129
- accruals << interest_accrual if interest_accrual
126
+ unless accrual_exists_for_date?(loan, "interest") && !force_regenerate
127
+ interest_accrual = generate_interest_accrual(loan)
128
+ accruals << interest_accrual if interest_accrual
129
+ end
130
130
  end
131
131
 
132
132
  # Generate penalty accrual if overdue
133
133
  if loan_is_overdue?(loan)
134
- penalty_accrual = generate_penalty_accrual(loan)
135
- accruals << penalty_accrual if penalty_accrual
134
+ unless accrual_exists_for_date?(loan, "penalty") && !force_regenerate
135
+ penalty_accrual = generate_penalty_accrual(loan)
136
+ accruals << penalty_accrual if penalty_accrual
137
+ end
136
138
  end
137
139
 
138
140
  accruals
139
141
  end
140
142
 
141
- # Check if accruals already exist for the loan on the given date
143
+ # Check if a specific type of accrual already exists for the loan on the given date
144
+ #
145
+ # This method checks per accrual type (interest or penalty) rather than both together.
146
+ # This ensures that an existing penalty accrual doesn't block an interest accrual,
147
+ # and vice versa, making the method more precise and flexible.
148
+ #
149
+ # Note: Only checks for interest and penalty accruals, not facilitation fees,
150
+ # since facilitation fees are generated during disbursement and should not
151
+ # prevent daily interest/penalty generation.
142
152
  #
143
153
  # @param loan [Dscf::Credit::Loan] The loan to check
144
- # @return [Boolean] True if accruals exist for the date
145
- def accrual_exists_for_date?(loan)
146
- loan.loan_accruals.where(applied_on: accrual_date).exists?
154
+ # @param accrual_type [String] The type of accrual to check ('interest' or 'penalty')
155
+ # @return [Boolean] True if the specified accrual type exists for the date
156
+ def accrual_exists_for_date?(loan, accrual_type)
157
+ loan.loan_accruals
158
+ .where(applied_on: accrual_date)
159
+ .where(accrual_type: accrual_type)
160
+ .exists?
147
161
  end
148
162
 
149
163
  # Generate interest accrual for a loan
@@ -261,25 +261,32 @@ module Dscf::Credit
261
261
  # Only runs if loan.status == "paid"
262
262
  #
263
263
  # Process:
264
- # 1. Find all locked eligible credit lines (locked during disbursement)
265
- # 2. For each locked line, recalculate available_limit based on:
266
- # - Credit limit (total allowed)
267
- # - Minus current usage from other active loans
268
- # 3. Unlock the credit line by setting locked: false
264
+ # 1. Find the eligible credit line used for this loan
265
+ # 2. Reset available_limit to credit_limit, applying risk if present
266
+ # 3. Find all other locked eligible credit lines (locked during disbursement)
267
+ # 4. Unlock them by setting locked: false
269
268
  #
270
269
  # This allows the borrower to access credit again after repayment.
271
270
  #
272
271
  # @return [void]
273
272
  #
274
273
  # @example
275
- # # Before: eligible_line.locked = true
276
- # # After payment: eligible_line.locked = false
274
+ # # Before: eligible_line.locked = true, available_limit = 50000
275
+ # # After payment: eligible_line.locked = false, available_limit = 100000 (or adjusted for risk)
277
276
  def reactivate_facilities_if_paid_off
278
277
  return unless loan.status == "paid"
279
278
 
280
279
  loan_profile = loan.loan_profile
281
280
  credit_line = loan.credit_line
282
281
 
282
+ # Reset available limit for the eligible credit line used for this loan
283
+ eligible_credit_line = loan_profile.eligible_credit_lines
284
+ .find_by(credit_line: credit_line)
285
+
286
+ if eligible_credit_line
287
+ reset_available_limit(eligible_credit_line)
288
+ end
289
+
283
290
  # Unlock other eligible credit lines that were locked during disbursement
284
291
  loan_profile.eligible_credit_lines
285
292
  .where.not(credit_line: credit_line)
@@ -287,6 +294,57 @@ module Dscf::Credit
287
294
  .update_all(locked: false)
288
295
  end
289
296
 
297
+ # Reset available limit for an eligible credit line, applying risk if present
298
+ #
299
+ # If risk > 0, the available limit is reduced by the risk percentage
300
+ # Formula: available_limit = credit_limit * (1 - risk)
301
+ #
302
+ # Also recalculates and updates the loan profile's total_limit to reflect
303
+ # the current state of all eligible credit lines.
304
+ #
305
+ # @param eligible_credit_line [Dscf::Credit::EligibleCreditLine]
306
+ # @return [void]
307
+ #
308
+ # @example Without risk
309
+ # # risk = 0 or nil
310
+ # # available_limit = credit_limit (100000)
311
+ #
312
+ # @example With risk
313
+ # # risk = 0.1 (10%)
314
+ # # available_limit = credit_limit * (1 - 0.1) = 100000 * 0.9 = 90000
315
+ def reset_available_limit(eligible_credit_line)
316
+ credit_limit = eligible_credit_line.credit_limit
317
+ risk = eligible_credit_line.risk || 0
318
+
319
+ if risk > 0
320
+ available_limit = credit_limit * (1 - risk)
321
+ else
322
+ available_limit = credit_limit
323
+ end
324
+
325
+ eligible_credit_line.update!(available_limit: available_limit)
326
+
327
+ # Recalculate loan_profile total_limit after updating available_limit
328
+ update_loan_profile_total_limit(eligible_credit_line.loan_profile)
329
+ end
330
+
331
+ # Recalculate and update loan profile's total_limit
332
+ #
333
+ # Sums all available_limit values from the loan profile's eligible credit lines
334
+ # and updates the loan_profile.total_limit to reflect the current state.
335
+ #
336
+ # @param loan_profile [Dscf::Credit::LoanProfile]
337
+ # @return [void]
338
+ def update_loan_profile_total_limit(loan_profile)
339
+ return unless loan_profile
340
+
341
+ total_available_limit = loan_profile.eligible_credit_lines.sum(:available_limit)
342
+
343
+ loan_profile.update!(total_limit: total_available_limit)
344
+
345
+ Rails.logger.info "Updated loan profile #{loan_profile.id} total limit to #{total_available_limit}"
346
+ end
347
+
290
348
  # Build success result hash
291
349
  #
292
350
  # @param allocation [Hash] The allocation hash with payment details
@@ -1,5 +1,5 @@
1
1
  module Dscf
2
2
  module Credit
3
- VERSION = "0.2.9"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dscf-credit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adoniyas