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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f175586a927e2dd609903b79ed629c1ab5bc3aecde02b1cc5809629d33c5b852
4
- data.tar.gz: fd695b7fba80c64efa7b5c0cefc9a1092ed6c74aa4ac070da3cb25771740e3bf
3
+ metadata.gz: fb6c8e7e02bc1349e47dcb07602828d051cfa63ac7fc3e16394604f8dda05e27
4
+ data.tar.gz: d103c1e0524641e73b307702eec5bd1cf43029236a3d791805e4d62e7e0732d3
5
5
  SHA512:
6
- metadata.gz: 6de1a02fc201dd985c0c255ee7b96989d477723607015647f9608228174f9bfdd4aa7f667ea9d74425a251b4912dae0430e1edd84623b7a8c1953e28ce1797f9
7
- data.tar.gz: b3b3e8d4a1d007b1f8481238df1d021b5f597d2a46b887495f4099320dcd20711ccc2fb9e2ed2fa34775a99c33826c9de2d4a5b7ede12d7eb8e978d45df017f1
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, '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'
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
@@ -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
- REQUIRED_ATTRIBUTES.each { |k| raise ArgumentError.new(k) unless @options.fetch(k, nil) }
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
- REQUIRED_ATTRIBUTES.each { |k| instance_variable_set(:"@#{k}", @options.fetch(k)) }
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
- !!@realistic_durations
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
@@ -1,3 +1,3 @@
1
1
  module LoanCreator
2
- VERSION = '0.8.2'.freeze
2
+ VERSION = '0.9.0'.freeze
3
3
  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.8.2
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-05 00:00:00.000000000 Z
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