loan_creator 0.2.1 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c512fd6d25322111e992b4a6b8cbf24ed2ef492e
4
- data.tar.gz: 67a1fdc42b417b91ca5ef72b1aeb9e136aa95a68
3
+ metadata.gz: 8dff90f00c1b47c09fd2ac97d4a0e3a28e12fbd5
4
+ data.tar.gz: '078e7ad135c712ef0fb767208cc99557718b99b1'
5
5
  SHA512:
6
- metadata.gz: af89b4866718ebb10a136c9fb3e6a1c7d54830a398cb0c972677d5472dbe0541bc9c5eac32b137efdbd685fda7f5ef95b0807016fe0f161cc77e0b4287cb3677
7
- data.tar.gz: 3c1b146323de4de7d20f597af5ba58c9a509f0131a48b34a1a83a080f42102ae4017cccee613dd2ec63fd5ebc6fae4e6e4c1c7a817276a8a6c433839c1c02722
6
+ metadata.gz: e318e46a05d49caa450705cb5a6e234c7b3e497dad501961c1339638c0641cc76f88dafb00f07af80b9b9cdc6d5ba1d0b5e761133f863f7a6034d479ddb0f012
7
+ data.tar.gz: bc258a7bd58a783c033c7c81857b467966e2e4ee25b091cc73cf1de1f5ec94a94f889fb2c32f5be072668503295564a841beb0573585c509b867dc9b0025cbe1
@@ -0,0 +1,45 @@
1
+ v0.6.2
2
+ -------------------------
3
+
4
+ - add `:initial_values` for loans initialization
5
+ - add and compute `capitalized_interests` for `LoanCreator::Bullet` terms
6
+
7
+ v0.6.1
8
+ -------------------------
9
+
10
+ - fix homepage url
11
+
12
+ v0.6.0
13
+ -------------------------
14
+
15
+ - add `LoanCreator::UncapitalizedBullet`
16
+
17
+ v0.5.0
18
+ -------------------------
19
+
20
+ - add `interests_start_date` in `LoanCreator::Common` attributes, replacing `first_term_date`
21
+
22
+ v0.3.0
23
+ -------------------------
24
+
25
+ - add `first_term_date` in `LoanCreator::Common` attributes
26
+
27
+ v0.2.3
28
+ -------------------------
29
+
30
+ - rename `starts_at` -> `starts_on`
31
+
32
+ v0.2.2
33
+ -------------------------
34
+
35
+ - rename `date` -> `due_on`
36
+
37
+ v0.2.1
38
+ -------------------------
39
+
40
+ - convert some options to their expected type by default
41
+
42
+ v0.2.0
43
+ -------------------------
44
+
45
+ - Huge rework : add `period`, rename `amount_in_cents` to `amount` and other breaking changes.
@@ -0,0 +1,73 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, sex characteristics, gender identity and expression,
9
+ level of experience, education, socio-economic status, nationality, personal
10
+ appearance, race, religion, or sexual identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at development@capsens.eu. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72
+
73
+ [homepage]: https://www.contributor-covenant.org
Binary file
data/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  `loan_creator` gem intends to provide a set of methods to allow automatic generation of loan timetables, for simulation, from a lender point of view and from a borrower point of view, regarding financial rounding differences. As of today, the gem makes the borrower support any rounding issue. In a later work, an option should be provided to decide who supports such issues.
4
4
 
5
- Link to Loan_Creator excel simulator [Click here] (Excel)
6
-
7
5
  ## Installation
8
6
 
9
7
  Add this line to your application's Gemfile:
@@ -35,6 +33,7 @@ There are four types of loans. All inherit from a `LoanCreator::Common` class.
35
33
  LoanCreator::Linear
36
34
  LoanCreator::InFine
37
35
  LoanCreator::Bullet
