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 +5 -5
- data/.circleci/config.yml +45 -0
- data/.rubocop.yml +2 -2
- data/.ruby-version +1 -1
- data/CHANGELOG.md +32 -0
- data/README.md +11 -2
- data/lib/loan_creator/borrower_timetable.rb +5 -3
- data/lib/loan_creator/bullet.rb +21 -13
- data/lib/loan_creator/common.rb +55 -23
- data/lib/loan_creator/in_fine.rb +1 -1
- data/lib/loan_creator/linear.rb +10 -13
- data/lib/loan_creator/standard.rb +19 -21
- data/lib/loan_creator/term.rb +5 -2
- data/lib/loan_creator/timetable.rb +11 -38
- data/lib/loan_creator/uncapitalized_bullet.rb +26 -15
- data/lib/loan_creator/version.rb +1 -1
- data/loan_creator.gemspec +2 -2
- metadata +12 -13
- data/.gitlab-ci.yml +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4dcaafbce29a00ebcdea57133619e566cc210be6ed613003af14e0a199cc126a
|
4
|
+
data.tar.gz: 2da6fe223cfc40fd8455a5c1ec0d8ef857bfd48effb22b285f46da7c3800a81b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
15
|
+
Layout/LineLength:
|
16
16
|
Max: 120
|
17
17
|
|
18
18
|
Metrics/MethodLength:
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
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 (
|
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
|
-
|
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
|
-
|
22
|
-
|
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
|
data/lib/loan_creator/bullet.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
-
@
|
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
|
23
|
-
@total_paid_interests_end_of_period
|
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(
|
29
|
-
|
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
|
33
|
-
@
|
34
|
-
|
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
|
data/lib/loan_creator/common.rb
CHANGED
@@ -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
|
41
|
-
|
42
|
-
|
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
|
46
|
-
@
|
47
|
-
|
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
|
-
@
|
117
|
-
@
|
118
|
-
@
|
119
|
-
@
|
120
|
-
@
|
121
|
-
@
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@
|
125
|
-
@
|
126
|
-
@
|
127
|
-
@
|
128
|
-
@
|
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
|
-
|
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
|
-
|
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
|
data/lib/loan_creator/in_fine.rb
CHANGED
@@ -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
|
data/lib/loan_creator/linear.rb
CHANGED
@@ -11,26 +11,23 @@ module LoanCreator
|
|
11
11
|
timetable << current_term
|
12
12
|
end
|
13
13
|
|
14
|
-
duration_in_periods.times
|
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
|
27
|
-
|
28
|
-
|
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 *
|
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
|
-
|
53
|
-
|
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
|
16
|
-
|
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
|
27
|
-
|
28
|
-
|
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(
|
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(
|
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
|
-
|
52
|
-
|
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 *
|
55
|
+
@crd_beginning_of_period * computed_periodic_interests_rate
|
58
56
|
else
|
59
57
|
-ipmt(
|
60
|
-
|
61
|
-
|
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
|
-
|
76
|
-
|
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)
|
data/lib/loan_creator/term.rb
CHANGED
@@ -17,8 +17,11 @@ module LoanCreator
|
|
17
17
|
# Accrued interests' delta
|
18
18
|
:accrued_delta_interests,
|
19
19
|
|
20
|
-
#
|
21
|
-
:
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
|
30
|
-
|
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
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
19
|
-
@
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
28
|
-
|
29
|
-
|
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
|
data/lib/loan_creator/version.rb
CHANGED
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.
|
23
|
-
spec.add_development_dependency 'rake', '~>
|
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.
|
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:
|
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:
|
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:
|
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: '
|
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: '
|
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
|
-
|
176
|
-
|
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