loan_creator 0.6.2 → 0.8.1

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
- SHA1:
3
- metadata.gz: 8dff90f00c1b47c09fd2ac97d4a0e3a28e12fbd5
4
- data.tar.gz: '078e7ad135c712ef0fb767208cc99557718b99b1'
2
+ SHA256:
3
+ metadata.gz: 4dcaafbce29a00ebcdea57133619e566cc210be6ed613003af14e0a199cc126a
4
+ data.tar.gz: 2da6fe223cfc40fd8455a5c1ec0d8ef857bfd48effb22b285f46da7c3800a81b
5
5
  SHA512:
6
- metadata.gz: e318e46a05d49caa450705cb5a6e234c7b3e497dad501961c1339638c0641cc76f88dafb00f07af80b9b9cdc6d5ba1d0b5e761133f863f7a6034d479ddb0f012
7
- data.tar.gz: bc258a7bd58a783c033c7c81857b467966e2e4ee25b091cc73cf1de1f5ec94a94f889fb2c32f5be072668503295564a841beb0573585c509b867dc9b0025cbe1
6
+ metadata.gz: 215f9a9b34c5ac6f9eef09f8658bbcd7c322e1964f0d4a0c9f55d3f97c99d1c9fc804c9e69f2c5509085bca9ec6ed848e92561ce456d6348d7b5ea343e38ada6
7
+ data.tar.gz: 98b6cf146fd802777c156266cd9378d8816ef00d4c4ab8aafdc092f4b52b001e2b8862d61a260502803f888f3b7f0f1b34bea5e29ea4a5843f8b01b5d99ce729
@@ -0,0 +1,45 @@
1
+ version: 2.1
2
+ orbs:
3
+ ruby: circleci/ruby@0.1.2
4
+
5
+ jobs:
6
+ build:
7
+ docker:
8
+ - image: circleci/ruby:2.7-node
9
+ executor: ruby/default
10
+ steps:
11
+ - checkout
12
+ - run:
13
+ name: Bundle install
14
+ command: |
15
+ gem install bundler:2.1.4
16
+ bundle install --path vendor/bundle
17
+ - run:
18
+ name: Test
19
+ command: bundle exec rspec
20
+ - run:
21
+ name: Coding style
22
+ command: |
23
+ gem install rubocop
24
+ mkdir -p rubocop
25
+ rubocop --format html -o rubocop/rubocop.html || true
26
+ - run:
27
+ name: Notation
28
+ command: |
29
+ gem install sexp_processor -v 4.13.0
30
+ gem install rubycritic -v 4.3.2
31
+ rubycritic app --no-browser || true
32
+ - run:
33
+ name: "Axe d'optimisation"
34
+ command: |
35
+ gem install fasterer
36
+ fasterer || true
37
+ - run:
38
+ name: "Audit Securité du Gemfile"
39
+ command: |
40
+ gem install bundler-audit
41
+ bundle audit --update
42
+ - store_artifacts:
43
+ path: rubocop/rubocop.html
44
+ - store_artifacts:
45
+ path: tmp/rubycritic
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ TargetRubyVersion: 2.7
3
3
  Exclude:
4
4
  - 'db/**/*'
5
5
  - 'vendor/**/*'
@@ -12,7 +12,7 @@ Metrics/BlockLength:
12
12
  Exclude:
13
13
  - spec/**/*
14
14
 
15
- Metrics/LineLength:
15
+ Layout/LineLength:
16
16
  Max: 120
17
17
 
