hledger-forecast 2.0.0 → 3.0.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{test.yml → ci.yml} +18 -10
  3. data/.github/workflows/publish_ruby_gem.yml +24 -0
  4. data/.github/workflows/release.yml +12 -13
  5. data/.mise.toml +2 -0
  6. data/CHANGELOG.md +17 -0
  7. data/Gemfile +1 -0
  8. data/README.md +149 -119
  9. data/example.csv +15 -15
  10. data/example.journal +17 -18
  11. data/hledger-forecast.gemspec +20 -18
  12. data/lib/hledger_forecast/calculator.rb +7 -15
  13. data/lib/hledger_forecast/cli.rb +98 -71
  14. data/lib/hledger_forecast/comparator.rb +12 -11
  15. data/lib/hledger_forecast/forecast.rb +29 -0
  16. data/lib/hledger_forecast/formatter.rb +13 -15
  17. data/lib/hledger_forecast/generator.rb +32 -72
  18. data/lib/hledger_forecast/settings.rb +34 -47
  19. data/lib/hledger_forecast/summarizer.rb +34 -55
  20. data/lib/hledger_forecast/summarizer_formatter.rb +75 -78
  21. data/lib/hledger_forecast/transaction.rb +63 -0
  22. data/lib/hledger_forecast/transactions/default.rb +45 -72
  23. data/lib/hledger_forecast/version.rb +1 -1
  24. data/lib/hledger_forecast.rb +21 -22
  25. data/spec/calculator_spec.rb +45 -0
  26. data/spec/cli_spec.rb +19 -17
  27. data/spec/compare_spec.rb +16 -14
  28. data/spec/computed_amounts_spec.rb +7 -7
  29. data/spec/custom_spec.rb +9 -9
  30. data/spec/formatter_spec.rb +51 -0
  31. data/spec/half-yearly_spec.rb +5 -5
  32. data/spec/monthly_end_date_spec.rb +6 -6
  33. data/spec/monthly_end_date_transaction_spec.rb +10 -10
  34. data/spec/monthly_spec.rb +7 -7
  35. data/spec/once_spec.rb +5 -5
  36. data/spec/quarterly_spec.rb +5 -5
  37. data/spec/settings_spec.rb +101 -0
  38. data/spec/stubs/forecast.csv +4 -4
  39. data/spec/summarizer_spec.rb +28 -33
  40. data/spec/tags_spec.rb +92 -0
  41. data/spec/verbose_output_spec.rb +8 -8
  42. data/spec/yearly_spec.rb +5 -5
  43. metadata +49 -13
  44. data/lib/hledger_forecast/transactions/modifiers.rb +0 -90
  45. data/lib/hledger_forecast/transactions/trackers.rb +0 -88
  46. data/lib/hledger_forecast/utilities.rb +0 -14
  47. data/spec/track_spec.rb +0 -105
@@ -1,55 +1,50 @@
1
- require_relative '../lib/hledger_forecast'
1
+ require_relative "../lib/hledger_forecast"
2
2
 
3
3
  config = <<~CSV
4
- type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
5
- monthly,,Assets:Bank,01/03/2023,=24,Mortgage,Expenses:Mortgage,2000.55,,,
6
- monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,100,,,
7
- monthly,,Assets:Savings,01/03/2023,,Savings,Assets:Bank,-1000,,,
8
- custom,every 2 weeks,[Assets:Bank],01/05/2023,,Hair and beauty,[Expenses:Personal Care],80,26,,
9
- custom,every 2 weeks,[Assets:Checking],01/05/2023,,Extra Food,[Expenses:Groceries],50,73,,
4
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude
5
+ monthly,,Assets:Bank,01/03/2023,=24,Mortgage,Expenses:Mortgage,2000.55,,
6
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,100,,
7
+ monthly,,Assets:Savings,01/03/2023,,Savings,Assets:Bank,-1000,,
8
+ custom,every 2 weeks,[Assets:Bank],01/05/2023,,Hair and beauty,[Expenses:Personal Care],80,26,
9
+ custom,every 2 weeks,[Assets:Checking],01/05/2023,,Extra Food,[Expenses:Groceries],50,73,
10
10
  settings,currency,GBP,,,,,,,,
