hledger-forecast 1.2.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/example.journal CHANGED
@@ -1,51 +1,47 @@
1
1
  ~ monthly from 2023-03-01 * Salary, Bills, Food, New Kitchen
2
- Income:Salary $-3,500.00; Salary
3
- Expenses:Bills $175.00 ; Bills
4
- Expenses:Food $500.00 ; Food
5
- Expenses:House $208.33 ; New Kitchen
2
+ Income:Salary $-3,500.00; Salary
3
+ Expenses:Bills $175.00 ; Bills
4
+ Expenses:Food $500.00 ; Food
5
+ Expenses:House $208.33 ; New Kitchen
6
6
  Assets:Bank
7
7
 
8
8
  ~ monthly from 2023-03-01 to 2025-01-01 * Mortgage
9
- Expenses:Mortgage $2,000.00 ; Mortgage
9
+ Expenses:Mortgage $2,000.00 ; Mortgage
10
10
  Assets:Bank
11
11
 
12
12
  ~ monthly from 2023-03-01 to 2024-02-29 * Holiday
13
- Expenses:Holiday $125.00 ; Holiday
13
+ Expenses:Holiday $125.00 ; Holiday
14
14
  Assets:Bank
15
15
 
16
- ~ monthly from 2023-03-01 to 2025-01-01 * Rainy day fund
17
- Assets:Savings $300.00 ; Rainy day fund
16
+ ~ monthly from 2023-03-01 to 2025-03-01 * Rainy day fund
17
+ Assets:Savings $300.00 ; Rainy day fund
18
18
  Assets:Bank
19
19
 
20
20
  ~ monthly from 2024-01-01 * Pension draw down
21
- Income:Pension $-500.00 ; Pension draw down
21
+ Income:Pension $-500.00 ; Pension draw down
22
22
  Assets:Pension
23
23
 
24
24
  ~ every 3 months from 2023-04-01 * Quarterly bonus
25
- Income:Bonus $-1,000.00; Quarterly bonus
25
+ Income:Bonus $-1,000.00; Quarterly bonus
26
26
  Assets:Bank
27
27
 
28
28
  ~ every 6 months from 2023-04-01 * Top up holiday funds
29
- Expenses:Holiday $500.00 ; Top up holiday funds
29
+ Expenses:Holiday $500.00 ; Top up holiday funds
30
30
  Assets:Bank
31
31
 
32
- ~ yearly from 2023-04-01 * Annual Bonus
33
- Income:Bonus $-2,000.00; Annual Bonus
32
+ ~ yearly from 2023-04-01 * Annual bonus
33
+ Income:Bonus $-2,000.00; Annual bonus
34
34
  Assets:Bank
35
35
 
36
36
  ~ every 2 weeks from 2023-03-01 * Hair and beauty
37
- Expenses:Personal Care $80.00 ; Hair and beauty
37
+ Expenses:Personal Care $80.00 ; Hair and beauty
38
38
  Assets:Bank
39
39
 
40
- ~ 2023-06-01 * [TRACKED] Refund for that damn laptop
41
- Expenses:Shopping $-3,000.00; Refund for that damn laptop
40
+ ~ every 5 weeks from 2023-03-01 * Misc expenses
41
+ Expenses:General Expenses $30.00 ; Misc expenses
42
42
  Assets:Bank
43
43
 
44
- = Expenses:Food date:2024-01-01..2024-12-31
45
- Expenses:Food *0.02 ; Food - Inflation
46
- Assets:Bank *-0.02
47
-
48
- = Expenses:Food date:2025-01-01..2025-12-31
49
- Expenses:Food *0.05 ; Food - Inflation
50
- Assets:Bank *-0.05
44
+ ~ 2023-07-01 * [TRACKED] Refund for that damn laptop
45
+ Expenses:Shopping $-3,000.00; Refund for that damn laptop
46
+ Assets:Bank
51
47
 
data/example.yml CHANGED
@@ -80,14 +80,19 @@ once:
80
80
  track: true
81
81
 
82
82
  custom:
