spreadsheet_loan_generator 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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: []