loan_creator 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +2 -0
- data/lib/loan_creator.rb +11 -10
- data/lib/loan_creator/common.rb +53 -8
- data/lib/loan_creator/term_dates_validator.rb +73 -0
- data/lib/loan_creator/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb6c8e7e02bc1349e47dcb07602828d051cfa63ac7fc3e16394604f8dda05e27
|
4
|
+
data.tar.gz: d103c1e0524641e73b307702eec5bd1cf43029236a3d791805e4d62e7e0732d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c201e2a5fb2a5aaa54b1870b635e11c08b90d35f45258852f4cf61e5faa99756272b54db5a85c470123b5baaf5dedd5a9be79af8577796595997540cad4662b9
|
7
|
+
data.tar.gz: 00556c176b2e27765ca5112878495c7982cdaeb75abfe3b3956b57c6be57dfcf6c8cf89136bbfa0c94c40e7d43927ff2eafa8c0518f99d0be914e1bd7df1b70d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
v0.9.0
|
2
|
+
-------------------------
|
3
|
+
- add possibility to pass the argument `term_dates` for `LoanCreator::Bullet`, `LoanCreator::InFine` and `LoanCreator::Linear`
|
4
|
+
- `term_dates` option is based on `realistic_durations` and allow to compute terms custom date to custom date
|
5
|
+
|
1
6
|
v0.8.2
|
2
7
|
-------------------------
|
3
8
|
|
data/README.md
CHANGED
@@ -183,6 +183,8 @@ first term corresponding to 3 months of interests.
|
|
183
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
184
|
The default behaviour is to use the `period` in relation to the number of months in a year (ie: 12)
|
185
185
|
|
186
|
+
`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.
|
187
|
+
|
186
188
|
## Calculation
|
187
189
|
|
188
190
|
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
|
data/lib/loan_creator/common.rb
CHANGED
@@ -17,6 +17,14 @@ 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,
|
@@ -36,6 +44,7 @@ module LoanCreator
|
|
36
44
|
validate_attributes
|
37
45
|
set_initial_values
|
38
46
|
validate_initial_values
|
47
|
+
prepare_custom_term_dates if term_dates?
|
39
48
|
end
|
40
49
|
|
41
50
|
def periodic_interests_rate(date = nil, relative_to_date: nil)
|
@@ -75,35 +84,49 @@ module LoanCreator
|
|
75
84
|
private
|
76
85
|
|
77
86
|
def require_attributes
|
78
|
-
|
87
|
+
required_attributes.each { |k| raise ArgumentError.new(k) unless @options.fetch(k, nil) }
|
79
88
|
end
|
80
89
|
|
81
90
|
def reinterpret_attributes
|
82
|
-
@options[:period] = @options[:period].to_sym
|
91
|
+
@options[:period] = @options[:period].to_sym unless term_dates?
|
83
92
|
@options[:amount] = bigd(@options[:amount])
|
84
93
|
@options[:annual_interests_rate] = bigd(@options[:annual_interests_rate])
|
85
94
|
@options[:starts_on] = Date.parse(@options[:starts_on]) if @options[:starts_on].is_a?(String)
|
86
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
|
87
100
|
end
|
88
101
|
|
89
102
|
def set_attributes
|
90
|
-
|
103
|
+
required_attributes.each { |k| instance_variable_set(:"@#{k}", @options.fetch(k)) }
|
91
104
|
OPTIONAL_ATTRIBUTES.each { |k,v| instance_variable_set(:"@#{k}", @options.fetch(k, v)) }
|
92
105
|
end
|
93
106
|
|
94
107
|
def validate(key, &block)
|
95
108
|
raise unless block.call(instance_variable_get(:"@#{key}"))
|
96
|
-
rescue
|
97
|
-
raise ArgumentError.new(key)
|
109
|
+
rescue => e
|
110
|
+
raise ArgumentError.new([key, e.message].join(': '))
|
98
111
|
end
|
99
112
|
|
100
113
|
def validate_attributes
|
101
|
-
validate(:period) { |v| PERIODS_IN_MONTHS.keys.include?(v) }
|
114
|
+
validate(:period) { |v| PERIODS_IN_MONTHS.keys.include?(v) } unless term_dates?
|
102
115
|
validate(:amount) { |v| v.is_a?(BigDecimal) && v > 0 }
|
103
116
|
validate(:annual_interests_rate) { |v| v.is_a?(BigDecimal) && v >= 0 }
|
104
117
|
validate(:starts_on) { |v| v.is_a?(Date) }
|
105
118
|
validate(:duration_in_periods) { |v| v.is_a?(Integer) && v > 0 }
|
106
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
|
+
)
|
107
130
|
end
|
108
131
|
|
109
132
|
def validate_initial_values
|
@@ -210,7 +233,7 @@ module LoanCreator
|
|
210
233
|
end
|
211
234
|
|
212
235
|
def term_zero?
|
213
|
-
interests_start_date && interests_start_date < term_zero_date
|
236
|
+
(interests_start_date && interests_start_date < term_zero_date) && !term_dates?
|
214
237
|
end
|
215
238
|
|
216
239
|
def compute_realistic_periodic_interests_rate_percentage_for(date, relative_to_date:)
|
@@ -222,7 +245,29 @@ module LoanCreator
|
|
222
245
|
end
|
223
246
|
|
224
247
|
def realistic_durations?
|
225
|
-
|
248
|
+
term_dates? || @realistic_durations.present?
|
249
|
+
end
|
250
|
+
|
251
|
+
def required_attributes
|
252
|
+
if term_dates?
|
253
|
+
REQUIRED_ATTRIBUTES_TERM_DATES
|
254
|
+
else
|
255
|
+
REQUIRED_ATTRIBUTES
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def term_dates?
|
260
|
+
@options[:term_dates].present?
|
261
|
+
end
|
262
|
+
|
263
|
+
def prepare_custom_term_dates
|
264
|
+
term_dates = @options[:term_dates].each_with_index.with_object({}) do |(term_date, index), obj|
|
265
|
+
obj[index + 1] = term_date
|
266
|
+
end
|
267
|
+
|
268
|
+
term_dates[0] = starts_on
|
269
|
+
@_timetable_term_dates = term_dates
|
270
|
+
@realistic_durations = true
|
226
271
|
end
|
227
272
|
|
228
273
|
def compute_period_generated_interests(interests_rate)
|
@@ -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
|
data/lib/loan_creator/version.rb
CHANGED
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.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- thibaulth
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: exe
|
14
14
|
cert_chain: []
|
15
|
-
date: 2021-05-
|
15
|
+
date: 2021-05-07 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bundler
|
@@ -177,6 +177,7 @@ files:
|
|
177
177
|
- lib/loan_creator/linear.rb
|
178
178
|
- lib/loan_creator/standard.rb
|
179
179
|
- lib/loan_creator/term.rb
|
180
|
+
- lib/loan_creator/term_dates_validator.rb
|
180
181
|
- lib/loan_creator/timetable.rb
|
181
182
|
- lib/loan_creator/uncapitalized_bullet.rb
|
182
183
|
- lib/loan_creator/version.rb
|