83
- - frequency: "every 2 weeks"
84
- account: "Assets:Bank"
83
+ - account: "Assets:Bank"
85
84
  from: "2023-03-01"
86
- roll-up: 26
87
85
  transactions:
88
86
  - amount: 80
89
87
  category: "Expenses:Personal Care"
90
88
  description: Hair and beauty
89
+ frequency: "every 2 weeks"
90
+ roll-up: 26
91
+ - amount: 30
92
+ category: "Expenses:General Expenses"
93
+ description: Misc expenses
94
+ frequency: "every 5 weeks"
95
+ roll-up: 10.4
91
96
 
92
97
  settings:
93
98
  currency: USD
@@ -6,7 +6,7 @@ module HledgerForecast
6
6
  end
7
7
 
8
8
  def evaluate(amount)
9
- return amount unless amount.is_a?(String)
9
+ return amount.to_f unless amount.is_a?(String)
10
10
 
11
11
  @calculator.evaluate(amount.slice(1..-1))
12
12
  end
@@ -69,9 +69,16 @@ module HledgerForecast
69
69
  opts.separator ""
70
70
 
71
71
  opts.on("-f", "--forecast FILE",
72
- "The path to the FORECAST yaml file to generate from") do |file|
72
+ "The path to the FORECAST csv/yml file to generate from") do |file|
73
73
  options[:forecast_file] = file
74
- options[:output_file] ||= file.sub(/\.yml$/, '.journal')
74
+
75
+ options[:file_type] = if File.extname(file) == '.csv'
76
+ "csv"
77
+ else
78
+ "yml"
79
+ end
80
+
81
+ options[:output_file] ||= file.sub(options[:file_type], 'journal')
75
82
  end
76
83
 
77
84
  opts.on("-o", "--output-file FILE",
@@ -84,6 +91,11 @@ module HledgerForecast
84
91
  options[:transaction_file] = file
85
92
  end
86
93
 
94
+ opts.on("-v", "--verbose",
95
+ "Don't group transactions by type in the output file") do
96
+ options[:verbose] = true
97
+ end
98
+
87
99
  opts.on("--force",
88
100
  "Force an overwrite of the output file") do
89
101
  options[:force] = true
@@ -100,7 +112,8 @@ module HledgerForecast
100
112
  end
101
113
  end.parse!(args)
102
114
 
103
- options[:forecast_file] = "forecast.yml" unless options[:forecast_file]
115
+ options[:forecast_file] = "forecast.csv" unless options[:forecast_file]
116
+ options[:file_type] = "csv" unless options[:file_type]
104
117
  options[:output_file] = "forecast.journal" unless options[:output_file]
105
118
 
106
119
  options
@@ -114,7 +127,12 @@ module HledgerForecast
114
127
  opts.separator ""
115
128
 
116
129
  opts.on("-f", "--forecast FILE",
117
- "The path to the FORECAST yaml file to summarize") do |file|
130
+ "The path to the FORECAST csv/yml file to summarize") do |file|
131
+ options[:file_type] = if File.extname(file) == '.csv'
132
+ "csv"
133
+ else
134
+ "yml"
135
+ end
118
136
  options[:forecast_file] = file
119
137
  end
120
138
 
@@ -159,6 +177,7 @@ module HledgerForecast
159
177
  forecast = File.read(options[:forecast_file])
160
178
 
161
179
  begin
180
+ forecast = HledgerForecast::CSVParser.parse(forecast) if options[:file_type] == "csv"
162
181
  transactions = Generator.generate(forecast, options)
163
182
  rescue StandardError => e
164
183
  puts "An error occurred while generating transactions: #{e.message}"
@@ -166,6 +185,7 @@ module HledgerForecast
166
185
  end
167
186
 
168
187
  output_file = options[:output_file]
188
+
169
189
  if File.exist?(output_file) && !options[:force]
170
190
  print "\nFile '#{output_file}' already exists. Overwrite? (y/n): "
171
191
  overwrite = gets.chomp.downcase
@@ -184,6 +204,8 @@ module HledgerForecast
184
204
 
185
205
  def self.summarize(options)
186
206
  config = File.read(options[:forecast_file])