11
11
  CSV
12
12
 
13
13
  RSpec.describe HledgerForecast::Summarizer do
14
- let(:summarizer) { described_class.new }
14
+ describe "#summarize with roll_up" do
15
+ let(:result) { described_class.summarize(config, {roll_up: "monthly"}) }
16
+ let(:output) { result[:output] }
15
17
 
16
- describe '#generate with roll_up' do
17
- let(:forecast) { CSV.parse(config, headers: true) }
18
- let(:cli_options) { { roll_up: 'monthly' } }
19
-
20
- before do
21
- summarizer.summarize(config, cli_options)
18
+ it "includes the expected summary keys" do
19
+ expect(output.first).to(include(:account, :from, :to, :type, :frequency))
22
20
  end
23
21
 
24
- it 'generates the correct output' do
25
- output = summarizer.send(:generate, forecast)
22
+ it "returns the raw amount on each row" do
23
+ expect(output.first[:amount]).to(eq(2000.55))
24
+ end
26
25
 
27
- expect(output.first).to include(:account, :from, :to, :type, :frequency)
28
- expect(output.first[:amount]).to eq(2000.55)
29
- expect(output.last[:rolled_up_amount]).to eq((50.0 * 73.0) / 12.0) # ((50 * 73) / 12)
30
- expect(output.length).to eq(5)
26
+ it "calculates rolled_up_amount for custom transactions" do
27
+ expect(output.last[:rolled_up_amount]).to(eq((50.0 * 73.0) / 12.0))
31
28
  end
32
29
 
33
- it 'transaction TO date take precedence over block TO date' do
34
- output = summarizer.send(:generate, forecast)
30
+ it "returns a row for each non-excluded transaction" do
31
+ expect(output.length).to(eq(5))
32
+ end
35
33
 
36
- expect(output.first[:to]).to eq(Date.parse("2025-02-28"))
34
+ it "uses the calculated TO date from the CSV formula" do
35
+ expect(output.first[:to]).to(eq(Date.parse("2025-02-28")))
37
36
  end
38
37
  end
39
38
 
40
- describe '#generate' do
41
- let(:forecast) { CSV.parse(config, headers: true) }
42
- let(:cli_options) { nil }
39
+ describe "#summarize without roll_up" do
40
+ let(:output) { described_class.summarize(config)[:output] }
43
41
 
44
- before do
45
- summarizer.summarize(config, cli_options)
42
+ it "returns a row for each non-excluded transaction" do
43
+ expect(output.length).to(eq(5))
46
44
  end
47
45
 
48
- it 'generates the correct output' do
49
- output = summarizer.send(:generate, forecast)
50
-
51
- # expect(output.first).to include(:account, :from, :to, :type, :frequency)
52
- expect(output.length).to eq(5)
46
+ it "includes annualised_amount" do
47
+ expect(output.first).to(have_key(:annualised_amount))
53
48
  end
54
49
  end
55
50
  end
