hledger-forecast 1.5.2 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +17 -17
- data/README.md +7 -171
- data/example.journal +14 -14
- data/hledger-forecast.gemspec +1 -1
- data/lib/hledger_forecast/calculator.rb +5 -1
- data/lib/hledger_forecast/cli.rb +5 -18
- data/lib/hledger_forecast/generator.rb +52 -35
- data/lib/hledger_forecast/settings.rb +42 -27
- data/lib/hledger_forecast/summarizer.rb +28 -62
- data/lib/hledger_forecast/summarizer_formatter.rb +4 -4
- data/lib/hledger_forecast/transactions/default.rb +28 -57
- data/lib/hledger_forecast/transactions/trackers.rb +34 -40
- data/lib/hledger_forecast/utilities.rb +14 -0
- data/lib/hledger_forecast/version.rb +1 -1
- data/lib/hledger_forecast.rb +1 -2
- data/spec/cli_spec.rb +3 -12
- data/spec/computed_amounts_spec.rb +11 -22
- data/spec/custom_spec.rb +15 -35
- data/spec/half-yearly_spec.rb +6 -13
- data/spec/monthly_end_date_spec.rb +8 -18
- data/spec/monthly_end_date_transaction_spec.rb +20 -45
- data/spec/monthly_spec.rb +11 -28
- data/spec/once_spec.rb +6 -13
- data/spec/quarterly_spec.rb +5 -12
- data/spec/summarizer_spec.rb +11 -42
- data/spec/track_spec.rb +19 -49
- data/spec/verbose_output_spec.rb +3 -3
- data/spec/yearly_spec.rb +5 -12
- metadata +4 -13
- data/example.yml +0 -98
- data/lib/hledger_forecast/csv_parser.rb +0 -106
- data/spec/csv_and_yml_comparison_spec.rb +0 -32
- data/spec/csv_parser_spec.rb +0 -110
- data/spec/modifier_spec.rb +0 -102
- data/spec/stubs/forecast.yml +0 -19
@@ -6,67 +6,46 @@ module HledgerForecast
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def summarize(config, cli_options = nil)
|
9
|
-
@forecast =
|
9
|
+
@forecast = CSV.parse(config, headers: true)
|
10
10
|
@settings = Settings.config(@forecast, cli_options)
|
11
11
|
|
12
|
-
|
12
|
+
{ output: generate(@forecast), settings: @settings }
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def generate(forecast)
|
18
|
-
output = {}
|
19
|
-
forecast.each do |period, blocks|
|
20
|
-
next if %w[settings].include?(period)
|
21
|
-
|
22
|
-
blocks.each do |block|
|
23
|
-
key = if @settings[:roll_up].nil?
|
24
|
-
period
|
25
|
-
else
|
26
|
-
output.length
|
27
|
-
end
|
28
|
-
|
29
|
-
output[key] ||= []
|
30
|
-
output[key] << process_block(period, block)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
output = filter_out(flatten_and_merge(output))
|
35
|
-
output = calculate_rolled_up_amount(output) unless @settings[:roll_up].nil?
|
36
|
-
|
37
|
-
output
|
38
|
-
end
|
39
|
-
|
40
|
-
def process_block(period, block)
|
41
18
|
output = []
|
42
19
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
to: block['to'] ? Date.parse(block['to']) : nil,
|
47
|
-
type: period,
|
48
|
-
frequency: block['frequency'],
|
49
|
-
transactions: []
|
50
|
-
}
|
20
|
+
forecast.each do |row|
|
21
|
+
next if row['type'] == 'settings'
|
22
|
+
next if row['summary_exclude']
|
51
23
|
|
52
|
-
|
53
|
-
end
|
24
|
+
row['amount'] = Calculator.new.evaluate(Utilities.convert_amount(row['amount']))
|
54
25
|
|
55
|
-
|
56
|
-
|
57
|
-
|
26
|
+
begin
|
27
|
+
annualised_amount = row['roll-up'] ? row['amount'] * row['roll-up'].to_f : row['amount'] * annualise(row['type'])
|
28
|
+
rescue StandardError
|
29
|
+
puts "\nError: ".bold.red + 'Could not create an annualised ammount. Have you set the roll-up for your custom type transactions?'
|
30
|
+
exit
|
31
|
+
end
|
58
32
|
|
59
|
-
output
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
33
|
+
output << {
|
34
|
+
account: row['account'],
|
35
|
+
from: Date.parse(row['from']),
|
36
|
+
to: row['to'] ? Calculator.new.evaluate_date(Date.parse(row['from']), row['to']) : nil,
|
37
|
+
type: row['type'],
|
38
|
+
frequency: row['frequency'],
|
39
|
+
category: row['category'],
|
40
|
+
description: row['description'],
|
41
|
+
amount: row['amount'],
|
42
|
+
annualised_amount: annualised_amount.to_f,
|
43
|
+
exclude: row['summary_exclude']
|
67
44
|
}
|
68
45
|
end
|
69
46
|
|
47
|
+
output = calculate_rolled_up_amount(output) unless @settings[:roll_up].nil?
|
48
|
+
|
70
49
|
output
|
71
50
|
end
|
72
51
|
|
@@ -84,22 +63,9 @@ module HledgerForecast
|
|
84
63
|
annualise[period]
|
85
64
|
end
|
86
65
|
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
def flatten_and_merge(blocks)
|
92
|
-
blocks.values.flatten.flat_map do |block|
|
93
|
-
block[:transactions].map do |transaction|
|
94
|
-
block.slice(:account, :from, :to, :type, :frequency).merge(transaction)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def calculate_rolled_up_amount(data)
|
100
|
-
data.map do |item|
|
101
|
-
item[:rolled_up_amount] = item[:annualised_amount] / annualise(@settings[:roll_up])
|
102
|
-
item
|
66
|
+
def calculate_rolled_up_amount(forecast)
|
67
|
+
forecast.each do |row|
|
68
|
+
row[:rolled_up_amount] = row[:annualised_amount] / annualise(@settings[:roll_up])
|
103
69
|
end
|
104
70
|
end
|
105
71
|
end
|
@@ -41,7 +41,7 @@ module HledgerForecast
|
|
41
41
|
@table.add_row([{ value: type.capitalize.bold, colspan: 3, alignment: :center }])
|
42
42
|
total = 0
|
43
43
|
items.each do |item|
|
44
|
-
total += item[:amount]
|
44
|
+
total += item[:amount].to_f
|
45
45
|
|
46
46
|
if @settings[:verbose]
|
47
47
|
@table.add_row [{ value: item[:category], alignment: :left },
|
@@ -63,7 +63,7 @@ module HledgerForecast
|
|
63
63
|
def sort(data)
|
64
64
|
data.each do |type, items|
|
65
65
|
data[type] = items.sort_by do |item|
|
66
|
-
value = item[:amount]
|
66
|
+
value = item[:amount].to_f
|
67
67
|
[value >= 0 ? 1 : 0, value >= 0 ? -value : value]
|
68
68
|
end
|
69
69
|
end
|
@@ -114,11 +114,11 @@ module HledgerForecast
|
|
114
114
|
|
115
115
|
def add_total_row_to_table(data, row_to_sum)
|
116
116
|
total = data.reduce(0) do |sum, item|
|
117
|
-
sum + item[row_to_sum]
|
117
|
+
sum + item[row_to_sum].to_f
|
118
118
|
end
|
119
119
|
|
120
120
|
income = data.reduce(0) do |sum, item|
|
121
|
-
sum += item[row_to_sum] if item[row_to_sum] < 0
|
121
|
+
sum += item[row_to_sum].to_f if item[row_to_sum].to_f < 0
|
122
122
|
sum
|
123
123
|
end
|
124
124
|
|
@@ -6,15 +6,15 @@ module HledgerForecast
|
|
6
6
|
# Expenses:Groceries $250.00 ; Food expenses
|
7
7
|
# Assets:Checking
|
8
8
|
class Default
|
9
|
-
def self.generate(
|
10
|
-
new(
|
9
|
+
def self.generate(forecast, settings)
|
10
|
+
new(forecast, settings).generate
|
11
11
|
end
|
12
12
|
|
13
13
|
def generate
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
forecast.each do |row|
|
15
|
+
next if row[:type] == "settings"
|
16
|
+
|
17
|
+
process_transactions(row)
|
18
18
|
end
|
19
19
|
|
20
20
|
output
|
@@ -22,59 +22,34 @@ module HledgerForecast
|
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
attr_reader :
|
25
|
+
attr_reader :forecast, :settings, :output
|
26
26
|
|
27
|
-
def initialize(
|
28
|
-
@
|
29
|
-
@
|
27
|
+
def initialize(forecast, settings)
|
28
|
+
@forecast = forecast
|
29
|
+
@settings = settings
|
30
30
|
@output = []
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
36
|
-
|
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])
|
33
|
+
def process_transactions(row)
|
34
|
+
to = build_to_header(row[:to])
|
35
|
+
frequency = get_periodic_rules(row[:type], row[:frequency])
|
48
36
|
|
49
|
-
|
50
|
-
|
51
|
-
|
37
|
+
if @settings[:verbose]
|
38
|
+
description = row[:description]
|
39
|
+
transactions = [row]
|
40
|
+
else
|
41
|
+
description = get_descriptions(row[:transactions])
|
42
|
+
transactions = row[:transactions]
|
52
43
|
end
|
53
|
-
end
|
54
44
|
|
55
|
-
|
56
|
-
|
57
|
-
transactions.map do |t|
|
58
|
-
# Skip transactions that have been marked as tracked
|
59
|
-
next if t[:track]
|
60
|
-
|
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
|
67
|
-
end
|
45
|
+
header = build_header(row, frequency, to, description)
|
46
|
+
footer = build_footer(row)
|
68
47
|
|
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
48
|
output << build_transaction(header, transactions, footer)
|
74
49
|
end
|
75
50
|
|
76
|
-
def build_header(
|
77
|
-
"#{frequency} #{
|
51
|
+
def build_header(row, frequency, to, descriptions)
|
52
|
+
"#{frequency} #{row[:from]}#{to} * #{descriptions}\n"
|
78
53
|
end
|
79
54
|
|
80
55
|
def build_footer(block)
|
@@ -85,11 +60,8 @@ module HledgerForecast
|
|
85
60
|
{ header: header, transactions: write_transactions(transactions), footer: footer }
|
86
61
|
end
|
87
62
|
|
88
|
-
def
|
89
|
-
return " to #{
|
90
|
-
return " to #{block}" if block
|
91
|
-
|
92
|
-
return nil
|
63
|
+
def build_to_header(to)
|
64
|
+
return " to #{to}" if to
|
93
65
|
end
|
94
66
|
|
95
67
|
def get_descriptions(transactions)
|
@@ -110,7 +82,6 @@ module HledgerForecast
|
|
110
82
|
'yearly' => '~ yearly from',
|
111
83
|
'custom' => "~ #{frequency} from"
|
112
84
|
}
|
113
|
-
|
114
85
|
map[type]
|
115
86
|
end
|
116
87
|
|
@@ -119,11 +90,11 @@ module HledgerForecast
|
|
119
90
|
# Skip transactions that have been marked as tracked
|
120
91
|
next if t[:track]
|
121
92
|
|
122
|
-
t[:amount] = t[:amount].to_s.ljust(
|
123
|
-
t[:category] = t[:category].ljust(
|
93
|
+
t[:amount] = t[:amount].to_s.ljust(@settings[:max_amount] + 5)
|
94
|
+
t[:category] = t[:category].to_s.ljust(@settings[:max_category])
|
124
95
|
|
125
96
|
" #{t[:category]} #{t[:amount]}; #{t[:description]}\n"
|
126
|
-
end
|
97
|
+
end.compact
|
127
98
|
end
|
128
99
|
end
|
129
100
|
end
|
@@ -9,84 +9,78 @@ module HledgerForecast
|
|
9
9
|
# Expenses:Groceries $250.00 ; Food expenses
|
10
10
|
# Assets:Checking
|
11
11
|
class Trackers
|
12
|
-
def self.generate(
|
13
|
-
new(
|
12
|
+
def self.generate(forecast, options)
|
13
|
+
new(forecast, options).generate
|
14
14
|
end
|
15
15
|
|
16
16
|
def generate
|
17
|
-
return
|
17
|
+
return if @options[:no_track]
|
18
|
+
return nil unless tracked?(forecast)
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
process_tracked(block)
|
22
|
-
end
|
20
|
+
forecast.each do |row|
|
21
|
+
process_tracked(row)
|
23
22
|
end
|
24
23
|
|
25
24
|
output
|
26
25
|
end
|
27
26
|
|
28
|
-
def self.track?(
|
27
|
+
def self.track?(row, options)
|
29
28
|
now = Date.today
|
30
|
-
|
31
|
-
data['from'], now, options)
|
29
|
+
row['track'] && Date.parse(row['from']) <= now && !exists?(row, now, options)
|
32
30
|
end
|
33
31
|
|
34
|
-
def self.exists?(
|
35
|
-
|
36
|
-
if !options[:transaction_file]
|
32
|
+
def self.exists?(row, now, options)
|
33
|
+
unless options[:transaction_file]
|
37
34
|
puts "\nWarning: ".bold.yellow + "For tracked transactions, please specify a file with the `-t` flag"
|
38
35
|
puts "ERROR: ".bold.red + "Tracked transactions ignored for now"
|
39
36
|
return
|
40
37
|
end
|
41
38
|
|
42
39
|
# Format the money
|
43
|
-
amount = Formatter.format_money(
|
44
|
-
inverse_amount = Formatter.format_money(
|
40
|
+
amount = Formatter.format_money(row['amount'], options)
|
41
|
+
inverse_amount = Formatter.format_money(row['amount'] * -1, options)
|
45
42
|
|
46
|
-
|
43
|
+
from = Date.parse(row['from'])
|
44
|
+
category = row['category'].gsub('[', '\\[').gsub(']', '\\]').gsub('(', '\\(').gsub(')', '\\)')
|
47
45
|
|
48
46
|
# We run two commands and check to see if category +/- amount or account +/- amount exists
|
49
|
-
command1 = %(hledger print -f #{options[:transaction_file]} "date:#{from}..#{
|
50
|
-
command2 = %(hledger print -f #{options[:transaction_file]} "date:#{from}..#{
|
47
|
+
command1 = %(hledger print -f #{options[:transaction_file]} "date:#{from}..#{now}" | tr -s '[:space:]' ' ' | grep -q -Eo "#{category} (#{amount}|#{inverse_amount})")
|
48
|
+
command2 = %(hledger print -f #{options[:transaction_file]} "date:#{from}..#{now}" | tr -s '[:space:]' ' ' | grep -q -Eo "#{row['account']} (#{amount}|#{inverse_amount})")
|
51
49
|
|
52
50
|
system(command1) || system(command2)
|
53
51
|
end
|
54
52
|
|
55
53
|
private
|
56
54
|
|
57
|
-
attr_reader :
|
55
|
+
attr_reader :forecast, :options, :output
|
58
56
|
|
59
|
-
def initialize(
|
60
|
-
@
|
57
|
+
def initialize(forecast, options)
|
58
|
+
@forecast = forecast
|
61
59
|
@options = options
|
62
60
|
@output = []
|
63
61
|
end
|
64
62
|
|
65
|
-
def tracked?(
|
66
|
-
|
67
|
-
|
68
|
-
block[:transactions].any? do |_, transactions|
|
69
|
-
transactions.any? { |t| t[:track] }
|
70
|
-
end
|
71
|
-
end
|
63
|
+
def tracked?(forecast)
|
64
|
+
forecast.any? do |row|
|
65
|
+
return true if row[:track] == true
|
72
66
|
end
|
67
|
+
|
68
|
+
return false
|
73
69
|
end
|
74
70
|
|
75
|
-
def process_tracked(
|
76
|
-
|
77
|
-
|
78
|
-
next unless t[:track]
|
71
|
+
def process_tracked(row)
|
72
|
+
row[:transactions].each do |t|
|
73
|
+
next if t[:track] == false
|
79
74
|
|
80
|
-
|
81
|
-
|
75
|
+
category = t[:category].ljust(options[:max_category])
|
76
|
+
amount = t[:amount].to_s.ljust(options[:max_amount])
|
82
77
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
78
|
+
header = "~ #{Date.new(Date.today.year, Date.today.month,
|
79
|
+
1).next_month} * [TRACKED] #{t[:description]}\n"
|
80
|
+
transactions = " #{category} #{amount}; #{t[:description]}\n"
|
81
|
+
footer = " #{row[:account]}\n\n"
|
87
82
|
|
88
|
-
|
89
|
-
end
|
83
|
+
output << { header: header, transactions: [transactions], footer: footer }
|
90
84
|
end
|
91
85
|
end
|
92
86
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module HledgerForecast
|
2
|
+
class Utilities
|
3
|
+
def self.convert_amount(amount)
|
4
|
+
case amount
|
5
|
+
when /^-?\d+\.\d+$/ # Detects floating-point numbers (including negatives)
|
6
|
+
amount.to_f
|
7
|
+
when /^-?\d+$/ # Detects integers (including negatives)
|
8
|
+
amount.to_i
|
9
|
+
else
|
10
|
+
amount
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/hledger_forecast.rb
CHANGED
@@ -8,7 +8,6 @@ require 'highline'
|
|
8
8
|
require 'money'
|
9
9
|
require 'optparse'
|
10
10
|
require 'terminal-table'
|
11
|
-
require 'yaml'
|
12
11
|
|
13
12
|
Money.locale_backend = nil
|
14
13
|
Money.rounding_mode = BigDecimal::ROUND_HALF_UP
|
@@ -17,12 +16,12 @@ Money.default_currency = 'USD'
|
|
17
16
|
require_relative 'hledger_forecast/calculator'
|
18
17
|
require_relative 'hledger_forecast/cli'
|
19
18
|
require_relative 'hledger_forecast/comparator'
|
20
|
-
require_relative 'hledger_forecast/csv_parser'
|
21
19
|
require_relative 'hledger_forecast/formatter'
|
22
20
|
require_relative 'hledger_forecast/generator'
|
23
21
|
require_relative 'hledger_forecast/settings'
|
24
22
|
require_relative 'hledger_forecast/summarizer'
|
25
23
|
require_relative 'hledger_forecast/summarizer_formatter'
|
24
|
+
require_relative 'hledger_forecast/utilities'
|
26
25
|
require_relative 'hledger_forecast/version'
|
27
26
|
|
28
27
|
require_relative 'hledger_forecast/transactions/default'
|
data/spec/cli_spec.rb
CHANGED
@@ -2,12 +2,12 @@ require_relative '../lib/hledger_forecast'
|
|
2
2
|
|
3
3
|
output = <<~JOURNAL
|
4
4
|
~ monthly from 2023-03-01 * Mortgage, Food
|
5
|
-
Expenses:Mortgage £2,000.55; Mortgage
|
6
|
-
Expenses:Food £100.00
|
5
|
+
Expenses:Mortgage £2,000.55 ; Mortgage
|
6
|
+
Expenses:Food £100.00 ; Food
|
7
7
|
Assets:Bank
|
8
8
|
|
9
9
|
~ monthly from 2023-03-01 * Savings
|
10
|
-
Assets:Bank £-1,000.00; Savings
|
10
|
+
Assets:Bank £-1,000.00 ; Savings
|
11
11
|
Assets:Savings
|
12
12
|
|
13
13
|
JOURNAL
|
@@ -26,15 +26,6 @@ ensure
|
|
26
26
|
end
|
27
27
|
|
28
28
|
RSpec.describe 'command' do
|
29
|
-
it 'uses the CLI to generate an output' do
|
30
|
-
generated_journal = './test_output.journal'
|
31
|
-
File.delete(generated_journal) if File.exist?(generated_journal)
|
32
|
-
|
33
|
-
system("./bin/hledger-forecast generate -f ./spec/stubs/forecast.yml -o ./test_output.journal --force")
|
34
|
-
|
35
|
-
expect(File.read(generated_journal)).to eq(output)
|
36
|
-
end
|
37
|
-
|
38
29
|
it 'uses the CLI to generate an output with a CSV config file' do
|
39
30
|
generated_journal = './test_output.journal'
|
40
31
|
File.delete(generated_journal) if File.exist?(generated_journal)
|
@@ -1,29 +1,18 @@
|
|
1
1
|
require_relative '../lib/hledger_forecast'
|
2
2
|
|
3
|
-
config = <<~
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
monthly:
|
8
|
-
|
9
|
-
|
10
|
-
transactions:
|
11
|
-
- amount: "=5000/24"
|
12
|
-
category: "Expenses:House"
|
13
|
-
description: New Kitchen
|
14
|
-
- amount: "=25*4.3"
|
15
|
-
category: "Expenses:Food"
|
16
|
-
description: Monthly food shop
|
17
|
-
- amount: "=(102.50+3.25)/2"
|
18
|
-
category: "Expenses:Food"
|
19
|
-
description: Random food
|
20
|
-
YAML
|
3
|
+
config = <<~CSV
|
4
|
+
type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
|
5
|
+
monthly,,Liabilities:Amex,01/05/2023,,New kitchen,Expenses:House,=5000/24,,,
|
6
|
+
monthly,,Liabilities:Amex,01/05/2023,,Monthly food shop,Expenses:Food,=25*4.3,,,
|
7
|
+
monthly,,Liabilities:Amex,01/05/2023,,Random food,Expenses:Food,=(102.50+3.25)/2,,,
|
8
|
+
settings,currency,GBP,,,,,,,,
|
9
|
+
CSV
|
21
10
|
|
22
11
|
output = <<~JOURNAL
|
23
|
-
~ monthly from 2023-05-01 * New
|
24
|
-
Expenses:House £208.33
|
25
|
-
Expenses:Food £107.50
|
26
|
-
Expenses:Food £52.88
|
12
|
+
~ monthly from 2023-05-01 * New kitchen, Monthly food shop, Random food
|
13
|
+
Expenses:House £208.33 ; New kitchen
|
14
|
+
Expenses:Food £107.50 ; Monthly food shop
|
15
|
+
Expenses:Food £52.88 ; Random food
|
27
16
|
Liabilities:Amex
|
28
17
|
|
29
18
|
JOURNAL
|
data/spec/custom_spec.rb
CHANGED
@@ -1,52 +1,32 @@
|
|
1
1
|
require_relative '../lib/hledger_forecast'
|
2
2
|
|
3
|
-
base_config = <<~
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
category: "[Expenses:Personal Care]"
|
10
|
-
description: Hair and beauty
|
11
|
-
frequency: "every 2 weeks"
|
12
|
-
- amount: 50
|
13
|
-
category: "[Expenses:Groceries]"
|
14
|
-
description: Gotta feed that stomach
|
15
|
-
frequency: "every 5 days"
|
16
|
-
|
17
|
-
settings:
|
18
|
-
currency: GBP
|
19
|
-
YAML
|
3
|
+
base_config = <<~CSV
|
4
|
+
type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
|
5
|
+
custom,every 2 weeks,[Assets:Bank],01/05/2023,,Hair and beauty,[Expenses:Personal Care],80,,,
|
6
|
+
custom,every 5 days,[Assets:Bank],01/05/2023,,Food,[Expenses:Groceries],50,,,
|
7
|
+
settings,currency,GBP,,,,,,,,
|
8
|
+
CSV
|
20
9
|
|
21
10
|
base_output = <<~JOURNAL
|
22
11
|
~ every 2 weeks from 2023-05-01 * Hair and beauty
|
23
|
-
[Expenses:Personal Care] £80.00; Hair and beauty
|
12
|
+
[Expenses:Personal Care] £80.00 ; Hair and beauty
|
24
13
|
[Assets:Bank]
|
25
14
|
|
26
|
-
~ every 5 days from 2023-05-01 *
|
27
|
-
[Expenses:Groceries] £50.00;
|
15
|
+
~ every 5 days from 2023-05-01 * Food
|
16
|
+
[Expenses:Groceries] £50.00 ; Food
|
28
17
|
[Assets:Bank]
|
29
18
|
|
30
19
|
JOURNAL
|
31
20
|
|
32
|
-
calculated_config = <<~
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
- amount: 80
|
38
|
-
category: "[Expenses:Personal Care]"
|
39
|
-
description: Hair and beauty
|
40
|
-
frequency: "every 2 weeks"
|
41
|
-
to: "=6"
|
42
|
-
|
43
|
-
settings:
|
44
|
-
currency: GBP
|
45
|
-
YAML
|
21
|
+
calculated_config = <<~CSV
|
22
|
+
type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
|
23
|
+
custom,every 2 weeks,[Assets:Bank],01/05/2023,=6,Hair and beauty,[Expenses:Personal Care],80,,,
|
24
|
+
settings,currency,GBP,,,,,,,,
|
25
|
+
CSV
|
46
26
|
|
47
27
|
calculated_output = <<~JOURNAL
|
48
28
|
~ every 2 weeks from 2023-05-01 to 2023-10-31 * Hair and beauty
|
49
|
-
[Expenses:Personal Care] £80.00; Hair and beauty
|
29
|
+
[Expenses:Personal Care] £80.00 ; Hair and beauty
|
50
30
|
[Assets:Bank]
|
51
31
|
|
52
32
|
JOURNAL
|
data/spec/half-yearly_spec.rb
CHANGED
@@ -1,21 +1,14 @@
|
|
1
1
|
require_relative '../lib/hledger_forecast'
|
2
2
|
|
3
|
-
config = <<~
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
- from: "2023-04-01"
|
9
|
-
account: "Assets:Bank"
|
10
|
-
transactions:
|
11
|
-
- description: Holiday
|
12
|
-
category: "Expenses:Holiday"
|
13
|
-
amount: 500
|
14
|
-
YAML
|
3
|
+
config = <<~CSV
|
4
|
+
type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
|
5
|
+
half-yearly,,Assets:Bank,01/04/2023,,Holiday,Expenses:Holiday,500,,,
|
6
|
+
settings,currency,GBP,,,,,,,,
|
7
|
+
CSV
|
15
8
|
|
16
9
|
output = <<~JOURNAL
|
17
10
|
~ every 6 months from 2023-04-01 * Holiday
|
18
|
-
Expenses:Holiday £500.00; Holiday
|
11
|
+
Expenses:Holiday £500.00 ; Holiday
|
19
12
|
Assets:Bank
|
20
13
|
|
21
14
|
JOURNAL
|
@@ -1,26 +1,16 @@
|
|
1
1
|
require_relative '../lib/hledger_forecast'
|
2
2
|
|
3
|
-
config = <<~
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
to: "2023-06-01"
|
10
|
-
account: "Assets:Bank"
|
11
|
-
transactions:
|
12
|
-
- description: Mortgage
|
13
|
-
category: "Expenses:Mortgage"
|
14
|
-
amount: 2000.00
|
15
|
-
- description: Food
|
16
|
-
category: "Expenses:Food"
|
17
|
-
amount: 100.00
|
18
|
-
YAML
|
3
|
+
config = <<~CSV
|
4
|
+
type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
|
5
|
+
monthly,,Assets:Bank,01/03/2023,01/06/2023,Mortgage,Expenses:Mortgage,2000.00,,,
|
6
|
+
monthly,,Assets:Bank,01/03/2023,01/06/2023,Food,Expenses:Food,100.00,,,
|
7
|
+
settings,currency,GBP,,,,,,,,
|
8
|
+
CSV
|
19
9
|
|
20
10
|
output = <<~JOURNAL
|
21
11
|
~ monthly from 2023-03-01 to 2023-06-01 * Mortgage, Food
|
22
|
-
Expenses:Mortgage £2,000.00; Mortgage
|
23
|
-
Expenses:Food £100.00
|
12
|
+
Expenses:Mortgage £2,000.00 ; Mortgage
|
13
|
+
Expenses:Food £100.00 ; Food
|
24
14
|
Assets:Bank
|
25
15
|
|
26
16
|
JOURNAL
|