loan_creator 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 838d0d280c585125b013b8ea6d7e8b58e94c2234fffe3d49d1c97f39a67b254f
4
- data.tar.gz: 7a607035130b53af6678bdec2a91b9d99fcace0a1710db1ef83333a4b15133b0
3
+ metadata.gz: 3f7d2dcd74aa8a01bb25577eaf1c6ea05daba9eef071d76119d7ad00773fe0f7
4
+ data.tar.gz: ed40fe1ff5610cbef9bc5c5c92f31095a0eaa6ad745a8f194861e65d5b8e316b
5
5
  SHA512:
6
- metadata.gz: 06c8e0dfa135c8a80f9969f60db83a8bc3630161fea4f208dff6480ab9142635d196aaa729fdb32fd89e38e87804cbc76fcc1f10ec464c680c10219ef7aec23c
7
- data.tar.gz: c710e04d68debd3d7f63569586aed46ac1426aabf9ed995fa762a18745916d1f31339f93b173159ddd61dcf23b145527471dbc7ff873a1bdfc46169e310f4322
6
+ metadata.gz: 16256ac04047a1b505e37dddec9299d5409d1940dfe2d7271ebc0cc5207bfb5301f3375fbf3f793cb5a70dbacab3e5a7c3c6bd87d07c6449faeba2dd73368f71
7
+ data.tar.gz: 4fe77879f1dfa61adecaf5d30dcf2dd5d6c501c1d82b75232cd106ffa827c440d52326a37c25bc939f445010ab7c4d256527e92edef2124d3c6ed8369337c0eb
@@ -24,7 +24,7 @@ module LoanCreator
24
24
  def compute_last_term(timetable)
25
25
  @crd_end_of_period = bigd('0')
26
26
  @due_interests_beginning_of_period = @due_interests_end_of_period
27
- @period_interests = @due_interests_end_of_period + compute_capitalized_interests(@due_on, timetable)
27
+ @period_interests = @due_interests_end_of_period + compute_capitalized_interests(timetable)
28
28
  @due_interests_end_of_period = 0
29
29
  @period_capital = @crd_beginning_of_period
30
30
  @total_paid_capital_end_of_period += @period_capital
@@ -32,14 +32,22 @@ module LoanCreator
32
32
  @period_amount_to_pay = @period_capital + @period_interests
33
33
  end
34
34
 
35
- def compute_capitalized_interests(due_date, timetable)
36
- computed_periodic_interests_rate = periodic_interests_rate(due_date, relative_to_date: timetable_term_dates[timetable.next_index - 1])
37
- (amount + @due_interests_beginning_of_period).mult(computed_periodic_interests_rate, BIG_DECIMAL_DIGITS)
35
+ def compute_capitalized_interests(timetable)
36
+ if multi_part_interests_calculation && term_dates? && (timetable_term_dates[timetable.current_index] + 1.year) < @due_on
37
+ multi_part_interests(
38
+ timetable_term_dates[timetable.current_index],
39
+ @due_on,
40
+ annual_interests_rate,
41
+ amount_to_capitalize
42
+ )
43
+ else
44
+ compute_period_generated_interests(periodic_interests_rate(timetable_term_dates[timetable.current_index], @due_on))
45
+ end
38
46
  end
39
47
 
40
48
  def compute_term(timetable)
41
49
  @due_interests_beginning_of_period = @due_interests_end_of_period
42
- @due_interests_end_of_period += compute_capitalized_interests(@due_on, timetable)
50
+ @due_interests_end_of_period += compute_capitalized_interests(timetable)
43
51
  end
44
52
  end
45
53
  end
@@ -1,6 +1,7 @@
1
1
  module LoanCreator
2
2
  class Common
3
3
  extend BorrowerTimetable
4
+ include TimeHelper
4
5
 
5
6
  PERIODS_IN_MONTHS = {
6
7
  month: 1,
@@ -30,9 +31,11 @@ module LoanCreator
30
31
  deferred_in_periods: 0,
31
32
  interests_start_date: nil,
32
33
  initial_values: {},
33
- realistic_durations: false
34
+ realistic_durations: false,
35
+ multi_part_interests_calculation: true
34
36
  }.freeze
35
37
 
38
+
36
39
  attr_reader *REQUIRED_ATTRIBUTES
37
40
  attr_reader *OPTIONAL_ATTRIBUTES.keys
38
41
 
@@ -47,9 +50,9 @@ module LoanCreator
47
50
  prepare_custom_term_dates if term_dates?
48
51
  end
49
52
 
50
- def periodic_interests_rate(date = nil, relative_to_date: nil)
53
+ def periodic_interests_rate(start_date, end_date)
51
54
  if realistic_durations?
52
- compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date: relative_to_date).div(100, BIG_DECIMAL_DIGITS)
55
+ compute_realistic_periodic_interests_rate(start_date, end_date, annual_interests_rate)
53
56
  else