207
+ config = HledgerForecast::CSVParser.parse(config) if options[:file_type] == "csv"
208
+
187
209
  summarizer = Summarizer.summarize(config, options)
188
210
 
189
211
  puts SummarizerFormatter.format(summarizer[:output], summarizer[:settings])
@@ -0,0 +1,106 @@
1
+ module HledgerForecast
2
+ # Formats various items used throughout the application
3
+ class CSVParser
4
+ def self.parse(csv_data, cli_options = nil)
5
+ new.parse(csv_data, cli_options)
6
+ end
7
+
8
+ def parse(csv_data, _cli_options)
9
+ csv_data = CSV.parse(csv_data, headers: true)
10
+ yaml_data = {}
11
+ group_by_type(csv_data, yaml_data)
12
+ yaml_data.to_yaml
13
+ end
14
+
15
+ private
16
+
17
+ def group_by_type(csv_data, yaml_data)
18
+ csv_data.group_by { |row| row['type'] }.each do |type, rows|
19
+ if type == 'settings'
20
+ handle_settings(rows, yaml_data)
21
+ else
22
+ yaml_data[type] ||= []
23
+ group_by_account_and_from(rows, yaml_data[type], type)
24
+ end
25
+ end
26
+ end
27
+
28
+ def handle_settings(rows, yaml_data)
29
+ yaml_data['settings'] ||= {}
30
+ rows.each do |row|
31
+ yaml_data['settings'][row['frequency']] = cast_to_proper_type(row['account'])
32
+ end
33
+ end
34
+
35
+ def group_by_account_and_from(rows, yaml_rows, type)
36
+ rows.group_by { |row| [row['account'], row['from']] }.each do |(account, from), transactions|
37
+ yaml_rows << if type == 'custom'
38
+ build_custom_transaction(account, from, transactions)
39
+ else
40
+ build_transaction(account, from, transactions)
41
+ end
42
+ end
43
+ end
44
+
45
+ def build_transaction(account, from, transactions)
46
+ transaction = {
47
+ 'account' => account,
48
+ 'from' => Date.parse(from).strftime('%Y-%m-%d'),
49
+ 'transactions' => []
50
+ }
51
+
52
+ transactions.each do |row|
53
+ transaction['transactions'] << build_transaction_data(row)
54
+ end
55
+
56
+ transaction
57
+ end
58
+
59
+ def build_custom_transaction(account, from, transactions)
60
+ transaction = {
61
+ 'account' => account,
62
+ 'from' => Date.parse(from).strftime('%Y-%m-%d'),
63
+ 'transactions' => []
64
+ }
65
+
66
+ transactions.each do |row|
67
+ transaction_data = build_transaction_data(row)
68
+ transaction_data['frequency'] = row['frequency']
69
+ transaction_data['roll-up'] = row['roll-up'].to_f if row['roll-up']
70
+ transaction['transactions'] << transaction_data
71
+ end
72
+
73
+ transaction
74
+ end
75
+
76
+ def build_transaction_data(row)
77
+ transaction_data = {
78
+ 'amount' => row['amount'].start_with?("=") ? row['amount'].to_s : row['amount'].to_f,
79
+ 'category' => row['category'],
80
+ 'description' => row['description']
81
+ }
82
+
83
+ if row['to']
84
+ transaction_data['to'] = if row['to'].start_with?("=")
85
+ row['to']
86
+ else
87
+ Date.parse(row['to']).strftime('%Y-%m-%d')
88
+ end
89
+ end
90
+
91
+ transaction_data['summary_exclude'] = true if row['summary_exclude'] && row['summary_exclude'].downcase == "true"
92
+ transaction_data['track'] = true if row['track'] && row['track'].downcase == "true"
93
+
94
+ transaction_data
95
+ end
96
+
97
+ def cast_to_proper_type(str)
98
+ case str.downcase
99
+ when 'true', 'false'
100
+ str.downcase == 'true'
101
+ else
102
+ str
103
+ end
104
+ end
105
+ end
106
+ end
@@ -55,7 +55,8 @@ module HledgerForecast
55
55
  description: t['description'],