18
18
  Metrics/MethodLength:
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.3.7
1
+ ruby-2.7.1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ v0.8.1
2
+ -------------------------
3
+ - add `realistic_durations` option
4
+
5
+ v0.8.0
6
+ -------------------------
7
+
8
+ #### Feature
9
+ - due interests to date are now store into `due_interests_` columns for `LoanCreator::UncapitalizedBullet`
10
+
11
+ #### Bugfix
12
+ - fix `initial_values` not taking `paid_capital` and `paid_interests` into account for `LoanCreator::UncapitalizedBullet`
13
+ and `LoanCreator::Bullet`
14
+
15
+ #### Breaking changes
16
+ - rename columns `capitalized_interests_beginning_of_period` and `capitalized_interests_end_of_period` for
17
+ `due_interests_beginning_of_period` and `due_interests_end_of_period`
18
+ - rename `capitalized_interests` option in `:inital_values` into `due_interests`
19
+
20
+
21
+ v0.7.1
22
+ -------------------------
23
+
24
+ - fix `index` for `borrower_timetable` method that did not take into account starting index of `lender_timetables`
25
+
26
+ v0.7.0
27
+ -------------------------
28
+
29
+ - change `capitalized_interests` for `capitalized_interests_beginning_of_period`
30
+ and `capitalized_interests_end_of_period` in `LoanCreator::Term`
31
+ - add `capitalized_interests` in `:inital_values` for `LoanCreator::Bullet` loans
32
+
1
33
  v0.6.2
2
34
  -------------------------
3
35
 
data/README.md CHANGED
@@ -44,9 +44,10 @@ Each instance of one of the previous classes has the following attributes:
44
44
  :annual_interests_rate
45
45
  :starts_at
46
46
  :duration_in_periods
47
- :deferred_in_periods (default to zero)
47
+ :deferred_in_periods (defaults to zero)
48
48
  :interests_start_date (optional)
49
49
  :initial_values (to generate a timetable from a previous term or at a given state)
50
+ :realistic_durations (optional, defaults to false)
50
51
  ```
51
52
 
52
53
  Initial values must be a hash with specific keys, like so:
@@ -57,7 +58,7 @@ Initial values must be a hash with specific keys, like so:
57
58
  paid_interests: 11000.0,
58
59
  accrued_delta_interests: 0,
59
60
  starting_index: 2,
60
- capitalized_interests: 0
61
+ due_interests: 0
61
62
  }
62
63
  ```
63
64
 
@@ -102,6 +103,9 @@ There is also a `LoanCreator::Timetable` class dedicated to record the data of t
102
103
 
103
104
  # Amount to pay this term
104
105
  :period_amount_to_pay
106
+
107
+ # Whether or not to use the real number of days in each month
108
+ :realistic_durations
105
109
  ```
106
110
 
107
111
  `#periodic_interests_rate` renders a precise calculation of the loan's periodic interests rate based on two inputs: `#annual_interests_rate` and `#period`.
@@ -148,9 +152,11 @@ Capital share shall be repaid in full at loan's end.
148
152
  `Bullet` loan generates terms where terms' payments are zero. \
149
153
  Interests are capitalized, i.e. added to the borrowed capital on each term.\
150
154
  Capital share shall be repaid in full and all interests paid at loan's end.
155
+ N.b.: Due capitalized interests to date are stored into terms under `due_intesrests_` columns
151
156
 
152
157
  `UncapitalizedBullet` same as bullet, the only difference is the interests\
153
158
  are NOT capitalized.
159
+ N.b.: Due interests to date are stored into terms under `due_intesrests_` columns
154
160
 
155
161
  There is no deferred time for `InFine` and `Bullet` loans as it would be equivalent to increasing loan's duration.
156
162
 
@@ -174,6 +180,9 @@ additional term with only interests for the time difference.
174
180
  For example, with a `start_at` in january 2020 and a `interests_start_date` in october 2019, the timetable will include a
175
181
  first term corresponding to 3 months of interests.
176
182
 
183
+ `realistic_durations`: Optional. A boolean tha specifies whether or not to use the real number of days in each month when calculating the periodic interests rate. Note that leap years are taken into account. Default: `false`.
184
+ The default behaviour is to use the `period` in relation to the number of months in a year (ie: 12)
185
+
177
186
  ## Calculation
178
187
 
179
188
  An excel simulator for standard case can be found [here](CapSens_Loan.xlsx).
@@ -7,6 +7,8 @@ module LoanCreator
7
7
  :period_capital,
8
8
  :total_paid_capital_end_of_period,
9
9
  :total_paid_interests_end_of_period,
10
+ :due_interests_beginning_of_period,
11
+ :due_interests_end_of_period,
10
12
  :period_amount_to_pay