data/spec/tags_spec.rb ADDED
@@ -0,0 +1,92 @@
1
+ require_relative "../lib/hledger_forecast"
2
+
3
+ config = <<~CSV
4
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,tag
5
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,fixed|essential
6
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,living|essential
7
+ monthly,,Assets:Bank,01/03/2023,,Netflix,Expenses:Subscriptions,15,,,living
8
+ settings,currency,GBP,,,,,,,,
9
+ CSV
10
+
11
+ config_without_tags = <<~CSV
12
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,tag
13
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,,
14
+ monthly,,Assets:Bank,01/03/2023,,Food,Expenses:Food,500,,,
15
+ settings,currency,GBP,,,,,,,,
16
+ CSV
17
+
18
+ RSpec.describe "tags" do
19
+ it "outputs hledger tags in posting comments" do
20
+ expected = <<~JOURNAL
21
+ ~ monthly from 2023-03-01 * Salary, Food, Netflix
22
+ Income:Salary £-3,500.00; fixed:, essential:
23
+ Expenses:Food £500.00 ; living:, essential:
24
+ Expenses:Subscriptions £15.00 ; living:
25
+ Assets:Bank
26
+
27
+ JOURNAL
28
+
29
+ expect(HledgerForecast::Generator.generate(config)).to(eq(expected))
30
+ end
31
+
32
+ it "omits comments when no tags are present" do
33
+ expected = <<~JOURNAL
34
+ ~ monthly from 2023-03-01 * Salary, Food
35
+ Income:Salary £-3,500.00
36
+ Expenses:Food £500.00
37
+ Assets:Bank
38
+
39
+ JOURNAL
40
+
41
+ expect(HledgerForecast::Generator.generate(config_without_tags)).to(eq(expected))
42
+ end
43
+
44
+ it "filters transactions by a single tag" do
45
+ expected = <<~JOURNAL
46
+ ~ monthly from 2023-03-01 * Food, Netflix
47
+ Expenses:Food £500.00; living:, essential:
48
+ Expenses:Subscriptions £15.00 ; living:
49
+ Assets:Bank
50
+
51
+ JOURNAL
52
+
53
+ expect(HledgerForecast::Generator.generate(config, {tags: ["living"]})).to(eq(expected))
54
+ end
55
+
56
+ it "filters transactions by multiple tags (OR logic)" do
57
+ expected = <<~JOURNAL
58
+ ~ monthly from 2023-03-01 * Salary, Food
59
+ Income:Salary £-3,500.00; fixed:, essential:
60
+ Expenses:Food £500.00 ; living:, essential:
61
+ Assets:Bank
62
+
63
+ JOURNAL
64
+
65
+ expect(HledgerForecast::Generator.generate(config, {tags: ["fixed", "essential"]})).to(eq(expected))
66
+ end
67
+
68
+ it "filters summarizer output by tags" do
69
+ result = HledgerForecast::Summarizer.summarize(config, {tags: ["living"]})
70
+ descriptions = result[:output].map { |r| r[:description] }
71
+
72
+ expect(descriptions).to(eq(["Food", "Netflix"]))
73
+ end
74
+
75
+ it "raises an error when --tags is used on a CSV without a tag column" do
76
+ csv_without_tag_column = <<~CSV
77
+ type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude
78
+ monthly,,Assets:Bank,01/03/2023,,Salary,Income:Salary,-3500,,
79
+ settings,currency,GBP,,,,,,,,
80
+ CSV
81
+
82
+ expect {
83
+ HledgerForecast::Generator.generate(csv_without_tag_column, {tags: ["fixed"]})
84
+ }
85
+ .to(raise_error(RuntimeError, /tag.*column/i))
86
+
87
+ expect {
88
+ HledgerForecast::Summarizer.summarize(csv_without_tag_column, {tags: ["fixed"]})
89
+ }
90
+ .to(raise_error(RuntimeError, /tag.*column/i))
91
+ end
92
+ end
@@ -1,27 +1,27 @@
1
- require_relative '../lib/hledger_forecast'
1
+ require_relative "../lib/hledger_forecast"
2
2
 
3
3
  output = <<~JOURNAL
4
4
  ~ monthly from 2023-03-01 * Mortgage
5
- Expenses:Mortgage £2,000.55 ; Mortgage
5
+ Expenses:Mortgage £2,000.55
6
6
  Assets:Bank
7
7
 
8
8
  ~ monthly from 2023-03-01 * Food
9
- Expenses:Food £100.00 ; Food
9
+ Expenses:Food £100.00
10
10
  Assets:Bank
11
11
 
12
12
  ~ monthly from 2023-03-01 * Savings
13
- Assets:Bank £-1,000.00 ; Savings
13
+ Assets:Bank £-1,000.00
14
14
  Assets:Savings
15
15
 
16
16
  JOURNAL
17
17
 