56
56
  to: t['to'] ? Calculator.new.evaluate_date(Date.parse(block['from']), t['to']) : nil,
57
57
  modifiers: t['modifiers'] ? Transactions::Modifiers.get_modifiers(t, block) : [],
58
- track: Transactions::Trackers.track?(t, block, @settings) ? true : false
58
+ track: Transactions::Trackers.track?(t, block, @settings) ? true : false,
59
+ frequency: t['frequency'] || nil
59
60
  }
60
61
  end
61
62
 
@@ -58,7 +58,7 @@ module HledgerForecast
58
58
 
59
59
  output.last[:transactions] << {
60
60
  amount: amount,
61
- annualised_amount: amount * (block['roll-up'] || annualise(period)),
61
+ annualised_amount: amount * (t['roll-up'] || annualise(period)),
62
62
  rolled_up_amount: 0,
63
63
  category: t['category'],
64
64
  exclude: t['summary_exclude'],
@@ -33,14 +33,56 @@ module HledgerForecast
33
33
  def process_block(block)
34
34
  block[:transactions].each do |to, transactions|
35
35
  to = get_header(block[:to], to)
36
- block[:descriptions] = get_descriptions(transactions)
37
- frequency = get_periodic_rules(block[:type], block[:frequency])
38
36
 
39
- header = "#{frequency} #{block[:from]}#{to} * #{block[:descriptions]}\n"
40
- footer = " #{block[:account]}\n\n"
37
+ if block[:type] == "custom"
38
+ process_custom_transactions(block, to, transactions)
39
+ else
40
+ process_standard_transactions(block, to, transactions)
41
+ end
42
+ end
43
+ end
44
+
45
+ def process_custom_transactions(block, to, transactions)
46
+ transactions.each do |t|
47
+ frequency = get_periodic_rules(block[:type], t[:frequency])
48
+
49
+ header = build_header(block, to, frequency, t[:description])
50
+ footer = build_footer(block)
51
+ output << build_transaction(header, [t], footer)
52
+ end
53
+ end
54
+
55
+ def process_standard_transactions(block, to, transactions)
56
+ if @options[:verbose]
57
+ transactions.map do |t|
58
+ # Skip transactions that have been marked as tracked
59
+ next if t[:track]
41
60
 
42
- output << { header: header, transactions: write_transactions(transactions), footer: footer }
61
+ frequency = get_periodic_rules(block[:type], block[:frequency])
62
+ header = build_header(block, to, frequency, t[:description])
63
+ footer = build_footer(block)
64
+ output << build_transaction(header, [t], footer)
65
+ end
66
+ return
43
67
  end
68
+
69
+ block[:descriptions] = get_descriptions(transactions)
70
+ frequency = get_periodic_rules(block[:type], block[:frequency])
71
+ header = build_header(block, to, frequency, block[:descriptions])
72
+ footer = build_footer(block)
73
+ output << build_transaction(header, transactions, footer)
74
+ end
75
+
76
+ def build_header(block, to, frequency, description)
77
+ "#{frequency} #{block[:from]}#{to} * #{description}\n"
78
+ end
79
+
80
+ def build_footer(block)
81
+ " #{block[:account]}\n\n"
82
+ end
83
+
84
+ def build_transaction(header, transactions, footer)
85
+ { header: header, transactions: write_transactions(transactions), footer: footer }
44
86
  end
45
87
 
46
88
  def get_header(block, transaction)
@@ -32,6 +32,13 @@ module HledgerForecast
32
32
  end
33
33
 
34
34
  def self.exists?(transaction, account, from, to, options)
35
+
36
+ if !options[:transaction_file]
37
+ puts "\nWarning: ".bold.yellow + "For tracked transactions, please specify a file with the `-t` flag"
38
+ puts "ERROR: ".bold.red + "Tracked transactions ignored for now"
39
+ return
40
+ end
41
+
35
42
  # Format the money
36
43
  amount = Formatter.format_money(transaction['amount'], options)
37
44
  inverse_amount = Formatter.format_money(transaction['amount'] * -1, options)
@@ -1,3 +1,3 @@
1
1
  module HledgerForecast
2
- VERSION = "1.2.1"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'colorize'
4
+ require 'csv'
4
5
  require 'date'
5
6
  require 'dentaku'
6
7
  require 'highline'
@@ -14,6 +15,7 @@ Money.rounding_mode = BigDecimal::ROUND_HALF_UP
14
15
 
15
16
  require_relative 'hledger_forecast/calculator'
16
17
  require_relative 'hledger_forecast/cli'
18
+ require_relative 'hledger_forecast/csv_parser'
17
19
  require_relative 'hledger_forecast/formatter'
18
20
  require_relative 'hledger_forecast/generator'
19
21
  require_relative 'hledger_forecast/settings'
@@ -24,4 +26,3 @@ require_relative 'hledger_forecast/version'
24
26
  require_relative 'hledger_forecast/transactions/default'
25
27
  require_relative 'hledger_forecast/transactions/modifiers'
26
28
  require_relative 'hledger_forecast/transactions/trackers'
27
-
data/spec/command_spec.rb CHANGED
@@ -14,7 +14,6 @@ JOURNAL
14
14
 
15
15
  RSpec.describe 'command' do
16
16
  it 'uses the CLI to generate an output' do
17
- # Delete the file if it exists
18
17
  generated_journal = './test_output.journal'
19
18
  File.delete(generated_journal) if File.exist?(generated_journal)
20
19
 
@@ -22,4 +21,13 @@ RSpec.describe 'command' do
22
21
 
23
22
  expect(File.read(generated_journal)).to eq(output)
24
23
  end
24
+
25
+ it 'uses the CLI to generate an output with a CSV config file' do
26
+ generated_journal = './test_output.journal'
27
+ File.delete(generated_journal) if File.exist?(generated_journal)
28
+
29
+ system("./bin/hledger-forecast generate -f ./spec/stubs/forecast.csv -o ./test_output.journal --force")
30
+
31
+ expect(File.read(generated_journal)).to eq(output)
32
+ end
25
33
  end
@@ -0,0 +1,32 @@
1
+ require_relative '../lib/hledger_forecast'
2
+
3
+ RSpec.describe 'CSV and yml outputs' do
4
+ it 'should return the same value when ran through hledger' do
5
+ generated_journal = './test_output.journal'
6
+ File.delete(generated_journal) if File.exist?(generated_journal)
7
+
8
+ system("./bin/hledger-forecast generate -f example.csv -o ./test_output.journal -t ./spec/stubs/transactions_not_found.journal --force")
9
+ csv_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
10
+
11
+ system("./bin/hledger-forecast generate -f example.yml -o ./test_output.journal -t ./spec/stubs/transactions_not_found.journal --force")
12
+ yml_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
13
+
14
+ expect(csv_output).to eq(yml_output)
15
+ end
16
+
17
+ # it 'check that it can fail!' do
18
+ # generated_journal = './test_output.journal'
19
+ # File.delete(generated_journal) if File.exist?(generated_journal)
20
+ #
21
+ # system("./bin/hledger-forecast generate -f ./spec/stubs/csv_and_yml/forecast.csv -o ./test_output.journal --force > /dev/null 2>&1")
22
+ #
23
+ # ### CHANGE DATE!!!!!!!!!!!!!!!!
24
+ # csv_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-03`
25
+ # ### CHANGE DATE!!!!!!!!!!!!!!!!
26
+ #
27
+ # system("./bin/hledger-forecast generate -f ./spec/stubs/csv_and_yml/forecast.yml -o ./test_output.journal --force > /dev/null 2>&1")
28
+ # yml_output = `hledger -f ./test_output.journal --forecast bal -b=2023-01 -e=2023-06`
29
+ #
30
+ # expect(csv_output).not_to eq(yml_output)
31
+ # end
32
+ end
@@ -0,0 +1,110 @@
1
+ require_relative '../lib/hledger_forecast'
2
+
3
+ input = <<~CSV
4
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
5
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,
6
+ monthly,,Assets:Bank,01/03/2023,01/01/2025,Mortgage,Expenses:Mortgage,2000,,,
7
+ monthly,,Assets:Bank,01/03/2023,,Bills,Expenses:Bills,175,,,
8
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,
9
+ monthly,,Assets:Bank,01/03/2023,,New Kitchen,Expenses:House,=5000/24,,,
10
+ monthly,,Assets:Bank,01/03/2023,=12,Holiday,Expenses:Holiday,125,,,
11
+ monthly,,Assets:Bank,01/03/2023,01/01/2025,Rainy day fund,Assets:Savings,300,,,
12
+ monthly,,Assets:Pension,01/01/2024,,Pension draw down,Income:Pension,-500,,,
13
+ quarterly,,Assets:Bank,01/04/2023,,Quarterly bonus,Income:Bonus,-1000,,,
14
+ half-yearly,,Assets:Bank,01/04/2023,,Top up holiday funds,Expenses:Holiday,500,,,
15
+ yearly,,Assets:Bank,01/04/2023,,Annual bonus,Income:Bonus,-2000,,,
16
+ once,,Assets:Bank,05/03/2023,,Refund for that damn laptop,Expenses:Shopping,-3000,,TRUE,TRUE
17
+ custom,every 2 weeks,Assets:Bank,01/03/2023,,Hair and beauty,Expenses:Personal Care,80,26,,
18
+ settings,currency,USD,,,,,,,,
19
+ settings,show_symbol,TRUE,,,,,,,,
20
+ settings,thousands_separator,TRUE,,,,,,,,
21
+ CSV
22
+
23
+ output = <<~YAML
24
+ ---
25
+ monthly:
26
+ - account: Assets:Bank
27
+ from: '2023-03-01'
28
+ transactions:
29
+ - amount: -3500.0
30
+ category: Income:Salary
31
+ description: Salary
32
+ - amount: 2000.0
33
+ category: Expenses:Mortgage
34
+ description: Mortgage
35
+ to: '2025-01-01'
36
+ - amount: 175.0
37
+ category: Expenses:Bills
38
+ description: Bills
39
+ - amount: 500.0
40
+ category: Expenses:Food
41
+ description: Food
42
+ - amount: "=5000/24"
43
+ category: Expenses:House
44
+ description: New Kitchen
45
+ - amount: 125.0
46
+ category: Expenses:Holiday
47
+ description: Holiday
48
+ to: "=12"
49
+ - amount: 300.0
50
+ category: Assets:Savings
51
+ description: Rainy day fund
52
+ to: '2025-01-01'
53
+ - account: Assets:Pension
54
+ from: '2024-01-01'
55
+ transactions:
56
+ - amount: -500.0
57
+ category: Income:Pension
58
+ description: Pension draw down
59
+ quarterly:
60
+ - account: Assets:Bank
61
+ from: '2023-04-01'
62
+ transactions:
63
+ - amount: -1000.0
64
+ category: Income:Bonus
65
+ description: Quarterly bonus
66
+ half-yearly:
67
+ - account: Assets:Bank
68
+ from: '2023-04-01'
69
+ transactions:
70
+ - amount: 500.0
71
+ category: Expenses:Holiday
72
+ description: Top up holiday funds
73
+ yearly:
74
+ - account: Assets:Bank
75
+ from: '2023-04-01'
76
+ transactions:
77
+ - amount: -2000.0
78
+ category: Income:Bonus
79
+ description: Annual bonus
80
+ once:
81
+ - account: Assets:Bank
82
+ from: '2023-03-05'
83
+ transactions:
84
+ - amount: -3000.0
85
+ category: Expenses:Shopping
86
+ description: Refund for that damn laptop
87
+ summary_exclude: true
88
+ track: true
89
+ custom:
90
+ - account: Assets:Bank
91
+ from: '2023-03-01'
92
+ transactions:
93
+ - amount: 80.0
94
+ category: Expenses:Personal Care
95
+ description: Hair and beauty
96
+ frequency: every 2 weeks
97
+ roll-up: 26.0
98
+ settings:
99
+ currency: USD
100
+ show_symbol: true
101
+ thousands_separator: true
102
+ YAML
103
+
104
+ RSpec.describe 'CSV parser' do
105
+ it 'converts a CSV file to the YML output needed for the plugin' do
106
+ computed_yaml = HledgerForecast::CSVParser.parse(input)
107
+
108
+ expect(computed_yaml).to eq(output)
109
+ end
110
+ end
data/spec/custom_spec.rb CHANGED
@@ -2,20 +2,17 @@ require_relative '../lib/hledger_forecast'
2
2
 
3
3
  base_config = <<~YAML
4
4
  custom:
5
- - frequency: "every 2 weeks"
5
+ - account: "[Assets:Bank]"
6
6
  from: "2023-05-01"
7
- account: "[Assets:Bank]"
8
7
  transactions:
9
8
  - amount: 80
10
9
  category: "[Expenses:Personal Care]"
11
10
  description: Hair and beauty
12
- - frequency: "every 5 days"
13
- from: "2023-05-01"
14
- account: "[Assets:Checking]"
15
- transactions:
11
+ frequency: "every 2 weeks"
16
12
  - amount: 50
17
13
  category: "[Expenses:Groceries]"
18
14
  description: Gotta feed that stomach
15
+ frequency: "every 5 days"
19
16
 
20
17
  settings:
21
18
  currency: GBP
@@ -28,19 +25,19 @@ base_output = <<~JOURNAL
28
25
 
29
26
  ~ every 5 days from 2023-05-01 * Gotta feed that stomach
30
27
  [Expenses:Groceries] £50.00; Gotta feed that stomach
31
- [Assets:Checking]
28
+ [Assets:Bank]
32
29
 
33
30
  JOURNAL
34
31
 
35
32
  calculated_config = <<~YAML
36
33
  custom:
37
- - frequency: "every 2 weeks"
34
+ - account: "[Assets:Bank]"
38
35
  from: "2023-05-01"
39
- account: "[Assets:Bank]"
40
36
  transactions:
41
37
  - amount: 80
42
38
  category: "[Expenses:Personal Care]"
43
39
  description: Hair and beauty
40
+ frequency: "every 2 weeks"
44
41
  to: "=6"
45
42
 
46
43
  settings:
@@ -0,0 +1,5 @@
1
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
2
+ monthly,,Assets:Bank,01/03/2023,,Mortgage,Expenses:Mortgage,2000.55,,,
3
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,100,,,
4
+ monthly,,Assets:Savings,01/03/2023,,Savings,Assets:Bank,-1000,,,
5
+ settings,currency,GBP,,,,,,,,
@@ -20,22 +20,22 @@ config = <<~YAML
20
20
  description: Savings
21
21
 
22
22
  custom:
23
- - frequency: "every 2 weeks"
23
+ - account: "[Assets:Bank]"
24
24
  from: "2023-05-01"
25
- account: "[Assets:Bank]"
26
- roll-up: 26
27
25
  transactions:
28
26
  - amount: 80
29
27
  category: "[Expenses:Personal Care]"
30
28
  description: Hair and beauty
31
- - frequency: "every 5 days"
29
+ frequency: "every 2 weeks"
30
+ roll-up: 26
31
+ - account: "[Assets:Checking]"
32
32
  from: "2023-05-01"
33
- account: "[Assets:Checking]"
34
- roll-up: 73
35
33
  transactions:
36
34
  - amount: 50
37
35
  category: "[Expenses:Groceries]"
38
36
  description: Gotta feed that stomach
37
+ frequency: "every 5 days"
38
+ roll-up: 73
39
39
 
40
40
  settings:
41
41
  currency: GBP
@@ -57,7 +57,7 @@ RSpec.describe HledgerForecast::Summarizer do
57
57
 
58
58
  expect(output.first).to include(:account, :from, :to, :type, :frequency)
59
59
  expect(output.first[:amount]).to eq(2000.55)
60
- expect(output.last[:rolled_up_amount]).to eq(304) # ((50 * 73) / 12)
60
+ expect(output.last[:rolled_up_amount]).to eq((50.0 * 73.0) / 12.0) # ((50 * 73) / 12)
61
61
  expect(output.length).to eq(5)
62
62
  end
63
63