11
13
  ].freeze
12
14
 
@@ -18,8 +20,8 @@ module LoanCreator
18
20
  end
19
21
 
20
22
  borrower_timetable = LoanCreator::Timetable.new(
21
- starts_on: lenders_timetables.first.starts_on,
22
- period: lenders_timetables.first.period
23
+ starting_index: lenders_timetables.first.starting_index,
24
+ loan: lenders_timetables.first.loan
23
25
  )
24
26
 
25
27
  # Borrower timetable is not concerned with computation-related value (delta, etc.),
@@ -34,7 +36,7 @@ module LoanCreator
34
36
  term = BORROWER_FINANCIAL_ATTRIBUTES.each_with_object({}) do |k, h|
35
37
  h[k] = arr.inject(bigd('0')) { |sum, tt| sum + tt.send(k) }
36
38
  end
37
- borrower_timetable << LoanCreator::Term.new(all_zero.merge(term))
39
+ borrower_timetable << LoanCreator::Term.new(**all_zero.merge(term))
38
40
  end
39
41
  borrower_timetable
40
42
  end
@@ -7,31 +7,39 @@ module LoanCreator
7
7
  reset_current_term
8
8
  @crd_beginning_of_period = amount
9
9
  @crd_end_of_period = amount
10
- (duration_in_periods - 1).times { |period| compute_term(timetable, period + 1) }
11
- compute_last_term
12
- timetable << current_term
10
+
11
+ duration_in_periods.times { |idx| timetable << compute_current_term(idx, timetable) }
12
+
13
13
  timetable
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def compute_last_term
18
+ def compute_current_term(idx, timetable)
19
+ @due_on = timetable_term_dates[timetable.next_index]
20
+ last_period?(idx) ? compute_last_term(timetable) : compute_term(timetable)
21
+ current_term
22
+ end
23
+
24
+ def compute_last_term(timetable)
19
25
  @crd_end_of_period = bigd('0')
20
- @period_interests = compute_capitalized_interests(duration_in_periods)
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)
28
+ @due_interests_end_of_period = 0
21
29
  @period_capital = @crd_beginning_of_period
22
- @total_paid_capital_end_of_period = @period_capital
23
- @total_paid_interests_end_of_period = @period_interests
30
+ @total_paid_capital_end_of_period += @period_capital
31
+ @total_paid_interests_end_of_period += @period_interests
24
32
  @period_amount_to_pay = @period_capital + @period_interests
25
- @capitalized_interests = compute_capitalized_interests(duration_in_periods)
26
33
  end
27
34
 
28
- def compute_capitalized_interests(period)
29
- amount.mult((bigd(1) + periodic_interests_rate) ** period, BIG_DECIMAL_DIGITS) - amount
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)
30
38
  end
31
39
 
32
- def compute_term(timetable, period)
33
- @capitalized_interests = compute_capitalized_interests(period)
34
- timetable << current_term
40
+ def compute_term(timetable)
41
+ @due_interests_beginning_of_period = @due_interests_end_of_period
42
+ @due_interests_end_of_period += compute_capitalized_interests(@due_on, timetable)
35
43
  end
36
44
  end
37
45
  end
@@ -21,7 +21,8 @@ module LoanCreator
21
21
  # attribute: default_value
22
22
  deferred_in_periods: 0,
23
23
  interests_start_date: nil,
24
- initial_values: {}
24
+ initial_values: {},
25
+ realistic_durations: false
25
26
  }.freeze
26
27
 
27
28
  attr_reader *REQUIRED_ATTRIBUTES
@@ -37,14 +38,26 @@ module LoanCreator
37
38
  validate_initial_values
38
39
  end
39
40
 
40
- def periodic_interests_rate_percentage
41
- @periodic_interests_rate_percentage ||=
42
- annual_interests_rate.div(12 / PERIODS_IN_MONTHS[period], BIG_DECIMAL_DIGITS)
41
+ def periodic_interests_rate(date = nil, relative_to_date: nil)
42
+ if realistic_durations?
43
+ compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date: relative_to_date).div(100, BIG_DECIMAL_DIGITS)
44
+ else
45
+ @periodic_interests_rate ||=
46
+ annual_interests_rate.div(12 / PERIODS_IN_MONTHS[period], BIG_DECIMAL_DIGITS).div(100, BIG_DECIMAL_DIGITS)
47
+ end
43
48
  end