36
+ LoanCreator::UncapitalizedBullet
38
37
  ```
39
38
 
40
39
  Each instance of one of the previous classes has the following attributes:
@@ -46,6 +45,20 @@ Each instance of one of the previous classes has the following attributes:
46
45
  :starts_at
47
46
  :duration_in_periods
48
47
  :deferred_in_periods (default to zero)
48
+ :interests_start_date (optional)
49
+ :initial_values (to generate a timetable from a previous term or at a given state)
50
+ ```
51
+
52
+ Initial values must be a hash with specific keys, like so:
53
+
54
+ ```ruby
55
+ {
56
+ paid_capital: 0,
57
+ paid_interests: 11000.0,
58
+ accrued_delta_interests: 0,
59
+ starting_index: 2,
60
+ capitalized_interests: 0
61
+ }
49
62
  ```
50
63
 
51
64
  There is also a `LoanCreator::Timetable` class dedicated to record the data of the loans' terms. Each instance of `LoanCreator::Timetable` represents an array of `LoanCreator::Term` records, each having the following attributes:
@@ -55,7 +68,7 @@ There is also a `LoanCreator::Timetable` class dedicated to record the data of t
55
68
  :index
56
69
 
57
70
  # Term date
58
- :date
71
+ :due_on
59
72
 
60
73
  # Remaining due capital at the beginning of the term
61
74
  :crd_beginning_of_period
@@ -99,8 +112,29 @@ support all those differences.
99
112
  `.borrower_timetable(*lenders_timetables)` (class method) intends to sum each attribute of each provided `lender_timetable` on each term and thus to provide an ascending order array of `LoanCreator::Term`. It should be used for the borrower of a loan, once all lenders and their lending amounts
100
113
  are known. It makes the borrower support all financial rounding differences.
101
114
 
