spreadsheet_loan_generator 0.1.6

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e8fb0a2c9eea77282750d497e0bd4e15bee2208f387e7f21d974b304870c20e0
4
+ data.tar.gz: e63010ba2a59739c4515073e36d8f778ff38fc59d697ce524e3a4d25bb7a580a
5
+ SHA512:
6
+ metadata.gz: 4ba69336288f6102ea17f5fbc7e7dbbe6f96e026c0af7d94c421630f9c7ca2af104ddf878d46b10a7a5c92bc640d613ef8c0cd6104cfdd57084c46b73a11f789
7
+ data.tar.gz: 27ef8c9a73208846e86dae009452cc6509ac0d593d6f8c8485744a8c86e84131fe1148efef781498b091dbc4fad93132ef52334c98b7a6bcac3688c1c838b4f4
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ Gemfile.lock
14
+
15
+ *.csv
16
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.2
6
+ before_install: gem install bundler -v 2.1.4
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spreadsheet_loan_generator.gemspec
4
+ gemspec
5
+
6
+ gem 'rake', '~> 12.0'
7
+ gem 'rspec', '~> 3.0'
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # SpreadsheetLoanGenerator
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/spreadsheet_loan_generator`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'spreadsheet_loan_generator'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install spreadsheet_loan_generator
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/spreadsheet_loan_generator.
36
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "spreadsheet_loan_generator"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/slg ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/spreadsheet_loan_generator'
4
+
5
+ Dry::CLI.new(SpreadsheetLoanGenerator).call
@@ -0,0 +1,29 @@
1
+ module SpreadsheetLoanGenerator
2
+ module CsvConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def generate_csv(worksheet:, target_path:)
7
+ filename = File.join(target_path, "#{loan.name}.csv")
8
+ CSV.open(filename, 'wb') do |csv|
9
+ loan.duration.times do |line|
10
+ row = []
11
+ columns.each.with_index do |name, column|
12
+ row << (
13
+ case name
14
+ when 'index'
15
+ worksheet[line + 2, column + 1]
16
+ when 'due_on'
17
+ Date.parse(worksheet[line + 2, column + 1]).strftime('%m/%d/%Y')
18
+ else
19
+ worksheet[line + 2, column + 1].gsub(',', '.').to_f
20
+ end
21
+ )
22
+ end
23
+ csv << row
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,111 @@
1
+ module SpreadsheetLoanGenerator
2
+ module SpreadsheetConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ def columns
7
+ %w[
8
+ index
9
+ due_on
10
+ remaining_capital_start
11
+ remaining_capital_end
12
+ period_theoric_interests
13
+ delta
14
+ accrued_delta
15
+ amount_to_add
16
+ period_interests
17
+ period_capital
18
+ total_paid_capital_end_of_period
19
+ total_paid_interests_end_of_period
20
+ period_total
21
+ capitalized_interests_start
22
+ capitalized_interests_end
23
+ period_rate
24
+ period_calculated_capital
25
+ period_calculated_interests
26
+ period_reimbursed_capitalized_interests
27
+ period_leap_days
28
+ period_non_leap_days
29
+ ]
30
+ end
31
+
32
+ def currency_columns
33
+ %w[
34
+ remaining_capital_start
35
+ remaining_capital_end
36
+ amount_to_add
37
+ period_interests
38
+ period_capital
39
+ total_paid_capital_end_of_period
40
+ total_paid_interests_end_of_period
41
+ period_total
42
+ capitalized_interests_start
43
+ capitalized_interests_end
44
+ period_reimbursed_capitalized_interests
45
+ ]
46
+ end
47
+
48
+ def precise_columns
49
+ %w[
50
+ period_theoric_interests
51
+ period_calculated_interests
52
+ period_calculated_capital
53
+ delta
54
+ accrued_delta
55
+ period_rate
56
+ ]
57
+ end
58
+
59
+ def column_letter(column)
60
+ ('A'..'ZZ').to_a[columns.index(column)]
61
+ end
62
+
63
+ def apply_formats(worksheet:)
64
+ precise_columns.each do |column|
65
+ index = columns.index(column) + 1
66
+ worksheet.set_number_format(1, index, loan.duration + 1, 1, '0.00000000')
67
+ end
68
+ currency_columns.each do |column|
69
+ index = columns.index(column) + 1
70
+ worksheet.set_number_format(1, index, loan.duration + 1, 1, '0.00')
71
+ end
72
+ end
73
+
74
+ def apply_formulas(worksheet:)
75
+ columns.each.with_index do |title, column|
76
+ worksheet[1, column + 1] = title
77
+ end
78
+ loan.duration.times do |line|
79
+ columns.each.with_index do |title, column|
80
+ worksheet[line + 2, column + 1] = @formula.send("#{title}_formula", line: line + 2)
81
+ end
82
+ end
83
+ end
84
+
85
+ def column_range(column: 'A', upto: , exclude_head: true)
86
+ start_line = exclude_head ? 2 : 1
87
+
88
+ "#{column}#{start_line}:#{column}#{upto}"
89
+ end
90
+
91
+ def index_to_line(index:)
92
+ index + 1 # first term is on line 2
93
+ end
94
+
95
+ def excel_float(float)
96
+ float.to_s.gsub('.', ',')
97
+ end
98
+
99
+ # used heavily in formula concern
100
+ def respond_to_missing?(method_name, include_private = false)
101
+ columns.include?(method_name.to_s) || super
102
+ end
103
+
104
+ def method_missing(method_name, *args, **kwargs)
105
+ return super unless respond_to_missing?(method_name)
106
+
107
+ "#{column_letter(method_name.to_s)}#{args.first}"
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,165 @@
1
+ module SpreadsheetLoanGenerator
2
+ class Formula
3
+ include SpreadsheetConcern
4
+
5
+ attr_accessor :loan
6
+
7
+ def initialize(loan:)
8
+ @loan = loan
9
+ @interests_formula = loan.interests_formula
10
+ @loan_type_formula = loan.loan_type_formula
11
+ end
12
+
13
+ def index_formula(line:)
14
+ line - 1
15
+ end
16
+
17
+ def due_on_formula(line:)
18
+ term = line - 1
19
+ loan.due_on + ((term - 1) * loan.period_duration).months
20
+ end
21
+
22
+ def period_capital_formula(line:)
23
+ if line == loan.duration + 1
24
+ "=ARRONDI(#{excel_float(loan.amount)} - #{total_paid_capital_end_of_period(line - 1)}; 2)"
25
+ else
26
+ "=ARRONDI(#{period_calculated_capital(line)} - #{period_reimbursed_capitalized_interests(line)}; 2)"
27
+ end
28
+ end
29
+
30
+ def period_interests_formula(line:)
31
+ term = line - 1
32
+ if term <= loan.deferred_and_capitalized
33
+ excel_float(0.0)
34
+ else
35
+ "=ARRONDI(#{period_calculated_interests(line)}; 2)"
36
+ end
37
+ end
38
+
39
+ def period_total_formula(line:)
40
+ total = [
41
+ period_capital(line),
42
+ '+',
43
+ period_interests(line),
44
+ '+',
45
+ period_reimbursed_capitalized_interests(line)
46
+ ].join(' ')
47
+ "=ARRONDI(#{total}; 2)"
48
+ end
49
+
50
+ def period_calculated_capital_formula(line:)
51
+ term = line - 1
52
+ if term <= loan.total_deferred_duration
53
+ excel_float(0.0)
54
+ else
55
+ @loan_type_formula.period_calculated_capital_formula(line: line)
56
+ end
57
+ end
58
+
59
+ def period_theoric_interests_formula(line:)
60
+ term = line - 1
61
+ if term <= loan.deferred_and_capitalized
62
+ excel_float(0.0)
63
+ else
64
+ "=#{period_calculated_interests(line)}"
65
+ end
66
+ end
67
+
68
+ def period_calculated_interests_formula(line:)
69
+ term = line - 1
70
+ if loan.type == 'standard' && loan.total_deferred_duration < term
71
+ return @loan_type_formula.period_calculated_interests_formula(line: line)
72
+ end
73
+
74
+ amount_to_capitalize = [
75
+ '(',
76
+ remaining_capital_start(line),
77
+ '+',
78
+ capitalized_interests_start(line),
79
+ ')'
80
+ ].join(' ')
81
+
82
+ "=#{amount_to_capitalize} * #{period_rate(line)}"
83
+ end
84
+
85
+ def remaining_capital_start_formula(line:)
86
+ return excel_float(loan.amount) if line == 2
87
+
88
+ "=#{excel_float(loan.amount)} - #{total_paid_capital_end_of_period(line - 1)}"
89
+ end
90
+
91
+ def remaining_capital_end_formula(line:)
92
+ "=#{excel_float(loan.amount)} - #{total_paid_capital_end_of_period(line)}"
93
+ end
94
+
95
+ def total_paid_capital_end_of_period_formula(line:)
96
+ return "=#{period_capital(line)}" if line == 2
97
+
98
+ "=ARRONDI(SOMME(#{column_range(column: period_capital, upto: line)}); 2)"
99
+ end
100
+
101
+ def total_paid_interests_end_of_period_formula(line:)
102
+ return "=#{period_interests(line)}" if line == 2
103
+
104
+ "=ARRONDI(SOMME(#{column_range(column: period_interests, upto: line)}); 2)"
105
+ end
106
+
107
+ def capitalized_interests_start_formula(line:)
108
+ return excel_float(loan.starting_capitalized_interests) if line == 2
109
+
110
+ "=ARRONDI(#{capitalized_interests_end(line - 1)}; 2)"
111
+ end
112
+
113
+ def capitalized_interests_end_formula(line:)
114
+ term = line - 1
115
+ if term <= loan.deferred_and_capitalized
116
+ "=ARRONDI(#{capitalized_interests_start(line)} + #{period_calculated_interests(line)}; 2)"
117
+ else
118
+ "=ARRONDI(#{capitalized_interests_start(line)} - #{period_reimbursed_capitalized_interests(line)}; 2)"
119
+ end
120
+ end
121
+
122
+ def period_reimbursed_capitalized_interests_formula(line:)
123
+ term = line - 1
124
+ if term <= loan.total_deferred_duration
125
+ excel_float(0.0)
126
+ else
127
+ "=ARRONDI(MIN(#{period_calculated_capital(line)}; #{capitalized_interests_start(line)}); 2)"
128
+ end
129
+ end
130
+
131
+ def period_rate_formula(line:)
132
+ @interests_formula.period_rate_formula(line: line)
133
+ end
134
+
135
+ def delta_formula(line:)
136
+ "=#{period_theoric_interests(line)} - #{period_interests(line)}"
137
+ end
138
+
139
+ def accrued_delta_formula(line:)
140
+ return excel_float(0.0) if line == 2
141
+
142
+ "=#{accrued_delta(line - 1)} + #{delta(line)} - #{amount_to_add(line - 1)}"
143
+ end
144
+
145
+ def amount_to_add_formula(line:)
146
+ "=TRONQUE(#{accrued_delta(line)}; 2)"
147
+ end
148
+
149
+ def period_leap_days_formula(line:)
150
+ term = line - 1
151
+ from = loan.due_on + ((term - 2) * loan.period_duration).months
152
+ to = loan.due_on + ((term - 1) * loan.period_duration).months
153
+
154
+ (from...to).sum { |d| d.leap? ? 1 : 0 }
155
+ end
156
+
157
+ def period_non_leap_days_formula(line:)
158
+ term = line - 1
159
+ from = loan.due_on + ((term - 2) * loan.period_duration).months
160
+ to = loan.due_on + ((term - 1) * loan.period_duration).months
161
+
162
+ (from...to).sum { |d| d.leap? ? 0 : 1 }
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,65 @@
1
+ module SpreadsheetLoanGenerator
2
+ class Generate < Dry::CLI::Command
3
+ include SpreadsheetConcern
4
+ include CsvConcern
5
+
6
+ attr_accessor :loan
7
+
8
+ desc "Generate spreadsheet"
9
+
10
+ argument :amount, type: :float, required: true, desc: 'amount borrowed'
11
+ argument :duration, type: :integer, required: true, desc: 'number of reimbursements'
12
+ argument :rate, type: :float, required: true, desc: 'year rate'
13
+
14
+ option :period_duration, type: :integer, default: 1, desc: 'duration of a period in months'
15
+ option :due_on, type: :date, default: Date.today, desc: 'date of the pay day of the first period DD/MM/YYYY'
16
+ option :deferred_and_capitalized, type: :integer, default: 0, desc: 'periods with no capital or interests paid'
17
+ option :deferred, type: :integer, default: 0, desc: 'periods with only interests paid'
18
+ option :type, type: :string, default: 'standard', values: %w[standard linear], desc: 'type of amortization'
19
+ option :interests_type, type: :string, default: 'simple', values: %w[simple realistic normal], desc: 'type of interests calculations'
20
+ option :starting_capitalized_interests, type: :float, default: 0.0, desc: 'starting capitalized interests (if ongoing loan)'
21
+ option :target_path, type: :string, default: './', desc: 'where to put the generated csv'
22
+
23
+ def call(amount:, duration:, rate:, **options)
24
+ begin
25
+ session = GoogleDrive::Session.from_config(
26
+ File.join(ENV['SPREADSHEET_LOAN_GENERATOR_DIR'], 'config.json')
27
+ )
28
+ rescue StandardError => e
29
+ if ENV['SPREADSHEET_LOAN_GENERATOR_DIR'].blank?
30
+ puts 'please set SPREADSHEET_LOAN_GENERATOR_DIR'
31
+ else
32
+ puts 'Cannot connect to google drive. Did you run slg init CLIENT_ID CLIENT_SECRET ?'
33
+ end
34
+ return
35
+ end
36
+
37
+ @loan = Loan.new(
38
+ amount: amount,
39
+ duration: duration,
40
+ period_duration: options.fetch(:period_duration),
41
+ rate: rate,
42
+ due_on: options.fetch(:due_on),
43
+ deferred_and_capitalized: options.fetch(:deferred_and_capitalized),
44
+ deferred: options.fetch(:deferred),
45
+ type: options.fetch(:type),
46
+ interests_type: options.fetch(:interests_type),
47
+ starting_capitalized_interests: options.fetch(:starting_capitalized_interests)
48
+ )
49
+
50
+ spreadsheet = session.create_spreadsheet(loan.name)
51
+ worksheet = spreadsheet.worksheets.first
52
+ @formula = Formula.new(loan: loan)
53
+
54
+ apply_formulas(worksheet: worksheet)
55
+ apply_formats(worksheet: worksheet)
56
+
57
+ worksheet.save
58
+ worksheet.reload
59
+
60
+ generate_csv(worksheet: worksheet, target_path: options.fetch(:target_path))
61
+
62
+ puts worksheet.human_url
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,36 @@
1
+ module SpreadsheetLoanGenerator
2
+ class Init < Dry::CLI::Command
3
+
4
+ argument :client_id, type: :string, required: true, desc: 'GCP client_id'
5
+ argument :client_secret, type: :string, required: true, desc: 'GCP client_secret'
6
+
7
+ def call(client_id:, client_secret:)
8
+ if !ENV.key?('SPREADSHEET_LOAN_GENERATOR_DIR')
9
+ puts "Please set up the environment variable SPREADSHEET_LOAN_GENERATOR_DIR to a dir that will store this gem's configuration"
10
+ return
11
+ end
12
+
13
+ create_config_dir
14
+ create_config_file(client_id: client_id, client_secret: client_secret)
15
+ end
16
+
17
+ def config_path
18
+ File.join(ENV['SPREADSHEET_LOAN_GENERATOR_DIR'], 'config.json')
19
+ end
20
+
21
+ def create_config_dir
22
+ if !File.exists?(ENV['SPREADSHEET_LOAN_GENERATOR_DIR'])
23
+ FileUtils.mkdir_p(ENV['SPREADSHEET_LOAN_GENERATOR_DIR'])
24
+ end
25
+ end
26
+
27
+ def create_config_file(client_id:, client_secret:)
28
+ f = File.new(config_path, 'w')
29
+ f.write({
30
+ client_id: client_id,
31
+ client_secret: client_secret
32
+ }.to_json)
33
+ f.close
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,20 @@
1
+ module SpreadsheetLoanGenerator
2
+ class Linear
3
+ include SpreadsheetConcern
4
+
5
+ attr_accessor :loan
6
+ def initialize(loan:)
7
+ @loan = loan
8
+ end
9
+
10
+ def period_calculated_capital_formula(*)
11
+ amount =
12
+ if loan.deferred_and_capitalized.zero?
13
+ excel_float(loan.amount)
14
+ else
15
+ "(#{capitalized_interests_end(loan.deferred_and_capitalized + 1)} + #{excel_float(loan.amount)})"
16
+ end
17
+ "=#{amount} / #{loan.non_deferred_duration}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,122 @@
1
+ module SpreadsheetLoanGenerator
2
+ class Loan
3
+ attr_accessor :amount,
4
+ :duration,
5
+ :period_duration,
6
+ :rate,
7
+ :due_on,
8
+ :deferred_and_capitalized,
9
+ :deferred,
10
+ :type,
11
+ :interests_type,
12
+ :starting_capitalized_interests
13
+
14
+ def initialize(
15
+ amount:,
16
+ duration:,
17
+ period_duration:,
18
+ rate:,
19
+ due_on:,
20
+ deferred_and_capitalized:,
21
+ deferred:,
22
+ type:,
23
+ interests_type:,
24
+ starting_capitalized_interests:)
25
+ @amount = amount.to_f
26
+ @duration = duration.to_i
27
+ @period_duration = period_duration.to_i
28
+ @rate = rate.to_f
29
+ @due_on = due_on.is_a?(Date) ? due_on : Date.parse(due_on)
30
+ @deferred_and_capitalized = deferred_and_capitalized.to_i
31
+ @deferred = deferred.to_i
32
+ @type = type
33
+ @interests_type = interests_type
34
+
35
+ @starting_capitalized_interests = starting_capitalized_interests.to_f
36
+
37
+ print_validation_errors
38
+ end
39
+
40
+ def print_validation_errors
41
+ puts 'amount < 0' if amount < 0
42
+ puts 'deferred & deferred_and_capitalized >= duration' if total_deferred_duration >= duration
43
+ if type == 'standard' && interests_type == 'realistic' && !fully_deferred?
44
+ puts 'standard & realistic interests do not work together'
45
+ end
46
+ end
47
+
48
+ def loan_type_formula
49
+ "SpreadsheetLoanGenerator::#{type.classify}".constantize.new(loan: self)
50
+ end
51
+
52
+ def interests_formula
53
+ "SpreadsheetLoanGenerator::#{interests_type.classify}Interests".constantize.new(loan: self)
54
+ end
55
+
56
+ def name_type
57
+ return 'bullet' if bullet?
58
+ return 'in_fine' if in_fine?
59
+
60
+ type
61
+ end
62
+
63
+ def name_period_duration
64
+ if period_duration.in?([1, 3, 6, 12])
65
+ {
66
+ '1' => 'month',
67
+ '3' => 'quarter',
68
+ '6' => 'semester',
69
+ '12' => 'year'
70
+ }[period_duration.to_s]
71
+ else
72
+ period_duration.to_s
73
+ end
74
+ end
75
+
76
+ def name_deferred
77
+ return '0' if fully_deferred?
78
+
79
+ total_deferred_duration
80
+ end
81
+
82
+ def name_due_on
83
+ due_on.strftime('%Y%m%d')
84
+ end
85
+
86
+ def name
87
+ args = []
88
+ args += ['realistic'] if interests_type == 'realistic'
89
+ args += [
90
+ name_type,
91
+ name_period_duration,
92
+ amount,
93
+ (rate * 100).to_s,
94
+ duration.to_s,
95
+ name_deferred,
96
+ name_due_on
97
+ ]
98
+
99
+ args.join('_')
100
+ end
101
+
102
+ def fully_deferred?
103
+ duration > 1 && non_deferred_duration == 1
104
+ end
105
+
106
+ def bullet?
107
+ fully_deferred? && deferred_and_capitalized == total_deferred_duration
108
+ end
109
+
110
+ def in_fine?
111
+ fully_deferred? && deferred == total_deferred_duration
112
+ end
113
+
114
+ def non_deferred_duration
115
+ duration - total_deferred_duration
116
+ end
117
+
118
+ def total_deferred_duration
119
+ deferred_and_capitalized + deferred
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,15 @@
1
+ module SpreadsheetLoanGenerator
2
+ class NormalInterests
3
+ include SpreadsheetConcern
4
+
5
+ attr_accessor :loan
6
+ def initialize(loan:)
7
+ @loan = loan
8
+ end
9
+
10
+ def period_rate_formula(*)
11
+ periods_per_year = excel_float(12.0 / loan.period_duration)
12
+ "=TAUX.NOMINAL(#{excel_float(loan.rate)};#{periods_per_year}) / #{periods_per_year}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module SpreadsheetLoanGenerator
2
+ class RealisticInterests
3
+ include SpreadsheetConcern
4
+
5
+ attr_accessor :loan
6
+ def initialize(loan:)
7
+ @loan = loan
8
+ end
9
+
10
+ def period_rate_formula(line:)
11
+ "=#{excel_float(loan.rate)} * ((#{period_leap_days(line)} / 366) + (#{period_non_leap_days(line)} / 365))"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module SpreadsheetLoanGenerator
2
+ class SimpleInterests
3
+ include SpreadsheetConcern
4
+
5
+ attr_accessor :loan
6
+ def initialize(loan:)
7
+ @loan = loan
8
+ end
9
+
10
+ def period_rate_formula(*)
11
+ "=#{excel_float(loan.rate)} * #{loan.period_duration} / 12,0"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ module SpreadsheetLoanGenerator
2
+ class Standard
3
+ include SpreadsheetConcern
4
+
5
+ attr_accessor :loan
6
+
7
+ def initialize(loan:)
8
+ @loan = loan
9
+ end
10
+
11
+ def standard_params(line:)
12
+ amount =
13
+ if loan.deferred_and_capitalized.zero?
14
+ excel_float(loan.amount)
15
+ else
16
+ "#{capitalized_interests_end(loan.deferred_and_capitalized + 1)} + #{excel_float(loan.amount)}"
17
+ end
18
+ term_cell = "#{index(line)} - #{loan.total_deferred_duration}"
19
+
20
+ "#{period_rate(line)};#{term_cell};#{loan.non_deferred_duration};#{amount}"
21
+ end
22
+
23
+ def period_calculated_capital_formula(line:)
24
+ "=-PPMT(#{standard_params(line: line)})"
25
+ end
26
+
27
+ def period_calculated_interests_formula(line:)
28
+ "=-IPMT(#{standard_params(line: line)})"
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,13 @@
1
+ require 'dry/cli'
2
+
3
+ module SpreadsheetLoanGenerator
4
+ VERSION = '0.1.6'
5
+
6
+ class Version < Dry::CLI::Command
7
+ desc 'Print version'
8
+
9
+ def call(*)
10
+ puts VERSION
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ require 'dry/cli'
2
+ require 'google_drive'
3
+ require 'active_support/all'
4
+ require 'fileutils'
5
+ require 'csv'
6
+
7
+ require 'spreadsheet_loan_generator/version'
8
+
9
+ module SpreadsheetLoanGenerator
10
+ class Error < StandardError; end
11
+
12
+ extend Dry::CLI::Registry
13
+
14
+ autoload :SpreadsheetConcern, 'spreadsheet_loan_generator/concerns/spreadsheet_concern'
15
+ autoload :CsvConcern, 'spreadsheet_loan_generator/concerns/csv_concern'
16
+
17
+ autoload :Linear, 'spreadsheet_loan_generator/linear'
18
+ autoload :Standard, 'spreadsheet_loan_generator/standard'
19
+ autoload :NormalInterests, 'spreadsheet_loan_generator/normal_interests'
20
+ autoload :SimpleInterests, 'spreadsheet_loan_generator/simple_interests'
21
+ autoload :RealisticInterests, 'spreadsheet_loan_generator/realistic_interests'
22
+ autoload :Formula, 'spreadsheet_loan_generator/formula'
23
+
24
+ autoload :Loan, 'spreadsheet_loan_generator/loan'
25
+
26
+ autoload :Version, 'spreadsheet_loan_generator/version'
27
+ autoload :Generate, 'spreadsheet_loan_generator/generate'
28
+ autoload :Init, 'spreadsheet_loan_generator/init'
29
+
30
+ register 'init', Init, aliases: ['i', '-i', '--init']
31
+ register 'version', Version, aliases: ['v', '-v', '--version']
32
+ register 'generate', Generate, aliases: ['g', '-g', '--generate']
33
+ end
@@ -0,0 +1,35 @@
1
+ require_relative 'lib/spreadsheet_loan_generator/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'spreadsheet_loan_generator'
5
+ spec.version = SpreadsheetLoanGenerator::VERSION
6
+ spec.authors = ['MZiserman']
7
+ spec.email = ['martinziserman@gmail.com']
8
+
9
+ spec.summary = 'Generate spreadsheets amortization schedules from the command line'
10
+ spec.homepage = 'https://github.com/CapSens/spreadsheet_loan_generator'
11
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
12
+
13
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = spec.homepage
17
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.executables << 'slg'
27
+
28
+ spec.require_paths = ['lib']
29
+
30
+ spec.add_runtime_dependency 'activesupport'
31
+ spec.add_runtime_dependency 'csv'
32
+ spec.add_runtime_dependency 'dry-cli', '0.6'
33
+ spec.add_runtime_dependency 'google_drive'
34
+ spec.add_development_dependency 'pry'
35
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spreadsheet_loan_generator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - MZiserman
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-05-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: csv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-cli
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '='
46
+ - !ruby/object:Gem::Version
47
+ version: '0.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '='
53
+ - !ruby/object:Gem::Version
54
+ version: '0.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: google_drive
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - martinziserman@gmail.com
86
+ executables:
87
+ - slg
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - exe/slg
100
+ - lib/spreadsheet_loan_generator.rb
101
+ - lib/spreadsheet_loan_generator/concerns/csv_concern.rb
102
+ - lib/spreadsheet_loan_generator/concerns/spreadsheet_concern.rb
103
+ - lib/spreadsheet_loan_generator/formula.rb
104
+ - lib/spreadsheet_loan_generator/generate.rb
105
+ - lib/spreadsheet_loan_generator/init.rb
106
+ - lib/spreadsheet_loan_generator/linear.rb
107
+ - lib/spreadsheet_loan_generator/loan.rb
108
+ - lib/spreadsheet_loan_generator/normal_interests.rb
109
+ - lib/spreadsheet_loan_generator/realistic_interests.rb
110
+ - lib/spreadsheet_loan_generator/simple_interests.rb
111
+ - lib/spreadsheet_loan_generator/standard.rb
112
+ - lib/spreadsheet_loan_generator/version.rb
113
+ - spreadsheet_loan_generator.gemspec
114
+ homepage: https://github.com/CapSens/spreadsheet_loan_generator
115
+ licenses: []
116
+ metadata:
117
+ allowed_push_host: https://rubygems.org
118
+ homepage_uri: https://github.com/CapSens/spreadsheet_loan_generator
119
+ source_code_uri: https://github.com/CapSens/spreadsheet_loan_generator
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: 2.3.0
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 3.1.4
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Generate spreadsheets amortization schedules from the command line
139
+ test_files: []