44
49
 
45
- def periodic_interests_rate
46
- @periodic_interests_rate ||=
47
- periodic_interests_rate_percentage.div(100, BIG_DECIMAL_DIGITS)
50
+ def timetable_term_dates
51
+ @_timetable_term_dates ||= Hash.new do |dates, index|
52
+ dates[index] =
53
+ if index < 1
54
+ dates[index + 1].advance(months: -PERIODS_IN_MONTHS.fetch(period))
55
+ elsif index == 1
56
+ starts_on
57
+ else
58
+ starts_on.advance(months: PERIODS_IN_MONTHS.fetch(period) * (index - 1))
59
+ end
60
+ end
48
61
  end
49
62
 
50
63
  def lender_timetable
@@ -110,22 +123,24 @@ module LoanCreator
110
123
  (@total_paid_capital_end_of_period = bigd(initial_values[:paid_capital]))
111
124
  (@total_paid_interests_end_of_period = bigd(initial_values[:paid_interests]))
112
125
  (@accrued_delta_interests = bigd(initial_values[:accrued_delta_interests]))
126
+ (@due_interests_beginning_of_period = bigd(initial_values[:due_interests] || 0))
113
127
  end
114
128
 
115
129
  def reset_current_term
116
- @crd_beginning_of_period = bigd('0')
117
- @crd_end_of_period = bigd('0')
118
- @period_theoric_interests = bigd('0')
119
- @capitalized_interests = bigd('0')
120
- @delta_interests = bigd('0')
121
- @accrued_delta_interests = @accrued_delta_interests || bigd('0')
122
- @amount_to_add = bigd('0')
123
- @period_interests = bigd('0')
124
- @period_capital = bigd('0')
125
- @total_paid_capital_end_of_period = @total_paid_capital_end_of_period || bigd('0')
126
- @total_paid_interests_end_of_period = @total_paid_interests_end_of_period || bigd('0')
127
- @period_amount_to_pay = bigd('0')
128
- @due_on = nil
130
+ @accrued_delta_interests ||= bigd('0')
131
+ @total_paid_capital_end_of_period ||= bigd('0')
132
+ @total_paid_interests_end_of_period ||= bigd('0')
133
+ @due_interests_beginning_of_period ||= bigd('0')
134
+ @crd_beginning_of_period = bigd('0')
135
+ @crd_end_of_period = bigd('0')
136
+ @period_theoric_interests = bigd('0')
137
+ @due_interests_end_of_period = @due_interests_beginning_of_period
138
+ @delta_interests = bigd('0')
139
+ @amount_to_add = bigd('0')
140
+ @period_interests = bigd('0')
141
+ @period_capital = bigd('0')
142
+ @period_amount_to_pay = bigd('0')
143
+ @due_on = nil
129
144
  end
130
145
 
131
146
  def current_term
@@ -135,7 +150,8 @@ module LoanCreator
135
150
  period_theoric_interests: @period_theoric_interests,
136
151
  delta_interests: @delta_interests,
137
152
  accrued_delta_interests: @accrued_delta_interests,
138
- capitalized_interests: @capitalized_interests,
153
+ due_interests_beginning_of_period: @due_interests_beginning_of_period,
154
+ due_interests_end_of_period: @due_interests_end_of_period,
139
155
  amount_to_add: @amount_to_add,
140
156
  period_interests: @period_interests,
141
157
  period_capital: @period_capital,
@@ -149,8 +165,7 @@ module LoanCreator
149
165
 
150
166
  def new_timetable
151
167
  LoanCreator::Timetable.new(
152
- starts_on: starts_on,
153
- period: period,
168
+ loan: self,
154
169
  interests_start_date: interests_start_date,
155
170
  starting_index: @starting_index
156
171
  )
