loan_creator 0.7.1 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +17 -2
- data/lib/loan_creator.rb +11 -10
- data/lib/loan_creator/borrower_timetable.rb +3 -4
- data/lib/loan_creator/bullet.rb +23 -17
- data/lib/loan_creator/common.rb +154 -48
- data/lib/loan_creator/linear.rb +38 -14
- data/lib/loan_creator/standard.rb +19 -21
- data/lib/loan_creator/term.rb +4 -4
- data/lib/loan_creator/term_dates_validator.rb +73 -0
- 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
- data/scripts/convert_export_to_spec.rb +51 -0
- metadata +36 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e207b2a1a30e9f07b773f9d3071c8df1cb815e17602661efb72d4315ead65dba
|
4
|
+
data.tar.gz: c6760d02448a8a9bd6f40fa992aa09f1d716a06b94e2ce5afa8b54eddfeeddf6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75d786838e61c1f342da8a3ebf3e0264e42f0e80909bc22ffe63f5c76acf4a1c36e1130ebcbcb4f8c72d87f7a99735c8e3d70f634889935dd57849f92bcdf98d
|
7
|
+
data.tar.gz: 768182d707c17e9f5b1f8f748f6dcaf1277e1a1bf0b68c33ed28a178464117272e8dfe294585baeac3fa1d9fb3f28945e1224b7e158fffea66bda89c5cb25dff
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,38 @@
|
|
1
|
+
v0.9.1
|
2
|
+
-------------------------
|
3
|
+
- add spec importer `scripts/convert_export_to_spec.rb {csv_url}`
|
4
|
+
- fix period interests rate for realistic duration period overlapping leap & non leap years
|
5
|
+
|
6
|
+
v0.9.0
|
7
|
+
-------------------------
|
8
|
+
- add possibility to pass the argument `term_dates` for `LoanCreator::Bullet`, `LoanCreator::InFine` and `LoanCreator::Linear`
|
9
|
+
- `term_dates` option is based on `realistic_durations` and allow to compute terms custom date to custom date
|
10
|
+
|
11
|
+
v0.8.2
|
12
|
+
-------------------------
|
13
|
+
|
14
|
+
- manage initial value `due_interests` for Linear and InFine loan types
|
15
|
+
|
16
|
+
v0.8.1
|
17
|
+
-------------------------
|
18
|
+
- add `realistic_durations` option
|
19
|
+
|
20
|
+
v0.8.0
|
21
|
+
-------------------------
|
22
|
+
|
23
|
+
#### Feature
|
24
|
+
- due interests to date are now store into `due_interests_` columns for `LoanCreator::UncapitalizedBullet`
|
25
|
+
|
26
|
+
#### Bugfix
|
27
|
+
- fix `initial_values` not taking `paid_capital` and `paid_interests` into account for `LoanCreator::UncapitalizedBullet`
|
28
|
+
and `LoanCreator::Bullet`
|
29
|
+
|
30
|
+
#### Breaking changes
|
31
|
+
- rename columns `capitalized_interests_beginning_of_period` and `capitalized_interests_end_of_period` for
|
32
|
+
`due_interests_beginning_of_period` and `due_interests_end_of_period`
|
33
|
+
- rename `capitalized_interests` option in `:inital_values` into `due_interests`
|
34
|
+
|
35
|
+
|
1
36
|
v0.7.1
|
2
37
|
-------------------------
|
3
38
|
|
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,15 @@ 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 that specifies whether or not to use the real number of days in each month when calculating the periodic interests rate (ie: for a period between January 1st 2021 and February 1st 2021 the period rate is annual_interests_rate * 31/355).
|
184
|
+
Note that leap years are taken into account (ie: for a period between January 1st 2020 and February 1st 2020 the period rate is annual_interests_rate * 31/356). Also, an overlaping period
|
185
|
+
take each parts into account (ie: for a period between December 1st 2020 and February 1st 2021, the period rate is (annual_interests_rate * (31/355) + annual_interests_rate * (31/356)))
|
186
|
+
Default: `false`.
|
187
|
+
The default behaviour is to use the `period` in relation to the number of months in a year (ie: for a monthly timetable annual_interests_rate * 1/12, for quarter annual_interests_rate * 3/12, etc.)
|
188
|
+
|
189
|
+
|
190
|
+
`term_dates`: Optional. Implemented for `LoanCreator::Bullet`, `LoanCreator::InFine` and `LoanCreator::Linear`. Can be used if you want to implement custom due dates for terms. Terms will be compute from date to date. Must be an array with following dates.
|
191
|
+
|
177
192
|
## Calculation
|
178
193
|
|
179
194
|
An excel simulator for standard case can be found [here](CapSens_Loan.xlsx).
|
data/lib/loan_creator.rb
CHANGED
@@ -7,14 +7,15 @@ require 'loan_creator/version'
|
|
7
7
|
module LoanCreator
|
8
8
|
BIG_DECIMAL_DIGITS = 14
|
9
9
|
|
10
|
-
autoload :ExcelFormulas,
|
11
|
-
autoload :BorrowerTimetable,
|
12
|
-
autoload :Common,
|
13
|
-
autoload :Standard,
|
14
|
-
autoload :Linear,
|
15
|
-
autoload :InFine,
|
16
|
-
autoload :Bullet,
|
17
|
-
autoload :Timetable,
|
18
|
-
autoload :Term,
|
19
|
-
autoload :UncapitalizedBullet,
|
10
|
+
autoload :ExcelFormulas, 'loan_creator/excel_formulas'
|
11
|
+
autoload :BorrowerTimetable, 'loan_creator/borrower_timetable'
|
12
|
+
autoload :Common, 'loan_creator/common'
|
13
|
+
autoload :Standard, 'loan_creator/standard'
|
14
|
+
autoload :Linear, 'loan_creator/linear'
|
15
|
+
autoload :InFine, 'loan_creator/in_fine'
|
16
|
+
autoload :Bullet, 'loan_creator/bullet'
|
17
|
+
autoload :Timetable, 'loan_creator/timetable'
|
18
|
+
autoload :Term, 'loan_creator/term'
|
19
|
+
autoload :UncapitalizedBullet, 'loan_creator/uncapitalized_bullet'
|
20
|
+
autoload :TermDatesValidator, 'loan_creator/term_dates_validator'
|
20
21
|
end
|
@@ -7,8 +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
|
-
:
|
11
|
-
:
|
10
|
+
:due_interests_beginning_of_period,
|
11
|
+
:due_interests_end_of_period,
|
12
12
|
:period_amount_to_pay
|
13
13
|
].freeze
|
14
14
|
|
@@ -21,8 +21,7 @@ module LoanCreator
|
|
21
21
|
|
22
22
|
borrower_timetable = LoanCreator::Timetable.new(
|
23
23
|
starting_index: lenders_timetables.first.starting_index,
|
24
|
-
|
25
|
-
period: lenders_timetables.first.period
|
24
|
+
loan: lenders_timetables.first.loan
|
26
25
|
)
|
27
26
|
|
28
27
|
# Borrower timetable is not concerned with computation-related value (delta, etc.),
|
data/lib/loan_creator/bullet.rb
CHANGED
@@ -7,33 +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
|
-
|
25
|
-
@
|
26
|
-
@
|
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_capitalized_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
|
27
33
|
end
|
28
34
|
|
29
|
-
def compute_capitalized_interests
|
30
|
-
|
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)
|
31
38
|
end
|
32
39
|
|
33
40
|
def compute_term(timetable)
|
34
|
-
@
|
35
|
-
@
|
36
|
-
timetable << current_term
|
41
|
+
@due_interests_beginning_of_period = @due_interests_end_of_period
|
42
|
+
@due_interests_end_of_period += compute_capitalized_interests(@due_on, timetable)
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
data/lib/loan_creator/common.rb
CHANGED
@@ -17,11 +17,20 @@ module LoanCreator
|
|
17
17
|
:duration_in_periods
|
18
18
|
].freeze
|
19
19
|
|
20
|
+
REQUIRED_ATTRIBUTES_TERM_DATES = [
|
21
|
+
:amount,
|
22
|
+
:annual_interests_rate,
|
23
|
+
:starts_on,
|
24
|
+
:duration_in_periods,
|
25
|
+
:term_dates
|
26
|
+
].freeze
|
27
|
+
|
20
28
|
OPTIONAL_ATTRIBUTES = {
|
21
29
|
# attribute: default_value
|
22
30
|
deferred_in_periods: 0,
|
23
31
|
interests_start_date: nil,
|
24
|
-
initial_values: {}
|
32
|
+
initial_values: {},
|
33
|
+
realistic_durations: false
|
25
34
|
}.freeze
|
26
35
|
|
27
36
|
attr_reader *REQUIRED_ATTRIBUTES
|
@@ -35,16 +44,29 @@ module LoanCreator
|
|
35
44
|
validate_attributes
|
36
45
|
set_initial_values
|
37
46
|
validate_initial_values
|
47
|
+
prepare_custom_term_dates if term_dates?
|
38
48
|
end
|
39
49
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
50
|
+
def periodic_interests_rate(date = nil, relative_to_date: nil)
|
51
|
+
if realistic_durations?
|
52
|
+
compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date: relative_to_date).div(100, BIG_DECIMAL_DIGITS)
|
53
|
+
else
|
54
|
+
@periodic_interests_rate ||=
|
55
|
+
annual_interests_rate.div(12 / PERIODS_IN_MONTHS[period], BIG_DECIMAL_DIGITS).div(100, BIG_DECIMAL_DIGITS)
|
56
|
+
end
|
43
57
|
end
|
44
58
|
|
45
|
-
def
|
46
|
-
@
|
47
|
-
|
59
|
+
def timetable_term_dates
|
60
|
+
@_timetable_term_dates ||= Hash.new do |dates, index|
|
61
|
+
dates[index] =
|
62
|
+
if index < 1
|
63
|
+
dates[index + 1].advance(months: -PERIODS_IN_MONTHS.fetch(period))
|
64
|
+
elsif index == 1
|
65
|
+
starts_on
|
66
|
+
else
|
67
|
+
starts_on.advance(months: PERIODS_IN_MONTHS.fetch(period) * (index - 1))
|
68
|
+
end
|
69
|
+
end
|
48
70
|
end
|
49
71
|
|
50
72
|
def lender_timetable
|
@@ -62,35 +84,49 @@ module LoanCreator
|
|
62
84
|
private
|
63
85
|
|
64
86
|
def require_attributes
|
65
|
-
|
87
|
+
required_attributes.each { |k| raise ArgumentError.new(k) unless @options.fetch(k, nil) }
|
66
88
|
end
|
67
89
|
|
68
90
|
def reinterpret_attributes
|
69
|
-
@options[:period] = @options[:period].to_sym
|
91
|
+
@options[:period] = @options[:period].to_sym unless term_dates?
|
70
92
|
@options[:amount] = bigd(@options[:amount])
|
71
93
|
@options[:annual_interests_rate] = bigd(@options[:annual_interests_rate])
|
72
94
|
@options[:starts_on] = Date.parse(@options[:starts_on]) if @options[:starts_on].is_a?(String)
|
73
95
|
@options[:interests_start_date] = Date.parse(@options[:interests_start_date]) if @options[:interests_start_date].is_a?(String)
|
96
|
+
|
97
|
+
if term_dates?
|
98
|
+
@options[:term_dates].map!{ |term_date| Date.parse(term_date.to_s) }
|
99
|
+
end
|
74
100
|
end
|
75
101
|
|
76
102
|
def set_attributes
|
77
|
-
|
103
|
+
required_attributes.each { |k| instance_variable_set(:"@#{k}", @options.fetch(k)) }
|
78
104
|
OPTIONAL_ATTRIBUTES.each { |k,v| instance_variable_set(:"@#{k}", @options.fetch(k, v)) }
|
79
105
|
end
|
80
106
|
|
81
107
|
def validate(key, &block)
|
82
108
|
raise unless block.call(instance_variable_get(:"@#{key}"))
|
83
|
-
rescue
|
84
|
-
raise ArgumentError.new(key)
|
109
|
+
rescue => e
|
110
|
+
raise ArgumentError.new([key, e.message].join(': '))
|
85
111
|
end
|
86
112
|
|
87
113
|
def validate_attributes
|
88
|
-
validate(:period) { |v| PERIODS_IN_MONTHS.keys.include?(v) }
|
114
|
+
validate(:period) { |v| PERIODS_IN_MONTHS.keys.include?(v) } unless term_dates?
|
89
115
|
validate(:amount) { |v| v.is_a?(BigDecimal) && v > 0 }
|
90
116
|
validate(:annual_interests_rate) { |v| v.is_a?(BigDecimal) && v >= 0 }
|
91
117
|
validate(:starts_on) { |v| v.is_a?(Date) }
|
92
118
|
validate(:duration_in_periods) { |v| v.is_a?(Integer) && v > 0 }
|
93
119
|
validate(:deferred_in_periods) { |v| v.is_a?(Integer) && v >= 0 && v < duration_in_periods }
|
120
|
+
validate_term_dates if term_dates?
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_term_dates
|
124
|
+
TermDatesValidator.call(
|
125
|
+
term_dates: @options[:term_dates],
|
126
|
+
duration_in_periods: @options[:duration_in_periods],
|
127
|
+
interests_start_date: @options[:interests_start_date],
|
128
|
+
loan_class: self.class.name
|
129
|
+
)
|
94
130
|
end
|
95
131
|
|
96
132
|
def validate_initial_values
|
@@ -103,59 +139,57 @@ module LoanCreator
|
|
103
139
|
end
|
104
140
|
|
105
141
|
def set_initial_values
|
106
|
-
@starting_index
|
142
|
+
@starting_index = initial_values[:starting_index] || 1
|
143
|
+
@initial_due_interests = bigd(initial_values[:due_interests] || 0)
|
107
144
|
|
108
145
|
return if initial_values.blank?
|
109
146
|
|
110
147
|
(@total_paid_capital_end_of_period = bigd(initial_values[:paid_capital]))
|
111
148
|
(@total_paid_interests_end_of_period = bigd(initial_values[:paid_interests]))
|
112
149
|
(@accrued_delta_interests = bigd(initial_values[:accrued_delta_interests]))
|
113
|
-
|
114
|
-
(@capitalized_interests_beginning_of_period = bigd(initial_values[:capitalized_interests] || 0))
|
115
|
-
end
|
150
|
+
(@due_interests_beginning_of_period = bigd(initial_values[:due_interests] || 0))
|
116
151
|
end
|
117
152
|
|
118
153
|
def reset_current_term
|
119
|
-
@accrued_delta_interests
|
120
|
-
@total_paid_capital_end_of_period
|
154
|
+
@accrued_delta_interests ||= bigd('0')
|
155
|
+
@total_paid_capital_end_of_period ||= bigd('0')
|
121
156
|
@total_paid_interests_end_of_period ||= bigd('0')
|
122
|
-
@
|
123
|
-
@crd_beginning_of_period
|
124
|
-
@crd_end_of_period
|
125
|
-
@period_theoric_interests
|
126
|
-
@
|
127
|
-
@delta_interests
|
128
|
-
@amount_to_add
|
129
|
-
@period_interests
|
130
|
-
@period_capital
|
131
|
-
@period_amount_to_pay
|
132
|
-
@due_on
|
157
|
+
@due_interests_beginning_of_period ||= bigd('0')
|
158
|
+
@crd_beginning_of_period = bigd('0')
|
159
|
+
@crd_end_of_period = bigd('0')
|
160
|
+
@period_theoric_interests = bigd('0')
|
161
|
+
@due_interests_end_of_period = @due_interests_beginning_of_period
|
162
|
+
@delta_interests = bigd('0')
|
163
|
+
@amount_to_add = bigd('0')
|
164
|
+
@period_interests = bigd('0')
|
165
|
+
@period_capital = bigd('0')
|
166
|
+
@period_amount_to_pay = bigd('0')
|
167
|
+
@due_on = nil
|
133
168
|
end
|
134
169
|
|
135
170
|
def current_term
|
136
171
|
LoanCreator::Term.new(
|
137
|
-
crd_beginning_of_period:
|
138
|
-
crd_end_of_period:
|
139
|
-
period_theoric_interests:
|
140
|
-
delta_interests:
|
141
|
-
accrued_delta_interests:
|
142
|
-
|
143
|
-
|
144
|
-
amount_to_add:
|
145
|
-
period_interests:
|
146
|
-
period_capital:
|
147
|
-
total_paid_capital_end_of_period:
|
148
|
-
total_paid_interests_end_of_period:
|
149
|
-
period_amount_to_pay:
|
150
|
-
due_on:
|
151
|
-
index:
|
172
|
+
crd_beginning_of_period: @crd_beginning_of_period,
|
173
|
+
crd_end_of_period: @crd_end_of_period,
|
174
|
+
period_theoric_interests: @period_theoric_interests,
|
175
|
+
delta_interests: @delta_interests,
|
176
|
+
accrued_delta_interests: @accrued_delta_interests,
|
177
|
+
due_interests_beginning_of_period: @due_interests_beginning_of_period,
|
178
|
+
due_interests_end_of_period: @due_interests_end_of_period,
|
179
|
+
amount_to_add: @amount_to_add,
|
180
|
+
period_interests: @period_interests,
|
181
|
+
period_capital: @period_capital,
|
182
|
+
total_paid_capital_end_of_period: @total_paid_capital_end_of_period,
|
183
|
+
total_paid_interests_end_of_period: @total_paid_interests_end_of_period,
|
184
|
+
period_amount_to_pay: @period_amount_to_pay,
|
185
|
+
due_on: @due_on,
|
186
|
+
index: compute_index
|
152
187
|
)
|
153
188
|
end
|
154
189
|
|
155
190
|
def new_timetable
|
156
191
|
LoanCreator::Timetable.new(
|
157
|
-
|
158
|
-
period: period,
|
192
|
+
loan: self,
|
159
193
|
interests_start_date: interests_start_date,
|
160
194
|
starting_index: @starting_index
|
161
195
|
)
|
@@ -165,6 +199,10 @@ module LoanCreator
|
|
165
199
|
@index ? (@starting_index + @index - 1) : nil
|
166
200
|
end
|
167
201
|
|
202
|
+
def last_period?(idx)
|
203
|
+
idx == (duration_in_periods - 1)
|
204
|
+
end
|
205
|
+
|
168
206
|
def compute_term_zero
|
169
207
|
@crd_beginning_of_period = @crd_end_of_period
|
170
208
|
@period_theoric_interests = term_zero_interests
|
@@ -174,6 +212,7 @@ module LoanCreator
|
|
174
212
|
@total_paid_interests_end_of_period += @period_interests
|
175
213
|
@period_amount_to_pay = @period_interests
|
176
214
|
@index = 0
|
215
|
+
@due_on = timetable_term_dates[0]
|
177
216
|
end
|
178
217
|
|
179
218
|
def term_zero_interests
|
@@ -194,7 +233,74 @@ module LoanCreator
|
|
194
233
|
end
|
195
234
|
|
196
235
|
def term_zero?
|
197
|
-
interests_start_date && interests_start_date < term_zero_date
|
236
|
+
(interests_start_date && interests_start_date < term_zero_date) && !term_dates?
|
237
|
+
end
|
238
|
+
|
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
|
+
def realistic_durations?
|
277
|
+
term_dates? || @realistic_durations.present?
|
278
|
+
end
|
279
|
+
|
280
|
+
def required_attributes
|
281
|
+
if term_dates?
|
282
|
+
REQUIRED_ATTRIBUTES_TERM_DATES
|
283
|
+
else
|
284
|
+
REQUIRED_ATTRIBUTES
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def term_dates?
|
289
|
+
@options[:term_dates].present?
|
290
|
+
end
|
291
|
+
|
292
|
+
def prepare_custom_term_dates
|
293
|
+
term_dates = @options[:term_dates].each_with_index.with_object({}) do |(term_date, index), obj|
|
294
|
+
obj[index + 1] = term_date
|
295
|
+
end
|
296
|
+
|
297
|
+
term_dates[0] = starts_on
|
298
|
+
@_timetable_term_dates = term_dates
|
299
|
+
@realistic_durations = true
|
300
|
+
end
|
301
|
+
|
302
|
+
def compute_period_generated_interests(interests_rate)
|
303
|
+
(@crd_beginning_of_period + @due_interests_beginning_of_period).mult(interests_rate, BIG_DECIMAL_DIGITS)
|
198
304
|
end
|
199
305
|
end
|
200
306
|
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,11 +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
22
|
|
23
|
-
#
|
24
|
-
:
|
23
|
+
# Due interests at the end of the term (Bullet and UncapitalizedBullet only)
|
24
|
+
:due_interests_end_of_period,
|
25
25
|
|
26
26
|
# Adjustment of -0.01, 0 or +0.01 cent depending on accrued_delta_interests
|
27
27
|
:amount_to_add,
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module LoanCreator
|
2
|
+
module TermDatesValidator
|
3
|
+
def self.call(term_dates:, duration_in_periods:, interests_start_date:, loan_class:)
|
4
|
+
is_array(term_dates)
|
5
|
+
matches_duration(term_dates, duration_in_periods)
|
6
|
+
interests_start_date_present(interests_start_date)
|
7
|
+
coherent_dates_for_non_bullet(term_dates)
|
8
|
+
coherent_dates_for_bullet(term_dates) if bullet?(loan_class)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def self.is_array(term_dates)
|
14
|
+
unless term_dates.is_a?(Array)
|
15
|
+
raise TypeError, 'the :term_dates option must be an Array'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.matches_duration(term_dates, duration_in_periods)
|
20
|
+
unless term_dates.size == duration_in_periods
|
21
|
+
raise ArgumentError, "the size of :term_dates (#{term_dates.size}) do not match the :duration_in_periods (#{duration_in_periods})"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.interests_start_date_present(interests_start_date)
|
26
|
+
if interests_start_date.present?
|
27
|
+
raise ArgumentError, ":interests_start_date is no compatible with :term_dates"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.coherent_dates_for_non_bullet(term_dates)
|
32
|
+
term_dates.each_with_index do |term_date, index|
|
33
|
+
next if index.zero?
|
34
|
+
|
35
|
+
previous_term_date = term_dates[index - 1]
|
36
|
+
|
37
|
+
unless term_date > previous_term_date
|
38
|
+
previous_term_date_description =
|
39
|
+
":term_dates[#{index - 1}] (#{term_dates[index - 1].strftime('%Y-%m-%d')})"
|
40
|
+
|
41
|
+
error_message = "#{previous_term_date_description} must be before :term_dates[#{index}] (#{term_date.strftime('%Y-%m-%d')})"
|
42
|
+
|
43
|
+
raise ArgumentError, error_message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.coherent_dates_for_bullet(term_dates)
|
49
|
+
term_dates.each_with_index do |term_date, index|
|
50
|
+
next if index.zero?
|
51
|
+
|
52
|
+
days_in_year = 365
|
53
|
+
days_in_year += 1 if term_date.leap?
|
54
|
+
|
55
|
+
previous_term_date = term_dates[index - 1]
|
56
|
+
days_between = (term_date - previous_term_date).to_i.abs
|
57
|
+
|
58
|
+
if days_between > days_in_year
|
59
|
+
previous_term_date_description =
|
60
|
+
":term_dates[#{index - 1}] (#{term_dates[index - 1].strftime('%Y-%m-%d')})"
|
61
|
+
|
62
|
+
error_description = "There are #{days_between} days between #{previous_term_date_description} and :term_dates[#{index}]"
|
63
|
+
|
64
|
+
raise ArgumentError, "term dates can't be more than 1 year apart. #{error_description}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.bullet?(loan_class)
|
70
|
+
loan_class == "LoanCreator::Bullet"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -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'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'csv'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'pry'
|
5
|
+
require 'securerandom'
|
6
|
+
|
7
|
+
csv_url = ARGV[0]
|
8
|
+
|
9
|
+
spec_order = %i[
|
10
|
+
index
|
11
|
+
due_on
|
12
|
+
crd_beginning_of_period
|
13
|
+
crd_end_of_period
|
14
|
+
period_theoric_interests
|
15
|
+
delta_interests
|
16
|
+
accrued_delta_interests
|
17
|
+
amount_to_add
|
18
|
+
period_interests
|
19
|
+
period_capital
|
20
|
+
total_paid_capital_end_of_period
|
21
|
+
total_paid_interests_end_of_period
|
22
|
+
period_amount_to_pay
|
23
|
+
due_interests_beginning_of_period
|
24
|
+
due_interests_end_of_period
|
25
|
+
]
|
26
|
+
|
27
|
+
column_getter = {
|
28
|
+
index: proc { |r| r['t'].to_i },
|
29
|
+
due_on: proc { |r| r['date'] },
|
30
|
+
crd_beginning_of_period: proc { |r| r['crd_start'].gsub(',', '.').to_f },
|
31
|
+
crd_end_of_period: proc { |r| r['crd_end'].gsub(',', '.').to_f },
|
32
|
+
period_theoric_interests: proc { |r| r['period_theoric_interests'].gsub(',', '.').to_f },
|
33
|
+
delta_interests: proc { |r| r['delta_interests'].gsub(',', '.').to_f },
|
34
|
+
accrued_delta_interests: proc { |r| r['cumulated_delta_interests'].gsub(',', '.').to_f },
|
35
|
+
amount_to_add: proc { |r| r['amount_to_add'].gsub(',', '.').to_f },
|
36
|
+
period_interests: proc { |r| r['period_interests'].gsub(',', '.').to_f },
|
37
|
+
period_capital: proc { |r| r['period_capital'].gsub(',', '.').to_f },
|
38
|
+
total_paid_capital_end_of_period: proc { |r| r['total_capital_paid'].gsub(',', '.').to_f },
|
39
|
+
total_paid_interests_end_of_period: proc { |r| r['total_interests_paid'].gsub(',', '.').to_f },
|
40
|
+
period_amount_to_pay: proc { |r| r['period_total'].gsub(',', '.').to_f },
|
41
|
+
due_interests_beginning_of_period: proc { |r| r['due_interests_beginning_of_period'].gsub(',', '.').to_f },
|
42
|
+
due_interests_end_of_period: proc { |r| r['due_interests_end_of_period'].gsub(',', '.').to_f }
|
43
|
+
}
|
44
|
+
|
45
|
+
data = CSV.parse(URI.open(csv_url), headers: true).map(&:to_hash)
|
46
|
+
|
47
|
+
CSV.open("./spec/fixtures/new/#{SecureRandom.alphanumeric}.csv", 'w') do |csv|
|
48
|
+
data.each do |row|
|
49
|
+
csv << spec_order.map { |column| column_getter[column].call(row) }
|
50
|
+
end
|
51
|
+
end
|
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.9.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: 2021-
|
15
|
+
date: 2021-05-11 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
|
@@ -149,15 +177,17 @@ files:
|
|
149
177
|
- lib/loan_creator/linear.rb
|
150
178
|
- lib/loan_creator/standard.rb
|
151
179
|
- lib/loan_creator/term.rb
|
180
|
+
- lib/loan_creator/term_dates_validator.rb
|
152
181
|
- lib/loan_creator/timetable.rb
|
153
182
|
- lib/loan_creator/uncapitalized_bullet.rb
|
154
183
|
- lib/loan_creator/version.rb
|
155
184
|
- loan_creator.gemspec
|
185
|
+
- scripts/convert_export_to_spec.rb
|
156
186
|
homepage: https://github.com/CapSens/loan-creator
|
157
187
|
licenses:
|
158
188
|
- MIT
|
159
189
|
metadata: {}
|
160
|
-
post_install_message:
|
190
|
+
post_install_message:
|
161
191
|
rdoc_options: []
|
162
192
|
require_paths:
|
163
193
|
- lib
|
@@ -173,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
203
|
version: '0'
|
174
204
|
requirements: []
|
175
205
|
rubygems_version: 3.1.2
|
176
|
-
signing_key:
|
206
|
+
signing_key:
|
177
207
|
specification_version: 4
|
178
208
|
summary: Create and update timetables from input data
|
179
209
|
test_files: []
|