18
- RSpec.describe 'verbose command' do
19
- it 'does not group similar type transactions together in the output' do
20
- generated_journal = './test_output.journal'
18
+ RSpec.describe "verbose command" do
19
+ it "does not group similar type transactions together in the output" do
20
+ generated_journal = "./test_output.journal"
21
21
  File.delete(generated_journal) if File.exist?(generated_journal)
22
22
 
23
23
  system("./bin/hledger-forecast generate -f ./spec/stubs/forecast.csv -o ./test_output.journal --verbose --force")
24
24
 
25
- expect(File.read(generated_journal)).to eq(output)
25
+ expect(File.read(generated_journal)).to(eq(output))
26
26
  end
27
27
  end
data/spec/yearly_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require_relative '../lib/hledger_forecast'
1
+ require_relative "../lib/hledger_forecast"
2
2
 
3
3
  config = <<~CSV
4
4
  type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
@@ -8,13 +8,13 @@ CSV
8
8
 
9
9
  output = <<~JOURNAL
10
10
  ~ yearly from 2023-04-01 * Bonus
11
- Income:Bonus £-3,000.00; Bonus
11
+ Income:Bonus £-3,000.00
12
12
  Assets:Bank
13
13
 
14
14
  JOURNAL
15
15
 
16
- RSpec.describe 'generate' do
17
- it 'generates a forecast with correct YEARLY transactions' do
18
- expect(HledgerForecast::Generator.generate(config)).to eq(output)
16
+ RSpec.describe "generate" do
17
+ it "generates a forecast with correct YEARLY transactions" do
18
+ expect(HledgerForecast::Generator.generate(config)).to(eq(output))
19
19
  end
20
20
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hledger-forecast
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oli Morris
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-03 00:00:00.000000000 Z
11
+ date: 2026-03-27 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: abbrev
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: csv
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: colorize
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -101,10 +129,13 @@ executables:
101
129
  extensions: []
102
130
  extra_rdoc_files: []
103
131
  files:
132
+ - ".github/workflows/ci.yml"
133
+ - ".github/workflows/publish_ruby_gem.yml"
104
134
  - ".github/workflows/release.yml"
105
- - ".github/workflows/test.yml"
106
135
  - ".gitignore"
136
+ - ".mise.toml"
107
137
  - ".rubocop.yml"
138
+ - CHANGELOG.md
108
139
  - Gemfile
109
140
  - LICENSE
110
141
  - README.md
@@ -116,26 +147,28 @@ files:
116
147
  - lib/hledger_forecast/calculator.rb
117
148
  - lib/hledger_forecast/cli.rb
118
149
  - lib/hledger_forecast/comparator.rb
150
+ - lib/hledger_forecast/forecast.rb
119
151
  - lib/hledger_forecast/formatter.rb
120
152
  - lib/hledger_forecast/generator.rb
121
153
  - lib/hledger_forecast/settings.rb
122
154
  - lib/hledger_forecast/summarizer.rb
123
155
  - lib/hledger_forecast/summarizer_formatter.rb
156
+ - lib/hledger_forecast/transaction.rb
124
157
  - lib/hledger_forecast/transactions/default.rb
125
- - lib/hledger_forecast/transactions/modifiers.rb
126
- - lib/hledger_forecast/transactions/trackers.rb
127
- - lib/hledger_forecast/utilities.rb
128
158
  - lib/hledger_forecast/version.rb
159
+ - spec/calculator_spec.rb
129
160
  - spec/cli_spec.rb
130
161
  - spec/compare_spec.rb
131
162
  - spec/computed_amounts_spec.rb
132
163
  - spec/custom_spec.rb
164
+ - spec/formatter_spec.rb
133
165
  - spec/half-yearly_spec.rb
134
166
  - spec/monthly_end_date_spec.rb
135
167
  - spec/monthly_end_date_transaction_spec.rb
136
168
  - spec/monthly_spec.rb
137
169
  - spec/once_spec.rb
138
170
  - spec/quarterly_spec.rb
171
+ - spec/settings_spec.rb
139
172
  - spec/stubs/forecast.csv
140
173
  - spec/stubs/output1.csv
141
174
  - spec/stubs/output2.csv