@@ -160,6 +175,10 @@ module LoanCreator
160
175
  @index ? (@starting_index + @index - 1) : nil
161
176
  end
162
177
 
178
+ def last_period?(idx)
179
+ idx == (duration_in_periods - 1)
180
+ end
181
+
163
182
  def compute_term_zero
164
183
  @crd_beginning_of_period = @crd_end_of_period
165
184
  @period_theoric_interests = term_zero_interests
@@ -169,6 +188,7 @@ module LoanCreator
169
188
  @total_paid_interests_end_of_period += @period_interests
170
189
  @period_amount_to_pay = @period_interests
171
190
  @index = 0
191
+ @due_on = timetable_term_dates[0]
172
192
  end
173
193
 
174
194
  def term_zero_interests
@@ -191,5 +211,17 @@ module LoanCreator
191
211
  def term_zero?
192
212
  interests_start_date && interests_start_date < term_zero_date
193
213
  end
214
+
215
+ def compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date:)
216
+ realistic_days = 365
217
+ realistic_days += 1 if date.leap?
218
+ realistic_days_in_period = (date - relative_to_date).to_i
219
+
220
+ annual_interests_rate.div(bigd(realistic_days) / bigd(realistic_days_in_period), BIG_DECIMAL_DIGITS)
221
+ end
222
+
223
+ def realistic_durations?
224
+ !!@realistic_durations
225
+ end
194
226
  end
195
227
  end
@@ -4,7 +4,7 @@ module LoanCreator
4
4
  # Thus we're generating a Linear loan instead of rewriting already existing code.
5
5
  def lender_timetable
6
6
  options = @options.merge(deferred_in_periods: duration_in_periods - 1)
7
- LoanCreator::Linear.new(options).lender_timetable
7
+ LoanCreator::Linear.new(**options).lender_timetable
8
8
  end
9
9
  end
10
10
  end
@@ -11,26 +11,23 @@ module LoanCreator
11
11
  timetable << current_term
12
12
  end
13
13
 
14
- duration_in_periods.times do |idx|
15
- @last_period = last_period?(idx)
16
- @deferred_period = idx < deferred_in_periods
17
- compute_current_term(idx)
18
- timetable << current_term
19
- end
14
+ duration_in_periods.times { |idx| timetable << compute_current_term(idx, timetable) }
20
15
 
21
16
  timetable
22
17
  end
23
18
 
24
19
  private
25
20
 
26
- def last_period?(idx)
27
- idx == (duration_in_periods - 1)
28
- end
21
+ def compute_current_term(idx, timetable)
22
+ @index = idx + 1
23
+ @last_period = last_period?(idx)
24
+ @deferred_period = @index <= deferred_in_periods
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])
29
27
 
30
- def compute_current_term(idx)
31
28
  # Reminder: CRD beginning of period = CRD end of period **of previous period**
32
29
  @crd_beginning_of_period = @crd_end_of_period
33
- @period_theoric_interests = @crd_beginning_of_period * periodic_interests_rate
30
+ @period_theoric_interests = @crd_beginning_of_period * computed_periodic_interests_rate
34
31
  @delta_interests = @period_theoric_interests - @period_theoric_interests.round(2)
35
32
  @accrued_delta_interests += @delta_interests
36
33
  @amount_to_add = bigd(
@@ -49,8 +46,8 @@ module LoanCreator
49
46
  @total_paid_interests_end_of_period += @period_interests
50
47
  @period_amount_to_pay = @period_interests + @period_capital
51
48
  @crd_end_of_period -= @period_capital
52
- @due_on = nil
53
- @index = idx + 1
49
+
50
+ current_term
54
51
  end
55
52
 
56
53
  def period_capital
@@ -12,24 +12,22 @@ module LoanCreator
12
12
  timetable << current_term
13
13
  end
14
14
 
15
- duration_in_periods.times do |idx|
16
- @last_period = last_period?(idx)
17
- @deferred_period = idx < deferred_in_periods
18
- compute_current_term(idx)
19
- timetable << current_term
20
- end
15
+ duration_in_periods.times { |idx| timetable << compute_current_term(idx, timetable) }
16
+
21
17
  timetable
22
18
  end
23
19
 
24
20
  private
25
21
 
26
- def last_period?(idx)
27
- idx == (duration_in_periods - 1)
28
- end
22
+ def compute_current_term(idx, timetable)
23
+ @index = idx + 1
24
+ @last_period = last_period?(idx)
25
+ @deferred_period = @index <= deferred_in_periods
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])
29
28
 