115
+ ## Example
116
+
117
+ ```ruby
118
+ loan_creator = LoanCreator::Standard.new(
119
+ period: :year,
120
+ amount: 42_000,
121
+ annual_interests_rate: 4,
122
+ starts_on: '2019-03-01',
123
+ duration_in_periods: 5,
124
+ deferred_in_periods: 1,
125
+ interests_start_date: '2019-02-10'
126
+ )
127
+ loan_creator.lender_timetable
128
+ # => #<LoanCreator::Timetable:0x0000000003198bd0 @terms=[...] ...>
129
+ loan_creator.lender_timetable.terms.first
130
+ # => #<LoanCreator::Term:0x00000000030f1a88 @crd_beginning_of_period=0.42e5,
131
+ # [...] @period_amount_to_pay=0.8745e2, @index=0, @due_on=Sun, 10 Feb 2019>
132
+ ````
133
+
102
134
  ## Explanation
103
135
 
136
+ ### Classes
137
+
104
138
  `Standard` loan generates terms with constant payments.
105
139
 
106
140
  `Linear` loan generates terms with constant capital share payment.
@@ -115,8 +149,35 @@ Capital share shall be repaid in full at loan's end.
115
149
  Interests are capitalized, i.e. added to the borrowed capital on each term.\
116
150
  Capital share shall be repaid in full and all interests paid at loan's end.
117
151
 
152
+ `UncapitalizedBullet` same as bullet, the only difference is the interests\
153
+ are NOT capitalized.
154
+
118
155
  There is no deferred time for `InFine` and `Bullet` loans as it would be equivalent to increasing loan's duration.
119
156
 
157
+ ### Attributes
158
+
159
+ `period`: A `Symbol`. `:month`, `:quarter`, `:semester` or `:year`.
160
+
161
+ `duration_in_periods`: An `Integer`.
162
+
163
+ `amount`: Any number that can be converted to `BigDecimal`.
164
+
165
+ `annual_interests_rate`: In percentage. Any number that can be converted to `BigDecimal`.
166
+
167
+ `starts_at`: A `Date`, or a `String` that can be parsed.
168
+
169
+ `deferred_in_periods`: Optional. An `Integer`, smaller than `duration_in_periods`. Number of periods during which no
170
+ capital is refunded, only interest. Only relevant for `Standard` and `Linear` loans.
171
+
172
+ `interests_start_date`: Optional. To be used when the loan starts before the first full term date. This then compute an
173
+ additional term with only interests for the time difference.
174
+ For example, with a `start_at` in january 2020 and a `interests_start_date` in october 2019, the timetable will include a
175
+ first term corresponding to 3 months of interests.
176
+
177
+ ## Calculation
178
+
179
+ An excel simulator for standard case can be found [here](CapSens_Loan.xlsx).
180
+
120
181
  ## Development
121
182
 
122
183
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -125,7 +186,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
125
186
 
126
187
  ## Contributing
127
188
 
128
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/loan_creator.
189
+ Bug reports and pull requests are welcome on GitHub at https://github.com/CapSens/loan_creator.
129
190
 
130
191
 
131
192
  ## License
@@ -7,13 +7,14 @@ 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'
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'
19
20
  end
@@ -18,7 +18,7 @@ module LoanCreator
18
18
  end
19
19
 
20
20
  borrower_timetable = LoanCreator::Timetable.new(
21
- starts_at: lenders_timetables.first.starts_at,
21
+ starts_on: lenders_timetables.first.starts_on,
22
22
  period: lenders_timetables.first.period
23
23
  )
24
24
 
@@ -2,11 +2,12 @@ module LoanCreator
2
2
  class Bullet < LoanCreator::Common
3
3
  def lender_timetable
4
4
  raise ArgumentError.new(:deferred_in_periods) unless deferred_in_periods == 0
5
+ raise ArgumentError.new(:interests_start_date) unless interests_start_date.nil?
5
6
  timetable = new_timetable
6
7
  reset_current_term
7
8
  @crd_beginning_of_period = amount
8
9
  @crd_end_of_period = amount
9
- (duration_in_periods - 1).times { timetable << current_term }
10
+ (duration_in_periods - 1).times { |period| compute_term(timetable, period + 1) }
10
11
  compute_last_term
11
12
  timetable << current_term
12
13
  timetable
@@ -15,25 +16,22 @@ module LoanCreator
15
16
  private
16
17
 
17
18
  def compute_last_term
18
- @crd_end_of_period = bigd('0')
19
- @period_interests = total_interests
20
- @period_capital = @crd_beginning_of_period
21
- @total_paid_capital_end_of_period = @period_capital
19
+ @crd_end_of_period = bigd('0')
20
+ @period_interests = compute_capitalized_interests(duration_in_periods)
21
+ @period_capital = @crd_beginning_of_period
22
+ @total_paid_capital_end_of_period = @period_capital
22
23
  @total_paid_interests_end_of_period = @period_interests
23
- @period_amount_to_pay = @period_capital + @period_interests
24
+ @period_amount_to_pay = @period_capital + @period_interests
25
+ @capitalized_interests = compute_capitalized_interests(duration_in_periods)
24
26
  end
25
27
 
26
- # Capital * (periodic_interests_rate ^(total_terms))
27
- #
28
- def total_payment
29
- amount.mult(
30
- (bigd(1) + periodic_interests_rate) ** bigd(duration_in_periods),
31
- BIG_DECIMAL_DIGITS
32
- )
28
+ def compute_capitalized_interests(period)
29
+ amount.mult((bigd(1) + periodic_interests_rate) ** period, BIG_DECIMAL_DIGITS) - amount
33
30
  end
34
31
 
35
- def total_interests
36
- total_payment - amount
32
+ def compute_term(timetable, period)
33
+ @capitalized_interests = compute_capitalized_interests(period)
34
+ timetable << current_term
37
35
  end
38
36
  end
39
37
  end
@@ -13,13 +13,15 @@ module LoanCreator
13
13
  :period,
14
14
  :amount,
15
15
  :annual_interests_rate,
16
- :starts_at,
16
+ :starts_on,
17
17
  :duration_in_periods
18
18
  ].freeze
19
19
 
20
20
  OPTIONAL_ATTRIBUTES = {
21
21
  # attribute: default_value
22
- deferred_in_periods: 0
22
+ deferred_in_periods: 0,
23
+ interests_start_date: nil,
24
+ initial_values: {}
23
25
  }.freeze
24
26
 
25
27
  attr_reader *REQUIRED_ATTRIBUTES
@@ -31,6 +33,8 @@ module LoanCreator
31
33
  reinterpret_attributes
32
34
  set_attributes
33
35
  validate_attributes
36
+ set_initial_values
37
+ validate_initial_values
34
38
  end
35
39
 
36
40
  def periodic_interests_rate_percentage
@@ -48,7 +52,7 @@ module LoanCreator
48
52
  end
49
53
 
50
54
  def self.bigd(value)
51
- BigDecimal.new(value, BIG_DECIMAL_DIGITS)
55
+ BigDecimal(value, BIG_DECIMAL_DIGITS)
52
56
  end
53
57
 
54
58
  def bigd(value)
@@ -65,6 +69,8 @@ module LoanCreator
65
69
  @options[:period] = @options[:period].to_sym
66
70
  @options[:amount] = bigd(@options[:amount])
67
71
  @options[:annual_interests_rate] = bigd(@options[:annual_interests_rate])
72
+ @options[:starts_on] = Date.parse(@options[:starts_on]) if @options[:starts_on].is_a?(String)
73
+ @options[:interests_start_date] = Date.parse(@options[:interests_start_date]) if @options[:interests_start_date].is_a?(String)
68
74
  end
69
75
 
70
76
  def set_attributes
@@ -82,43 +88,108 @@ module LoanCreator
82
88
  validate(:period) { |v| PERIODS_IN_MONTHS.keys.include?(v) }
83
89
  validate(:amount) { |v| v.is_a?(BigDecimal) && v > 0 }
84
90
  validate(:annual_interests_rate) { |v| v.is_a?(BigDecimal) && v >= 0 }
85
- validate(:starts_at) { |v| !!Date.parse(v) }
91
+ validate(:starts_on) { |v| v.is_a?(Date) }
86
92
  validate(:duration_in_periods) { |v| v.is_a?(Integer) && v > 0 }
87
93
  validate(:deferred_in_periods) { |v| v.is_a?(Integer) && v >= 0 && v < duration_in_periods }
88
94
  end
89
95
 
96
+ def validate_initial_values
97
+ return if initial_values.blank?
98
+
99
+ validate(:total_paid_capital_end_of_period) { |v| v.is_a?(BigDecimal) && v >= 0 }
100
+ validate(:total_paid_interests_end_of_period) { |v| v.is_a?(BigDecimal) && v >= 0 }
101
+ validate(:accrued_delta_interests) { |v| v.is_a?(BigDecimal) }
102
+ validate(:starting_index) { |v| v.is_a?(Integer) && v >= 0 }
103
+ end
104
+
105
+ def set_initial_values
106
+ @starting_index = initial_values[:starting_index] || 1
107
+
108
+ return if initial_values.blank?
109
+
110
+ (@total_paid_capital_end_of_period = bigd(initial_values[:paid_capital]))
111
+ (@total_paid_interests_end_of_period = bigd(initial_values[:paid_interests]))
112
+ (@accrued_delta_interests = bigd(initial_values[:accrued_delta_interests]))
113
+ end
114
+
90
115
  def reset_current_term
91
- @crd_beginning_of_period = bigd('0')
92
- @crd_end_of_period = bigd('0')
93
- @period_theoric_interests = bigd('0')
94
- @delta_interests = bigd('0')
95
- @accrued_delta_interests = bigd('0')
96
- @amount_to_add = bigd('0')
97
- @period_interests = bigd('0')
98
- @period_capital = bigd('0')
99
- @total_paid_capital_end_of_period = bigd('0')
100
- @total_paid_interests_end_of_period = bigd('0')
101
- @period_amount_to_pay = bigd('0')
116
+ @crd_beginning_of_period = bigd('0')
117
+ @crd_end_of_period = bigd('0')
118
+ @period_theoric_interests = bigd('0')
119
+ @capitalized_interests = bigd('0')
120
+ @delta_interests = bigd('0')
121
+ @accrued_delta_interests = @accrued_delta_interests || bigd('0')
122
+ @amount_to_add = bigd('0')
123
+ @period_interests = bigd('0')
124
+ @period_capital = bigd('0')
125
+ @total_paid_capital_end_of_period = @total_paid_capital_end_of_period || bigd('0')
126
+ @total_paid_interests_end_of_period = @total_paid_interests_end_of_period || bigd('0')
127
+ @period_amount_to_pay = bigd('0')
128
+ @due_on = nil
102
129
  end
103
130
 
104
131
  def current_term
105
132
  LoanCreator::Term.new(
106
- crd_beginning_of_period: @crd_beginning_of_period,
107
- crd_end_of_period: @crd_end_of_period,
108
- period_theoric_interests: @period_theoric_interests,
109
- delta_interests: @delta_interests,
110
- accrued_delta_interests: @accrued_delta_interests,
111
- amount_to_add: @amount_to_add,
112
- period_interests: @period_interests,
113
- period_capital: @period_capital,
114
- total_paid_capital_end_of_period: @total_paid_capital_end_of_period,
133
+ crd_beginning_of_period: @crd_beginning_of_period,
134
+ crd_end_of_period: @crd_end_of_period,
135
+ period_theoric_interests: @period_theoric_interests,
136
+ delta_interests: @delta_interests,
137
+ accrued_delta_interests: @accrued_delta_interests,
138
+ capitalized_interests: @capitalized_interests,
139
+ amount_to_add: @amount_to_add,
140
+ period_interests: @period_interests,
141
+ period_capital: @period_capital,
142
+ total_paid_capital_end_of_period: @total_paid_capital_end_of_period,
115
143
  total_paid_interests_end_of_period: @total_paid_interests_end_of_period,
116
- period_amount_to_pay: @period_amount_to_pay
144
+ period_amount_to_pay: @period_amount_to_pay,
145
+ due_on: @due_on,
146
+ index: compute_index
117
147
  )
118
148
  end
119
149
 
120
150
  def new_timetable
121
- LoanCreator::Timetable.new(starts_at: starts_at, period: period)
151
+ LoanCreator::Timetable.new(
152
+ starts_on: starts_on,
153
+ period: period,
154
+ interests_start_date: interests_start_date,
155
+ starting_index: @starting_index
156
+ )
157
+ end
158
+
159
+ def compute_index
160
+ @index ? (@starting_index + @index - 1) : nil
161
+ end
162
+
163
+ def compute_term_zero
164
+ @crd_beginning_of_period = @crd_end_of_period
165
+ @period_theoric_interests = term_zero_interests
166
+ @delta_interests = @period_theoric_interests - @period_theoric_interests.round(2)
167
+ @accrued_delta_interests += @delta_interests
168
+ @period_interests = @period_theoric_interests.round(2)
169
+ @total_paid_interests_end_of_period += @period_interests
170
+ @period_amount_to_pay = @period_interests
171
+ @index = 0
172
+ end
173
+
174
+ def term_zero_interests
175
+ @crd_beginning_of_period * term_zero_interests_rate
176
+ end
177
+
178
+ def term_zero_interests_rate
179
+ term_zero_interests_rate_percentage = (annual_interests_rate * term_zero_duration).div(365, BIG_DECIMAL_DIGITS)
180
+ term_zero_interests_rate_percentage.div(100, BIG_DECIMAL_DIGITS)
181
+ end
182
+
183
+ def term_zero_duration
184
+ (term_zero_date - interests_start_date).to_i
185
+ end
186
+
187
+ def term_zero_date
188
+ starts_on.advance(months: -PERIODS_IN_MONTHS.fetch(@period))
189
+ end
190
+
191
+ def term_zero?
192
+ interests_start_date && interests_start_date < term_zero_date
122
193
  end
123
194
  end
124
195
  end
@@ -1,12 +1,16 @@
1
1
  # Source: https://gist.github.com/mattetti/1015948
2
2
 
3
3
  module LoanCreator::ExcelFormulas
4
+ # Returns the interest payment
5
+ # for a given period for an investment based on periodic, constant payments and a constant interest rate.
4
6
  def ipmt(rate, per, nper, pv, fv=0, type=0)
5
7
  p = _pmt(rate, nper, pv, fv, 0);
6
8
  ip = -(pv * _pow1p(rate, per - 1) * rate + p * _pow1pm1(rate, per - 1))
7
9
  (type == 0) ? ip : ip / (1 + rate)
8
10
  end
9
11
 
12
+ # Returns the payment on the principal
13
+ # for a given period for an investment based on periodic, constant payments and a constant interest rate.
10
14
  def ppmt(rate, per, nper, pv, fv=0, type=0)
11
15
  p = _pmt(rate, nper, pv, fv, type)
12
16
  ip = ipmt(rate, per, nper, pv, fv, type)
@@ -3,9 +3,7 @@ module LoanCreator
3
3
  # InFine is the same as a Linear loan with (duration - 1) deferred periods.
4
4
  # Thus we're generating a Linear loan instead of rewriting already existing code.
5
5
  def lender_timetable
6
- raise ArgumentError.new(:deferred_in_periods) unless deferred_in_periods == 0
7
- options = { deferred_in_periods: duration_in_periods - 1 }
8
- options = REQUIRED_ATTRIBUTES.each_with_object(options) { |k,h| h[k] = send(k) }
6
+ options = @options.merge(deferred_in_periods: duration_in_periods - 1)
9
7
  LoanCreator::Linear.new(options).lender_timetable
10
8
  end
11
9
  end
@@ -5,12 +5,19 @@ module LoanCreator
5
5
  timetable = new_timetable
6
6
  reset_current_term
7
7
  @crd_end_of_period = amount
8
+
9
+ if term_zero?
10
+ compute_term_zero
11
+ timetable << current_term
12
+ end
13
+
8
14
  duration_in_periods.times do |idx|
9
15
  @last_period = last_period?(idx)
10
16
  @deferred_period = idx < deferred_in_periods
11
- compute_current_term
17
+ compute_current_term(idx)
12
18
  timetable << current_term
13
19
  end
20
+
14
21
  timetable
15
22
  end
16
23
 
@@ -20,7 +27,7 @@ module LoanCreator
20
27
  idx == (duration_in_periods - 1)
21
28
  end
22
29
 
23
- def compute_current_term
30
+ def compute_current_term(idx)
24
31
  # Reminder: CRD beginning of period = CRD end of period **of previous period**
25
32
  @crd_beginning_of_period = @crd_end_of_period
26
33
  @period_theoric_interests = @crd_beginning_of_period * periodic_interests_rate
@@ -42,6 +49,8 @@ module LoanCreator
42
49
  @total_paid_interests_end_of_period += @period_interests
43
50
  @period_amount_to_pay = @period_interests + @period_capital
44
51
  @crd_end_of_period -= @period_capital
52
+ @due_on = nil
53
+ @index = idx + 1
45
54
  end
46
55
 
47
56
  def period_capital
@@ -6,6 +6,12 @@ module LoanCreator
6
6
  timetable = new_timetable
7
7
  reset_current_term
8
8
  @crd_end_of_period = amount
9
+
10
+ if term_zero?
11
+ compute_term_zero
12
+ timetable << current_term
13
+ end
14
+
9
15
  duration_in_periods.times do |idx|
10
16
  @last_period = last_period?(idx)
11
17
  @deferred_period = idx < deferred_in_periods
@@ -42,6 +48,8 @@ module LoanCreator
42
48
  @total_paid_interests_end_of_period += @period_interests
43
49
  @period_amount_to_pay = @period_interests + @period_capital
44
50
  @crd_end_of_period -= @period_capital
51
+ @due_on = nil
52
+ @index = idx + 1
45
53
  end
46
54
 
47
55
  def period_theoric_interests(idx)
@@ -17,6 +17,9 @@ module LoanCreator
17
17
  # Accrued interests' delta
18
18
  :accrued_delta_interests,
19
19
 
20
+ # Capitalized interests (Bullet only)
21
+ :capitalized_interests,
22
+
20
23
  # Adjustment of -0.01, 0 or +0.01 cent depending on accrued_delta_interests
21
24
  :amount_to_add,
22
25
 
@@ -36,23 +39,23 @@ module LoanCreator
36
39
  :period_amount_to_pay
37
40
  ].freeze
38
41
 
39
- ATTRIBUTES = [
42
+ OPTIONAL_ARGUMENTS = [
40
43
  # Term number (starts at 1)
41
44
  # This value is to be set by Timetable
42
45
  :index,
43
46
 
44
47
  # Term date
45
48
  # This value is to be set by Timetable
46
- :date,
49
+ :due_on,
50
+ ]
47
51
 
48
- # These values are to be specified during Term's initialization
49
- *ARGUMENTS
50
- ].freeze
52
+ ATTRIBUTES = (ARGUMENTS + OPTIONAL_ARGUMENTS).freeze
51
53
 
52
54
  attr_accessor *ATTRIBUTES
53
55
 
54
56
  def initialize(**options)
55
57
  ARGUMENTS.each { |k| instance_variable_set(:"@#{k}", options.fetch(k)) }
58
+ OPTIONAL_ARGUMENTS.each { |k| instance_variable_set(:"@#{k}", options.fetch(k, nil)) }
56
59
  end
57
60
 
58
61
  def to_csv
@@ -3,39 +3,35 @@ module LoanCreator
3
3
  class Timetable
4
4
  # Used to calculate next term's date (see ActiveSupport#advance)
5
5
  PERIODS = {
6
- month: { months: 1 },
7
- quarter: { months: 3 },
8
- semester: { months: 6 },
9
- year: { years: 1 }
6
+ month: {months: 1},
7
+ quarter: {months: 3},
8
+ semester: {months: 6},
9
+ year: {years: 1}
10
10
  }
11
11
 
12
- attr_reader :terms, :starts_at, :period
12
+ attr_reader :terms, :starts_on, :period #, :interests_start_date
13
13
 
14
- def initialize(starts_at:, period:)
15
- @terms = []
16
- @starts_at = (Date === starts_at ? starts_at : Date.parse(starts_at))
14
+ def initialize(starts_on:, period:, interests_start_date: nil, starting_index: 1)
17
15
  raise ArgumentError.new(:period) unless PERIODS.keys.include?(period)
18
- @period = period
16
+
17
+ @terms = []
18
+ @starts_on = (starts_on.is_a?(Date) ? starts_on : Date.parse(starts_on))
19
+ @period = period
20
+ @starting_index = starting_index
21
+
22
+ if interests_start_date
23
+ @interests_start_date = (interests_start_date.is_a?(Date) ? interests_start_date : Date.parse(interests_start_date))
24
+ end
19
25
  end
20
26
 
21
27
  def <<(term)
22
- raise ArgumentError.new('LoanCreator::Term expected') unless LoanCreator::Term === term
23
- term.index = autoincrement_index
24
- term.date = autoincrement_date
28
+ raise ArgumentError.new('LoanCreator::Term expected') unless term.is_a?(LoanCreator::Term)
29
+ term.index ||= autoincrement_index
30
+ term.due_on ||= date_for(term.index)
25
31
  @terms << term
26
32
  self
27
33
  end
28
34
 
29
- def reset_indexes_and_dates
30
- @autoincrement_index = 0
31
- @autoincrement_date = @starts_at
32
- @terms.each do |term|
33
- term[:index] = autoincrement_index
34
- term[:date] = autoincrement_date
35
- end
36
- self
37
- end
38
-
39
35
  def to_csv(header: true)
40
36
  output = []
41
37
  output << terms.first.to_h.keys.join(',') if header
@@ -43,20 +39,33 @@ module LoanCreator
43
39
  output
44
40
  end
45
41
 
42
+ def term(index)
43
+ @terms.find { |term| term.index == index }
44
+ end
45
+
46
46
  private
47
47
 
48
- # First term index of a timetable term is 1
49
48
  def autoincrement_index
50
- @autoincrement_index ||= 0
51
- @autoincrement_index += 1
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]
52
65
  end
53
66
 
54
- # First term date of timetable term is the starts_at given date
55
- def autoincrement_date
56
- @autoincrement_date ||= @starts_at
57
- date = @autoincrement_date
58
- @autoincrement_date = @autoincrement_date.advance(PERIODS.fetch(@period))
59
- date
67
+ def reset_dates
68
+ @_dates = nil
60
69
  end
61
70
  end
62
71
  end
@@ -0,0 +1,34 @@
1
+ module LoanCreator
2
+ class UncapitalizedBullet < LoanCreator::Common
3
+ def lender_timetable
4
+ raise ArgumentError.new(:deferred_in_periods) unless deferred_in_periods == 0
5
+ raise ArgumentError.new(:interests_start_date) unless interests_start_date.nil?
6
+ timetable = new_timetable
7
+ reset_current_term
8
+ @crd_beginning_of_period = amount
9
+ @crd_end_of_period = amount
10
+ (duration_in_periods - 1).times { timetable << current_term }
11
+ compute_last_term
12
+ timetable << current_term
13
+ timetable
14
+ end
15
+
16
+ private
17
+
18
+ def compute_last_term
19
+ @crd_end_of_period = bigd('0')
20
+ @period_interests = total_interests
21
+ @period_capital = @crd_beginning_of_period
22
+ @total_paid_capital_end_of_period = @period_capital
23
+ @total_paid_interests_end_of_period = @period_interests
24
+ @period_amount_to_pay = @period_capital + @period_interests
25
+ end
26
+
27
+ def total_interests
28
+ amount.mult(
29
+ bigd(periodic_interests_rate) * bigd(duration_in_periods),
30
+ BIG_DECIMAL_DIGITS
31
+ )
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module LoanCreator
2
- VERSION = '0.2.1'.freeze
2
+ VERSION = '0.6.2'.freeze
3
3
  end
@@ -5,11 +5,11 @@ require 'loan_creator/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'loan_creator'
7
7
  spec.version = LoanCreator::VERSION
8
- spec.authors = %w[thibaulth nicob younes.serraj]
9
- spec.email = ['thibault@capsens.eu', 'nicolas.besnard@capsens.eu', 'younes.serraj@gmail.com']
8
+ spec.authors = ["thibaulth", "nicob", "younes.serraj", "Antoine Becquet", "Jerome Drevet"]
9
+ spec.email = ['thibault@capsens.eu', 'nicolas.besnard@capsens.eu', 'younes.serraj@gmail.com', "antoine@capsens.eu", "jerome@capsens.eu"]
10
10
 
11
11
  spec.summary = 'Create and update timetables from input data'
12
- spec.homepage = 'https://capsens.githost.io/capsens/loan-creator'
12
+ spec.homepage = 'https://github.com/CapSens/loan-creator'
13
13
  spec.license = 'MIT'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
metadata CHANGED
@@ -1,16 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loan_creator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - thibaulth
8
8
  - nicob
9
9
  - younes.serraj
10
+ - Antoine Becquet
11
+ - Jerome Drevet
10
12
  autorequire:
11
13
  bindir: exe
12
14
  cert_chain: []
13
- date: 2020-09-16 00:00:00.000000000 Z
15
+ date: 2020-12-08 00:00:00.000000000 Z
14
16
  dependencies:
15
17
  - !ruby/object:Gem::Dependency
16
18
  name: bundler
@@ -115,6 +117,8 @@ email:
115
117
  - thibault@capsens.eu
116
118
  - nicolas.besnard@capsens.eu
117
119
  - younes.serraj@gmail.com
120
+ - antoine@capsens.eu
121
+ - jerome@capsens.eu
118
122
  executables: []
119
123
  extensions: []
120
124
  extra_rdoc_files: []
@@ -126,6 +130,9 @@ files:
126
130
  - ".ruby-gemset"
127
131
  - ".ruby-version"
128
132
  - ".travis.yml"
133
+ - CHANGELOG.md
134
+ - CODE_OF_CONDUCT.md
135
+ - CapSens_Loan.xlsx
129
136
  - Gemfile
130
137
  - LICENSE.txt
131
138
  - README.md
@@ -143,9 +150,10 @@ files:
143
150
  - lib/loan_creator/standard.rb
144
151
  - lib/loan_creator/term.rb
145
152
  - lib/loan_creator/timetable.rb
153
+ - lib/loan_creator/uncapitalized_bullet.rb
146
154
  - lib/loan_creator/version.rb
147
155
  - loan_creator.gemspec
148
- homepage: https://capsens.githost.io/capsens/loan-creator
156
+ homepage: https://github.com/CapSens/loan-creator
149
157
  licenses:
150
158
  - MIT
151
159
  metadata: {}