@@ -143,14 +176,14 @@ files:
143
176
  - spec/stubs/transactions_found_inverse.journal
144
177
  - spec/stubs/transactions_not_found.journal
145
178
  - spec/summarizer_spec.rb
146
- - spec/track_spec.rb
179
+ - spec/tags_spec.rb
147
180
  - spec/verbose_output_spec.rb
148
181
  - spec/yearly_spec.rb
149
182
  homepage: https://github.com/olimorris/hledger-forecast
150
183
  licenses:
151
184
  - MIT
152
185
  metadata: {}
153
- post_install_message:
186
+ post_install_message:
154
187
  rdoc_options: []
155
188
  require_paths:
156
189
  - lib
@@ -158,28 +191,31 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
191
  requirements:
159
192
  - - "~>"
160
193
  - !ruby/object:Gem::Version
161
- version: '3.0'
194
+ version: '3.3'
162
195
  required_rubygems_version: !ruby/object:Gem::Requirement
163
196
  requirements:
164
197
  - - ">="
165
198
  - !ruby/object:Gem::Version
166
199
  version: '0'
167
200
  requirements: []
168
- rubygems_version: 3.3.5
169
- signing_key:
201
+ rubygems_version: 3.4.20
202
+ signing_key:
170
203
  specification_version: 4
171
204
  summary: An extended wrapper around hledger's forecasting functionality
172
205
  test_files:
206
+ - spec/calculator_spec.rb
173
207
  - spec/cli_spec.rb
174
208
  - spec/compare_spec.rb
175
209
  - spec/computed_amounts_spec.rb
176
210
  - spec/custom_spec.rb
211
+ - spec/formatter_spec.rb
177
212
  - spec/half-yearly_spec.rb
178
213
  - spec/monthly_end_date_spec.rb
179
214
  - spec/monthly_end_date_transaction_spec.rb
180
215
  - spec/monthly_spec.rb
181
216
  - spec/once_spec.rb
182
217
  - spec/quarterly_spec.rb
218
+ - spec/settings_spec.rb
183
219
  - spec/stubs/forecast.csv
184
220
  - spec/stubs/output1.csv
185
221
  - spec/stubs/output2.csv
@@ -187,6 +223,6 @@ test_files:
187
223
  - spec/stubs/transactions_found_inverse.journal
188
224
  - spec/stubs/transactions_not_found.journal
189
225
  - spec/summarizer_spec.rb
190
- - spec/track_spec.rb
226
+ - spec/tags_spec.rb
191
227
  - spec/verbose_output_spec.rb
192
228
  - spec/yearly_spec.rb