30
- def compute_current_term(idx)
31
29
  @crd_beginning_of_period = @crd_end_of_period
32
- @period_theoric_interests = period_theoric_interests(idx)
30
+ @period_theoric_interests = period_theoric_interests(@index, computed_periodic_interests_rate)
33
31
  @delta_interests = @period_theoric_interests - @period_theoric_interests.round(2)
34
32
  @accrued_delta_interests += @delta_interests
35
33
  @amount_to_add = bigd(
@@ -43,37 +41,37 @@ module LoanCreator
43
41
  )
44
42
  @accrued_delta_interests -= @amount_to_add
45
43
  @period_interests = @period_theoric_interests.round(2) + @amount_to_add
46
- @period_capital = period_capital(idx)
44
+ @period_capital = period_capital(@index, computed_periodic_interests_rate)
47
45
  @total_paid_capital_end_of_period += @period_capital
48
46
  @total_paid_interests_end_of_period += @period_interests
49
47
  @period_amount_to_pay = @period_interests + @period_capital
50
48
  @crd_end_of_period -= @period_capital
51
- @due_on = nil
52
- @index = idx + 1
49
+
50
+ current_term
53
51
  end
54
52
 
55
- def period_theoric_interests(idx)
53
+ def period_theoric_interests(idx, computed_periodic_interests_rate)
56
54
  if @deferred_period
57
- @crd_beginning_of_period * periodic_interests_rate
55
+ @crd_beginning_of_period * computed_periodic_interests_rate
58
56
  else
59
57
  -ipmt(
60
- periodic_interests_rate,
61
- (idx + 1) - deferred_in_periods,
58
+ computed_periodic_interests_rate,
59
+ idx - deferred_in_periods,
62
60
  duration_in_periods - deferred_in_periods,
63
61
  amount
64
62
  )
65
63
  end
66
64
  end
67
65
 
68
- def period_capital(idx)
66
+ def period_capital(idx, computed_periodic_interests_rate)
69
67
  if @last_period
70
68
  @crd_beginning_of_period
71
69
  elsif @deferred_period
72
70
  bigd(0)
73
71
  else
74
72
  -ppmt(
75
- periodic_interests_rate,
76
- (idx + 1) - deferred_in_periods,
73
+ computed_periodic_interests_rate,
74
+ idx - deferred_in_periods,
77
75
  duration_in_periods - deferred_in_periods,
78
76
  amount
79
77
  ).round(2)
@@ -17,8 +17,11 @@ module LoanCreator
17
17
  # Accrued interests' delta
18
18
  :accrued_delta_interests,
19
19
 
20
- # Capitalized interests (Bullet only)
21
- :capitalized_interests,
20
+ # Due interests at the beginning of the term (Bullet and UncapitalizedBullet only)
21
+ :due_interests_beginning_of_period,
22
+
23
+ # Due interests at the end of the term (Bullet and UncapitalizedBullet only)
24
+ :due_interests_end_of_period,
22
25
 
23
26
  # Adjustment of -0.01, 0 or +0.01 cent depending on accrued_delta_interests
24
27
  :amount_to_add,
@@ -1,22 +1,13 @@
1
1
  # coding: utf-8
2
2
  module LoanCreator
3
3
  class Timetable
4
- # Used to calculate next term's date (see ActiveSupport#advance)
5
- PERIODS = {
6
- month: {months: 1},
7
- quarter: {months: 3},
8
- semester: {months: 6},
9
- year: {years: 1}
10
- }
4
+ attr_reader :loan, :terms, :starting_index #, :interests_start_date
11
5
 