54
57
  @periodic_interests_rate ||=
55
58
  annual_interests_rate.div(12 / PERIODS_IN_MONTHS[period], BIG_DECIMAL_DIGITS).div(100, BIG_DECIMAL_DIGITS)
@@ -101,7 +104,7 @@ module LoanCreator
101
104
 
102
105
  def set_attributes
103
106
  required_attributes.each { |k| instance_variable_set(:"@#{k}", @options.fetch(k)) }
104
- OPTIONAL_ATTRIBUTES.each { |k,v| instance_variable_set(:"@#{k}", @options.fetch(k, v)) }
107
+ OPTIONAL_ATTRIBUTES.each { |k, v| instance_variable_set(:"@#{k}", @options.fetch(k, v)) }
105
108
  end
106
109
 
107
110
  def validate(key, &block)
@@ -236,43 +239,6 @@ module LoanCreator
236
239
  (interests_start_date && interests_start_date < term_zero_date) && !term_dates?
237
240
  end
238
241
 
239
- def leap_days_count(date, relative_to_date:)
240
- start_year = relative_to_date.year
241
- end_year = date.year
242
-
243
- (start_year..end_year).sum do |year|
244
- next 0 unless Date.gregorian_leap?(year)
245
-
246
- start_date =
247
- if start_year == year
248
- relative_to_date
249
- else
250
- Date.new(year - 1, 12, 31)
251
- end
252
-
253
- end_date =
254
- if end_year == year
255
- date
256
- else
257
- Date.new(year, 12, 31)
258
- end
259
-
260
- end_date - start_date
261
- end
262
- end
263
-
264
- def compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date:)
265
- total_days = date - relative_to_date
266
- leap_days = bigd(leap_days_count(date, relative_to_date: relative_to_date))
267
- non_leap_days = bigd(total_days - leap_days)
268
-
269
- annual_interests_rate.mult(
270
- leap_days.div(366, BIG_DECIMAL_DIGITS) +
271
- non_leap_days.div(365, BIG_DECIMAL_DIGITS),
272
- BIG_DECIMAL_DIGITS
273
- )
274
- end
275
-
276
242
  def realistic_durations?
277
243
  term_dates? || @realistic_durations.present?
278
244
  end
@@ -301,7 +267,11 @@ module LoanCreator
301
267
  end
302
268
 
303
269
  def compute_period_generated_interests(interests_rate)
304
- (@crd_beginning_of_period + @due_interests_beginning_of_period).mult(interests_rate, BIG_DECIMAL_DIGITS)
270
+ amount_to_capitalize.mult(interests_rate, BIG_DECIMAL_DIGITS)
271
+ end
272
+
273
+ def amount_to_capitalize
274
+ @crd_beginning_of_period + @due_interests_beginning_of_period
305
275
  end
306
276
  end
307
277
  end
@@ -23,12 +23,25 @@ module LoanCreator
23
23
  @last_period = last_period?(idx)
24
24
  @deferred_period = @index <= deferred_in_periods
25
25
  @due_on = timetable_term_dates[timetable.next_index]
26
- computed_periodic_interests_rate = periodic_interests_rate(@due_on, relative_to_date: timetable_term_dates[timetable.next_index - 1])
27
26
 
28
27
  # Reminder: CRD beginning of period = CRD end of period **of previous period**
29
28
  @crd_beginning_of_period = @crd_end_of_period
30
29
  @due_interests_beginning_of_period = @due_interests_end_of_period
31
- @period_theoric_interests = period_theoric_interests(computed_periodic_interests_rate)
30
+
31
+ @period_theoric_interests = (
32
+ # if period is more than a year
33
+ if multi_part_interests_calculation && term_dates? && (timetable_term_dates[timetable.current_index] + 1.year) < @due_on
34
+ multi_part_interests(
35
+ timetable_term_dates[timetable.current_index],
36
+ @due_on,
37
+ annual_interests_rate,
38
+ amount_to_capitalize
39
+ )
40
+ else
41
+ period_theoric_interests(periodic_interests_rate(timetable_term_dates[timetable.current_index], @due_on))
42
+ end
43
+ )
44
+
32
45
  @delta_interests = @period_theoric_interests - @period_theoric_interests.round(2)
33
46
  @accrued_delta_interests += @delta_interests