@@ -1,90 +0,0 @@
1
- module HledgerForecast
2
- module Transactions
3
- # Generate auto-posting hledger transactions
4
- # Example output:
5
- # = Expenses:Groceries date:2024-01-01..2025-12-31
6
- # Expenses:Groceries *0.1 ; Groceries
7
- # Assets:Checking *-0.1
8
- class Modifiers
9
- def self.generate(data, options)
10
- new(data, options).generate
11
- end
12
-
13
- def generate
14
- return nil unless modifiers?
15
-
16
- process_modifier
17
-
18
- output
19
- end
20
-
21
- def self.get_modifiers(transaction, block)
22
- modifiers = []
23
-
24
- transaction['modifiers'].each do |modifier|
25
- description = transaction['description']
26
- description += " - #{modifier['description']}" unless modifier['description'].empty?
27
-
28
- modifiers << {
29
- account: block['account'],
30
- amount: modifier['amount'],
31
- category: transaction['category'],
32
- description: description,
33
- from: Date.parse(modifier['from'] || block['from']),
34
- to: modifier['to'] ? Date.parse(modifier['to']) : nil
35
- }
36
- end
37
-
38
- modifiers
39
- end
40
-
41
- private
42
-
43
- attr_reader :data, :options, :output
44
-
45
- def initialize(data, options)
46
- @data = data
47
- @options = options
48
- @output = []
49
- end
50
-
51
- def modifiers?
52
- @data.any? do |_, blocks|
53
- blocks.any? do |block|
54
- block[:transactions].any? do |_, transactions|
55
- transactions.any? { |t| !t[:modifiers].empty? }
56
- end
57
- end
58
- end
59
- end
60
-
61
- def process_modifier
62
- get_transactions.each do |modifier|
63
- account = modifier[:account].ljust(@options[:max_category])
64
- category = modifier[:category].ljust(@options[:max_category])
65
- # Fix the ljust by counting strings in amount
66
- amount = modifier[:amount].to_s.ljust(@options[:max_amount] - 1)
67
- to = modifier[:to] ? "..#{modifier[:to]}" : nil
68
-
69
- header = "= #{modifier[:category]} date:#{modifier[:from]}#{to}\n"
70
- transactions = " #{category} *#{amount}; #{modifier[:description]}\n"
71
- footer = " #{account} *#{modifier[:amount] * -1}\n\n"
72
-
73
- output << { header: header, transactions: [transactions], footer: footer }
74
- end
75
- end
76
-
77
- def get_transactions
78
- @data.each_with_object([]) do |(_key, blocks), result|
79
- blocks.each do |block|
80
- block[:transactions].each_value do |transactions|
81
- transactions.each do |t|
82
- result.concat(t[:modifiers]) if t[:modifiers]
83
- end
84
- end
85
- end
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,88 +0,0 @@
1
- module HledgerForecast
2
- module Transactions
3
- # Generate hledger transactions based on the non-existance of a transaction
4
- # in your ledger. This is useful for ensuring that certain expenses are
5
- # accounted for, even if you forget to enter them.
6
- #
7
- # Example output:
8
- # ~ 2023-05-1 * [TRACKED] Food expenses
9
- # Expenses:Groceries $250.00 ; Food expenses
10
- # Assets:Checking
11
- class Trackers
12
- def self.generate(forecast, options)
13
- new(forecast, options).generate
14
- end
15
-
16
- def generate
17
- return if @options[:no_track]
18
- return nil unless tracked?(forecast)
19
-
20
- forecast.each do |row|
21
- process_tracked(row)
22
- end
23
-
24
- output
25
- end
26
-
27
- def self.track?(row, options)
28
- now = Date.today
29
- row['track'] && Date.parse(row['from']) <= now && !exists?(row, now, options)
30
- end
31
-
32
- def self.exists?(row, now, options)
33
- unless options[:transaction_file]
34
- puts "\nWarning: ".bold.yellow + "For tracked transactions, please specify a file with the `-t` flag"
35
- puts "ERROR: ".bold.red + "Tracked transactions ignored for now"
36
- return
37
- end
38
-
39
- # Format the money
40
- amount = Formatter.format_money(row['amount'], options)
41
- inverse_amount = Formatter.format_money(row['amount'] * -1, options)
42
-
43
- from = Date.parse(row['from'])
44
- category = row['category'].gsub('[', '\\[').gsub(']', '\\]').gsub('(', '\\(').gsub(')', '\\)')
45
-
46
- # We run two commands and check to see if category +/- amount or account +/- amount exists
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})")
49
-
50
- system(command1) || system(command2)
51
- end
52
-
53
- private
54
-
55
- attr_reader :forecast, :options, :output
56
-
57
- def initialize(forecast, options)
58
- @forecast = forecast
59
- @options = options
60
- @output = []
61
- end
62
-
63
- def tracked?(forecast)
64
- forecast.any? do |row|
65
- return true if row[:track] == true
66
- end
67
-
68
- return false
69
- end
70
-
71
- def process_tracked(row)
72
- row[:transactions].each do |t|
73
- next if t[:track] == false
74
-
75
- category = t[:category].ljust(options[:max_category])
76
- amount = t[:amount].to_s.ljust(options[:max_amount])
77
-
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"
82
-
83
- output << { header: header, transactions: [transactions], footer: footer }
84
- end
85
- end
86
- end
87
- end
88
- end
@@ -1,14 +0,0 @@
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/spec/track_spec.rb DELETED
@@ -1,105 +0,0 @@
1
- require_relative '../lib/hledger_forecast'
2
-
3
- current_month = Date.new(Date.today.year, Date.today.month, 1)
4
- previous_month = current_month.prev_month
5
- next_month = current_month.next_month
6
-
7
- base_config = <<~CSV
8
- type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
9
- once,,Assets:Bank,05/03/2023,,Tax owed,Expenses:Tax,3000,,,true
10
- once,,Assets:Bank,05/03/2023,,Food expenses,Expenses:Food,100,,,
11
- once,,Assets:Bank,05/03/2023,01/08/2023,Salary,Income:Salary,-1500,,,true
12
- settings,currency,GBP,,,,,,,,
13
- CSV
14
-
15
- base_output = <<~JOURNAL
16
- ~ 2023-03-05 * Food expenses
17
- Expenses:Food £100.00 ; Food expenses
18
- Assets:Bank
19
-
20
- ~ #{next_month} * [TRACKED] Tax owed
21
- Expenses:Tax £3,000.00; Tax owed
22
- Assets:Bank
23
-
24
- ~ #{next_month} * [TRACKED] Salary
25
- Income:Salary £-1,500.00; Salary
26
- Assets:Bank
27
-
28
- JOURNAL
29
-
30
- RSpec.describe 'Tracking transactions -' do
31
- it 'writes a NON-FOUND entry into a journal' do
32
- options = {}
33
- options[:transaction_file] = 'spec/stubs/transactions_not_found.journal'
34
-
35
- generated_journal = HledgerForecast::Generator.generate(base_config, options)
36
-
37
- expect(generated_journal).to eq(base_output)
38
- end
39
-
40
- it 'writes a NON-FOUND entry for dates that are close to the current period' do
41
- require 'tempfile'
42
-
43
- forecast_config = <<~CSV
44
- type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
45
- once,,Assets:Bank,"#{previous_month}",,New kitchen,Expenses:House,5000,,,true
46
- settings,currency,GBP,,,,,,,,
47
- CSV
48
-
49
- journal = <<~JOURNAL
50
- #{previous_month} * Opening balance
51
- Assets:Bank £1,000.00
52
- Equity:Opening balance
53
-
54
- #{previous_month} * Mortgage payment
55
- Expenses:Mortgage £1,500.00
56
- Assets:Bank
57
-
58
- #{current_month - 10} * Groceries
59
- Expenses:Groceries £1,500.00
60
- Assets:Bank
61
-
62
- JOURNAL
63
-
64
- temp_file = Tempfile.new('journal')
65
- temp_file.write(journal)
66
- temp_file.close
67
-
68
- options = {}
69
- options[:transaction_file] = temp_file.path
70
-
71
- generated_journal = HledgerForecast::Generator.generate(forecast_config, options)
72
-
73
- expected_output = <<~JOURNAL
74
-
75
- ~ #{next_month} * [TRACKED] New kitchen
76
- Expenses:House £5,000.00; New kitchen
77
- Assets:Bank
78
-
79
- JOURNAL
80
-
81
- expect(generated_journal).to eq(expected_output)
82
- end
83
-
84
- it 'treats a future tracked transaction as a regular transaction' do
85
- forecast_config = <<~CSV
86
- type,frequency,account,from,to,description,category,amount,roll-up,summary_exclude,track
87
- monthly,,Assets:Bank,"#{next_month}",,Food expenses,Expenses:Food,100,,,true
88
- settings,currency,GBP,,,,,,,,
89
- CSV
90
-
91
- options = {}
92
- options[:transaction_file] = 'spec/stubs/transactions_not_found.journal'
93
-
94
- generated_journal = HledgerForecast::Generator.generate(forecast_config, options)
95
-
96
- output = <<~JOURNAL
97
- ~ monthly from #{next_month} * Food expenses
98
- Expenses:Food £100.00 ; Food expenses
99
- Assets:Bank
100
-
101
- JOURNAL
102
-
103
- expect(generated_journal).to eq(output)
104
- end
105
- end