12
- attr_reader :terms, :starts_on, :period #, :interests_start_date
13
-
14
- def initialize(starts_on:, period:, interests_start_date: nil, starting_index: 1)
15
- raise ArgumentError.new(:period) unless PERIODS.keys.include?(period)
6
+ delegate :starts_on, :period, to: :loan
16
7
 
8
+ def initialize(loan:, interests_start_date: nil, starting_index: 1)
17
9
  @terms = []
18
- @starts_on = (starts_on.is_a?(Date) ? starts_on : Date.parse(starts_on))
19
- @period = period
10
+ @loan = loan
20
11
  @starting_index = starting_index
21
12
 
22
13
  if interests_start_date
@@ -26,8 +17,11 @@ module LoanCreator
26
17
 
27
18
  def <<(term)
28
19
  raise ArgumentError.new('LoanCreator::Term expected') unless term.is_a?(LoanCreator::Term)
29
- term.index ||= autoincrement_index
30
- term.due_on ||= date_for(term.index)
20
+
21
+ @current_index = term.index || next_index
22
+
23
+ term.index = @current_index
24
+ term.due_on ||= loan.timetable_term_dates[term.index]
31
25
  @terms << term
32
26
  self
33
27
  end
@@ -43,29 +37,8 @@ module LoanCreator
43
37
  @terms.find { |term| term.index == index }
44
38
  end
45
39
 
46
- private
47
-
48
- def autoincrement_index
49
- @current_index = (@current_index.nil? ? @starting_index : @current_index + 1)
50
- end
51
-
52
- def date_for(index)
53
- @_dates ||= Hash.new do |dates, index|
54
- dates[index] =
55
- if index < 1
56
- dates[index + 1].advance(PERIODS.fetch(period).transform_values {|n| -n})
57
- elsif index == 1
58
- starts_on
59
- else
60
- dates[index - 1].advance(PERIODS.fetch(period))
61
- end
62
- end
63
-
64
- @_dates[index]
65
- end
66
-
67
- def reset_dates
68
- @_dates = nil
40
+ def next_index
41
+ @current_index.nil? ? @starting_index : @current_index + 1
69
42
  end
70
43
  end
71
44
  end
@@ -7,28 +7,39 @@ module LoanCreator
7
7
  reset_current_term
8
8
  @crd_beginning_of_period = amount
9
9
  @crd_end_of_period = amount
10
- (duration_in_periods - 1).times { timetable << current_term }
11
- compute_last_term
12
- timetable << current_term
10
+
11
+ duration_in_periods.times { |idx| timetable << compute_current_term(idx, timetable) }
12
+
13
13
  timetable
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def compute_last_term
19
- @crd_end_of_period = bigd('0')
20
- @period_interests = total_interests
21
- @period_capital = @crd_beginning_of_period
22
- @total_paid_capital_end_of_period = @period_capital
23
- @total_paid_interests_end_of_period = @period_interests
24
- @period_amount_to_pay = @period_capital + @period_interests
18
+ def compute_current_term(idx, timetable)
19
+ @due_on = timetable_term_dates[timetable.next_index]
20
+ last_period?(idx) ? compute_last_term(timetable) : compute_term(timetable)
21
+ current_term
22
+ end
23
+
24
+ def compute_last_term(timetable)
25
+ @crd_end_of_period = bigd('0')
26
+ @due_interests_beginning_of_period = @due_interests_end_of_period
27
+ @period_interests = @due_interests_end_of_period + compute_interests(@due_on, timetable)
28
+ @due_interests_end_of_period = 0
29
+ @period_capital = @crd_beginning_of_period
30
+ @total_paid_capital_end_of_period += @period_capital
31
+ @total_paid_interests_end_of_period += @period_interests
32
+ @period_amount_to_pay = @period_capital + @period_interests
33
+ end
34
+
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])
37
+ amount.mult(bigd(computed_periodic_interests_rate), BIG_DECIMAL_DIGITS)
25
38
  end
26
39
 
