loan_creator 0.6.3.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -0
- data/README.md +11 -2
- data/lib/loan_creator/borrower_timetable.rb +4 -2
- data/lib/loan_creator/bullet.rb +21 -13
- data/lib/loan_creator/common.rb +61 -24
- data/lib/loan_creator/linear.rb +38 -14
- 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 -0
- metadata +34 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f175586a927e2dd609903b79ed629c1ab5bc3aecde02b1cc5809629d33c5b852
|
4
|
+
data.tar.gz: fd695b7fba80c64efa7b5c0cefc9a1092ed6c74aa4ac070da3cb25771740e3bf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6de1a02fc201dd985c0c255ee7b96989d477723607015647f9608228174f9bfdd4aa7f667ea9d74425a251b4912dae0430e1edd84623b7a8c1953e28ce1797f9
|
7
|
+
data.tar.gz: b3b3e8d4a1d007b1f8481238df1d021b5f597d2a46b887495f4099320dcd20711ccc2fb9e2ed2fa34775a99c33826c9de2d4a5b7ede12d7eb8e978d45df017f1
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,40 @@
|
|
1
|
+
v0.8.2
|
2
|
+
-------------------------
|
3
|
+
|
4
|
+
- manage initial value `due_interests` for Linear and InFine loan types
|
5
|
+
|
6
|
+
v0.8.1
|
7
|
+
-------------------------
|
8
|
+
- add `realistic_durations` option
|
9
|
+
|
10
|
+
v0.8.0
|
11
|
+
-------------------------
|
12
|
+
|
13
|
+
#### Feature
|
14
|
+
- due interests to date are now store into `due_interests_` columns for `LoanCreator::UncapitalizedBullet`
|
15
|
+
|
16
|
+
#### Bugfix
|
17
|
+
- fix `initial_values` not taking `paid_capital` and `paid_interests` into account for `LoanCreator::UncapitalizedBullet`
|
18
|
+
and `LoanCreator::Bullet`
|
19
|
+
|
20
|
+
#### Breaking changes
|
21
|
+
- rename columns `capitalized_interests_beginning_of_period` and `capitalized_interests_end_of_period` for
|
22
|
+
`due_interests_beginning_of_period` and `due_interests_end_of_period`
|
23
|
+
- rename `capitalized_interests` option in `:inital_values` into `due_interests`
|
24
|
+
|
25
|
+
|
26
|
+
v0.7.1
|
27
|
+
-------------------------
|
28
|
+
|
29
|
+
- fix `index` for `borrower_timetable` method that did not take into account starting index of `lender_timetables`
|
30
|
+
|
31
|
+
v0.7.0
|
32
|
+
-------------------------
|
33
|
+
|
34
|
+
- change `capitalized_interests` for `capitalized_interests_beginning_of_period`
|
35
|
+
and `capitalized_interests_end_of_period` in `LoanCreator::Term`
|
36
|
+
- add `capitalized_interests` in `:inital_values` for `LoanCreator::Bullet` loans
|
37
|
+
|
1
38
|
v0.6.2
|
2
39
|
-------------------------
|
3
40
|
|
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.),
|
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
|
@@ -103,29 +116,32 @@ module LoanCreator
|
|
103
116
|
end
|
104
117
|
|
105
118
|
def set_initial_values
|
106
|
-
@starting_index
|
119
|
+
@starting_index = initial_values[:starting_index] || 1
|
120
|
+
@initial_due_interests = bigd(initial_values[:due_interests] || 0)
|
107
121
|
|
108
122
|
return if initial_values.blank?
|
109
123
|
|
110
124
|
(@total_paid_capital_end_of_period = bigd(initial_values[:paid_capital]))
|
111
125
|
(@total_paid_interests_end_of_period = bigd(initial_values[:paid_interests]))
|
112
126
|
(@accrued_delta_interests = bigd(initial_values[:accrued_delta_interests]))
|
127
|
+
(@due_interests_beginning_of_period = bigd(initial_values[:due_interests] || 0))
|
113
128
|
end
|
114
129
|
|
115
130
|
def reset_current_term
|
116
|
-
@
|
117
|
-
@
|
118
|
-
@
|
119
|
-
@
|
120
|
-
@
|
121
|
-
@
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@
|
125
|
-
@
|
126
|
-
@
|
127
|
-
@
|
128
|
-
@
|
131
|
+
@accrued_delta_interests ||= bigd('0')
|
132
|
+
@total_paid_capital_end_of_period ||= bigd('0')
|
133
|
+
@total_paid_interests_end_of_period ||= bigd('0')
|
134
|
+
@due_interests_beginning_of_period ||= bigd('0')
|
135
|
+
@crd_beginning_of_period = bigd('0')
|
136
|
+
@crd_end_of_period = bigd('0')
|
137
|
+
@period_theoric_interests = bigd('0')
|
138
|
+
@due_interests_end_of_period = @due_interests_beginning_of_period
|
139
|
+
@delta_interests = bigd('0')
|
140
|
+
@amount_to_add = bigd('0')
|
141
|
+
@period_interests = bigd('0')
|
142
|
+
@period_capital = bigd('0')
|
143
|
+
@period_amount_to_pay = bigd('0')
|
144
|
+
@due_on = nil
|
129
145
|
end
|
130
146
|
|
131
147
|
def current_term
|
@@ -135,7 +151,8 @@ module LoanCreator
|
|
135
151
|
period_theoric_interests: @period_theoric_interests,
|
136
152
|
delta_interests: @delta_interests,
|
137
153
|
accrued_delta_interests: @accrued_delta_interests,
|
138
|
-
|
154
|
+
due_interests_beginning_of_period: @due_interests_beginning_of_period,
|
155
|
+
due_interests_end_of_period: @due_interests_end_of_period,
|
139
156
|
amount_to_add: @amount_to_add,
|
140
157
|
period_interests: @period_interests,
|
141
158
|
period_capital: @period_capital,
|
@@ -149,8 +166,7 @@ module LoanCreator
|
|
149
166
|
|
150
167
|
def new_timetable
|
151
168
|
LoanCreator::Timetable.new(
|
152
|
-
|
153
|
-
period: period,
|
169
|
+
loan: self,
|
154
170
|
interests_start_date: interests_start_date,
|
155
171
|
starting_index: @starting_index
|
156
172
|
)
|
@@ -160,6 +176,10 @@ module LoanCreator
|
|
160
176
|
@index ? (@starting_index + @index - 1) : nil
|
161
177
|
end
|
162
178
|
|
179
|
+
def last_period?(idx)
|
180
|
+
idx == (duration_in_periods - 1)
|
181
|
+
end
|
182
|
+
|
163
183
|
def compute_term_zero
|
164
184
|
@crd_beginning_of_period = @crd_end_of_period
|
165
185
|
@period_theoric_interests = term_zero_interests
|
@@ -169,6 +189,7 @@ module LoanCreator
|
|
169
189
|
@total_paid_interests_end_of_period += @period_interests
|
170
190
|
@period_amount_to_pay = @period_interests
|
171
191
|
@index = 0
|
192
|
+
@due_on = timetable_term_dates[0]
|
172
193
|
end
|
173
194
|
|
174
195
|
def term_zero_interests
|
@@ -191,5 +212,21 @@ module LoanCreator
|
|
191
212
|
def term_zero?
|
192
213
|
interests_start_date && interests_start_date < term_zero_date
|
193
214
|
end
|
215
|
+
|
216
|
+
def compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date:)
|
217
|
+
realistic_days = 365
|
218
|
+
realistic_days += 1 if date.leap?
|
219
|
+
realistic_days_in_period = (date - relative_to_date).to_i
|
220
|
+
|
221
|
+
annual_interests_rate.div(bigd(realistic_days) / bigd(realistic_days_in_period), BIG_DECIMAL_DIGITS)
|
222
|
+
end
|
223
|
+
|
224
|
+
def realistic_durations?
|
225
|
+
!!@realistic_durations
|
226
|
+
end
|
227
|
+
|
228
|
+
def compute_period_generated_interests(interests_rate)
|
229
|
+
(@crd_beginning_of_period + @due_interests_beginning_of_period).mult(interests_rate, BIG_DECIMAL_DIGITS)
|
230
|
+
end
|
194
231
|
end
|
195
232
|
end
|
data/lib/loan_creator/linear.rb
CHANGED
@@ -11,26 +11,24 @@ 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
|
-
@
|
30
|
+
@due_interests_beginning_of_period = @due_interests_end_of_period
|
31
|
+
@period_theoric_interests = period_theoric_interests(computed_periodic_interests_rate)
|
34
32
|
@delta_interests = @period_theoric_interests - @period_theoric_interests.round(2)
|
35
33
|
@accrued_delta_interests += @delta_interests
|
36
34
|
@amount_to_add = bigd(
|
@@ -49,8 +47,17 @@ module LoanCreator
|
|
49
47
|
@total_paid_interests_end_of_period += @period_interests
|
50
48
|
@period_amount_to_pay = @period_interests + @period_capital
|
51
49
|
@crd_end_of_period -= @period_capital
|
52
|
-
@
|
53
|
-
|
50
|
+
@due_interests_end_of_period -= reimbursed_due_interests
|
51
|
+
|
52
|
+
current_term
|
53
|
+
end
|
54
|
+
|
55
|
+
def period_theoric_interests(computed_periodic_interests_rate)
|
56
|
+
if @due_interests_beginning_of_period > 0
|
57
|
+
reimbursed_due_interests + compute_period_generated_interests(computed_periodic_interests_rate)
|
58
|
+
else
|
59
|
+
compute_period_generated_interests(computed_periodic_interests_rate)
|
60
|
+
end
|
54
61
|
end
|
55
62
|
|
56
63
|
def period_capital
|
@@ -58,9 +65,26 @@ module LoanCreator
|
|
58
65
|
@crd_beginning_of_period
|
59
66
|
elsif @deferred_period
|
60
67
|
bigd(0)
|
68
|
+
elsif @due_interests_beginning_of_period > 0
|
69
|
+
compute_period_capital - reimbursed_due_interests
|
70
|
+
else
|
71
|
+
compute_period_capital
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def reimbursed_due_interests
|
76
|
+
if @deferred_period
|
77
|
+
bigd(0)
|
61
78
|
else
|
62
|
-
|
79
|
+
[
|
80
|
+
@due_interests_beginning_of_period,
|
81
|
+
compute_period_capital
|
82
|
+
].min
|
63
83
|
end
|
64
84
|
end
|
85
|
+
|
86
|
+
def compute_period_capital
|
87
|
+
((amount + @initial_due_interests) / (duration_in_periods - deferred_in_periods)).round(2)
|
88
|
+
end
|
65
89
|
end
|
66
90
|
end
|
@@ -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
@@ -24,6 +24,8 @@ Gem::Specification.new do |spec|
|
|
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'
|
27
|
+
spec.add_development_dependency 'pry', '~> 0.13.1'
|
28
|
+
spec.add_development_dependency 'table_print', '~> 1.5'
|
27
29
|
|
28
30
|
spec.add_runtime_dependency 'bigdecimal'
|
29
31
|
spec.add_runtime_dependency 'activesupport'
|
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.2
|
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: 2021-
|
15
|
+
date: 2021-05-05 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bundler
|
@@ -84,6 +84,34 @@ dependencies:
|
|
84
84
|
- - "~>"
|
85
85
|
- !ruby/object:Gem::Version
|
86
86
|
version: '11.0'
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: pry
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 0.13.1
|
94
|
+
type: :development
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 0.13.1
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: table_print
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '1.5'
|
108
|
+
type: :development
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - "~>"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '1.5'
|
87
115
|
- !ruby/object:Gem::Dependency
|
88
116
|
name: bigdecimal
|
89
117
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,7 +140,7 @@ dependencies:
|
|
112
140
|
- - ">="
|
113
141
|
- !ruby/object:Gem::Version
|
114
142
|
version: '0'
|
115
|
-
description:
|
143
|
+
description:
|
116
144
|
email:
|
117
145
|
- thibault@capsens.eu
|
118
146
|
- nicolas.besnard@capsens.eu
|
@@ -157,7 +185,7 @@ homepage: https://github.com/CapSens/loan-creator
|
|
157
185
|
licenses:
|
158
186
|
- MIT
|
159
187
|
metadata: {}
|
160
|
-
post_install_message:
|
188
|
+
post_install_message:
|
161
189
|
rdoc_options: []
|
162
190
|
require_paths:
|
163
191
|
- lib
|
@@ -173,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
201
|
version: '0'
|
174
202
|
requirements: []
|
175
203
|
rubygems_version: 3.1.2
|
176
|
-
signing_key:
|
204
|
+
signing_key:
|
177
205
|
specification_version: 4
|
178
206
|
summary: Create and update timetables from input data
|
179
207
|
test_files: []
|