34
47
  @amount_to_add = bigd(
@@ -24,7 +24,7 @@ module LoanCreator
24
24
  @last_period = last_period?(idx)
25
25
  @deferred_period = @index <= deferred_in_periods
26
26
  @due_on = timetable_term_dates[timetable.next_index]
27
- computed_periodic_interests_rate = periodic_interests_rate(@due_on, relative_to_date: timetable_term_dates[timetable.next_index - 1])
27
+ computed_periodic_interests_rate = periodic_interests_rate(timetable_term_dates[timetable.current_index], @due_on)
28
28
 
29
29
  @crd_beginning_of_period = @crd_end_of_period
30
30
  @period_theoric_interests = period_theoric_interests(@index, computed_periodic_interests_rate)
@@ -0,0 +1,62 @@
1
+ module LoanCreator
2
+ module TimeHelper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def leap_days_count(start_date, end_date)
7
+ start_year = start_date.year
8
+ # mostly no op but allows to skip one iteration if end date is january 1st
9
+ end_year = (end_date - 1.day).year
10
+
11
+ (start_year..end_year).sum do |year|
12
+ next 0 unless Date.gregorian_leap?(year)
13
+
14
+ current_start_date =
15
+ if start_year == year
16
+ start_date
17
+ else
18
+ Date.new(year, 1, 1)
19
+ end
20
+
21
+ current_end_date =
22
+ if end_year == year
23
+ end_date
24
+ else
25
+ Date.new(year + 1, 1, 1)
26
+ end
27
+
28
+ current_end_date - current_start_date
29
+ end
30
+ end
31
+
32
+ def compute_realistic_periodic_interests_rate(start_date, end_date, annual_interests_rate)
33
+ total_days = end_date - start_date
34
+ leap_days = bigd(leap_days_count(start_date, end_date))
35
+ non_leap_days = bigd(total_days - leap_days)
36
+
37
+ annual_interests_rate.mult(
38
+ leap_days.div(366, BIG_DECIMAL_DIGITS) +
39
+ non_leap_days.div(365, BIG_DECIMAL_DIGITS),
40
+ BIG_DECIMAL_DIGITS
41
+ ).div(100, BIG_DECIMAL_DIGITS)
42
+ end
43
+
44
+ # for terms spanning more than a year,
45
+ # we capitalize each years until the last one which behaves normally
46
+ def multi_part_interests(start_date, end_date, annual_interests_rate, amount_to_capitalize)
47
+ duration_in_days = end_date - start_date
48
+ leap_days = bigd(leap_days_count(start_date, end_date))
49
+ non_leap_days = bigd(duration_in_days - leap_days)
50
+
51
+ ratio = non_leap_days.div(365, BIG_DECIMAL_DIGITS) + leap_days.div(366, BIG_DECIMAL_DIGITS)
52
+ full_years, year_part = ratio.divmod(1)
53
+ rate = annual_interests_rate.div(100, BIG_DECIMAL_DIGITS)
54
+
55
+ total = amount_to_capitalize.mult((1 + rate)**full_years, BIG_DECIMAL_DIGITS)
56
+ .mult(1 + rate * year_part, BIG_DECIMAL_DIGITS)
57
+
58
+ total - amount_to_capitalize
59
+ end
60
+ end
61
+ end
62
+ end
@@ -40,5 +40,9 @@ module LoanCreator
40
40
  def next_index
41
41
  @current_index.nil? ? @starting_index : @current_index + 1
42
42
  end
43
+
44
+ def current_index
45
+ @current_index.nil? ? @starting_index - 1 : @current_index
46
+ end
43
47
  end
44
48
  end
@@ -33,7 +33,7 @@ module LoanCreator
33
33
  end
34
34
 
35
35
  def compute_interests(due_date, timetable)
36
- computed_periodic_interests_rate = periodic_interests_rate(due_date, relative_to_date: timetable_term_dates[timetable.next_index - 1])
36
+ computed_periodic_interests_rate = periodic_interests_rate(timetable_term_dates[timetable.current_index], due_date)
37
37
  amount.mult(bigd(computed_periodic_interests_rate), BIG_DECIMAL_DIGITS)
38
38
  end
39
39
 
@@ -1,3 +1,3 @@
1
1
  module LoanCreator
2
- VERSION = '0.10.0'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
data/lib/loan_creator.rb CHANGED
@@ -8,6 +8,7 @@ module LoanCreator
8
8
  BIG_DECIMAL_DIGITS = 14
9
9
 
10
10
  autoload :ExcelFormulas, 'loan_creator/excel_formulas'
11
+ autoload :TimeHelper, 'loan_creator/time_helper'
11
12
  autoload :BorrowerTimetable, 'loan_creator/borrower_timetable'
12
13
  autoload :Common, 'loan_creator/common'
13
14
  autoload :Standard, 'loan_creator/standard'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loan_creator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thibaulth
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2021-05-26 00:00:00.000000000 Z
15
+ date: 2021-11-03 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bundler
@@ -178,6 +178,7 @@ files:
178
178
  - lib/loan_creator/standard.rb
179
179
  - lib/loan_creator/term.rb
180
180
  - lib/loan_creator/term_dates_validator.rb
181
+ - lib/loan_creator/time_helper.rb
181
182
  - lib/loan_creator/timetable.rb
182
183
  - lib/loan_creator/uncapitalized_bullet.rb
183
184
  - lib/loan_creator/version.rb