27
- def total_interests
28
- amount.mult(
29
- bigd(periodic_interests_rate) * bigd(duration_in_periods),
30
- BIG_DECIMAL_DIGITS
31
- )
40
+ def compute_term(timetable)
41
+ @due_interests_beginning_of_period = @due_interests_end_of_period
42
+ @due_interests_end_of_period += compute_interests(@due_on, timetable)
32
43
  end
33
44
  end
34
45
  end
@@ -1,3 +1,3 @@
1
1
  module LoanCreator
2
- VERSION = '0.6.2'.freeze
2
+ VERSION = '0.8.1'.freeze
3
3
  end
data/loan_creator.gemspec CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.add_development_dependency 'bundler', '~> 1.13'
23
- spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'bundler', '~> 2.1.4'
23
+ spec.add_development_dependency 'rake', '~> 12.0'
24
24
  spec.add_development_dependency 'rspec', '~> 3.0'
25
25
  spec.add_development_dependency 'simplecov', '~> 0.16'
26
26
  spec.add_development_dependency 'byebug', '~> 11.0'
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.6.2
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - thibaulth
@@ -9,10 +9,10 @@ authors:
9
9
  - younes.serraj
10
10
  - Antoine Becquet
11
11
  - Jerome Drevet
12
- autorequire:
12
+ autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2020-12-08 00:00:00.000000000 Z
15
+ date: 2021-04-02 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bundler
@@ -20,28 +20,28 @@ dependencies:
20
20
  requirements:
21
21
  - - "~>"
22
22
  - !ruby/object:Gem::Version
23
- version: '1.13'
23
+ version: 2.1.4
24
24
  type: :development
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
28
  - - "~>"
29
29
  - !ruby/object:Gem::Version
30
- version: '1.13'
30
+ version: 2.1.4
31
31
  - !ruby/object:Gem::Dependency
32
32
  name: rake
33
33
  requirement: !ruby/object:Gem::Requirement
34
34
  requirements:
35
35
  - - "~>"
36
36
  - !ruby/object:Gem::Version
37
- version: '10.0'
37
+ version: '12.0'
38
38
  type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  requirements:
42
42
  - - "~>"
43
43
  - !ruby/object:Gem::Version
44
- version: '10.0'
44
+ version: '12.0'
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: rspec
47
47
  requirement: !ruby/object:Gem::Requirement
@@ -112,7 +112,7 @@ dependencies:
112
112
  - - ">="
113
113
  - !ruby/object:Gem::Version
114
114
  version: '0'
115
- description:
115
+ description:
116
116
  email:
117
117
  - thibault@capsens.eu
118
118
  - nicolas.besnard@capsens.eu
@@ -123,8 +123,8 @@ executables: []
123
123
  extensions: []
124
124
  extra_rdoc_files: []
125
125
  files:
126
+ - ".circleci/config.yml"
126
127
  - ".gitignore"
127
- - ".gitlab-ci.yml"
128
128
  - ".rspec"
129
129
  - ".rubocop.yml"
130
130
  - ".ruby-gemset"
@@ -157,7 +157,7 @@ homepage: https://github.com/CapSens/loan-creator
157
157
  licenses:
158
158
  - MIT
159
159
  metadata: {}
160
- post_install_message:
160
+ post_install_message:
161
161
  rdoc_options: []
162
162
  require_paths:
163
163
  - lib
@@ -172,9 +172,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
172
172
  - !ruby/object:Gem::Version
173
173
  version: '0'
174
174
  requirements: []
175
- rubyforge_project:
176
- rubygems_version: 2.5.2.3
177
- signing_key:
175
+ rubygems_version: 3.1.2
176
+ signing_key:
178
177
  specification_version: 4
179
178
  summary: Create and update timetables from input data
180
179
  test_files: []
data/.gitlab-ci.yml DELETED
@@ -1,11 +0,0 @@
1
- rspec:
2
- image: "ruby:2.3"
3
- before_script:
4
- # COMMANDS
5
- - ruby -v
6
- - which ruby
7
- # BUNDLER
8
- - gem install bundler --no-ri --no-rdoc
9
- - bundle install --jobs $(nproc) "${FLAGS[@]}"
10
- script:
11
- - bundle exec rspec --color --format documentation