hledger-forecast 0.1.6 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60e8bd939c2bb57d5d1fdf01d39d4fd7bc1cd59886a189b8b0d8a8fe7a7b6640
4
- data.tar.gz: e752946a04d49b531ca303a8832d11a686d610392ce29d5a7d96c5aae2658dee
3
+ metadata.gz: 4e4232c29fd944d0483590e646c0c5e1566357ec8dfc9f942007cdea6121dcad
4
+ data.tar.gz: 2a8636024b63cdce5cfe7bbec937c123a9f78492bfb53ac873f40e9aa00d204d
5
5
  SHA512:
6
- metadata.gz: a7b44e5ceebc727be1a578a1210741fc5bbb9ec5e6d5a13feb36012bd35fb253173594ef0a35fb8448e1da697575ecdc97797473f7d5751db032a5065cce2bf8
7
- data.tar.gz: c341817f928c9120662319a6ef9c1434d22b8872e685283b162ad4043efd9a0d1366eeeed4a58196e54708689833fa24af1b6b9e92dc59b8a75a98ad77810390
6
+ metadata.gz: 0a725ff17d252a8d2427ba4e2dd2c6fa90d10e4cc9e853a6d9293eebde6aa9a594c96a9e57f387c858767b03b56e3bb3717b49bee7adc64d32a47bcd93118ebe
7
+ data.tar.gz: 118d14cde7575795a9e6632b856b4a9a60392aa19d88ce4030e555f49b580aa0cd924a61e9d6970bfe1e26306b848af756337f50eb2fc48203662714f1edd4a3
data/Gemfile CHANGED
@@ -4,5 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gem 'colorize'
6
6
  gem 'money'
7
+ gem 'terminal-table'
7
8
 
8
9
  gemspec
data/README.md CHANGED
@@ -2,18 +2,17 @@
2
2
 
3
3
  [![Tests](https://github.com/olimorris/hledger-forecast/actions/workflows/ci.yml/badge.svg)](https://github.com/olimorris/hledger-forecast/actions/workflows/ci.yml)
4
4
 
5
- > **Warning**: This is still in the early stages of development and the API is likely to change
6
-
7
- Uses a YAML file to generate monthly, quarterly, yearly and one-off transactions for better forecasting in [Hledger](https://github.com/simonmichael/hledger).
5
+ Uses a YAML file to generate periodic transactions for better forecasting in [Hledger](https://github.com/simonmichael/hledger).
8
6
 
9
7
  See the [rationale](#brain-rationale) section for why this gem may be useful to you.
10
8
 
11
9
  ## :sparkles: Features
12
10
 
13
11
  - :book: Uses a simple YAML config file to generate periodic transactions
14
- - :date: Specify start and end dates for forecasts
12
+ - :date: Generate forecasts between specified start and end dates
15
13
  - :heavy_dollar_sign: Full currency support (uses the [RubyMoney](https://github.com/RubyMoney/money) gem)
16
14
  - :computer: Simple and easy to use CLI
15
+ - :chart_with_upwards_trend: Summarize your forecasts by period and category and output to the CLI
17
16
 
18
17
  ## :package: Installation
19
18
 
@@ -23,10 +22,12 @@ Assuming you have Ruby and [Rubygems](http://rubygems.org/pages/download) instal
23
22
 
24
23
  ## :rocket: Usage
25
24
 
26
- Simply run:
25
+ Run:
27
26
 
28
27
  hledger-forecast
29
28
 
29
+ > **Note**: This assumes that a `forecast.yml` exists in the current working directory
30
+
30
31
  Running `hledger-forecast -h` shows the available options:
31
32
 
32
33
  Usage: Hledger-Forecast [options]
@@ -41,18 +42,26 @@ Running `hledger-forecast -h` shows the available options:
41
42
  -h, --help Show this message
42
43
  --version Show version
43
44
 
44
- To then include in Hledger:
45
+ Another example of a common command:
46
+
47
+ hledger-forecast -f my_forecast.yml -s 2023-05-01 -e 2024-12-31
48
+
49
+ This will generate an output file (`my_forecast.journal`) from the forecast file between the two date ranges.
50
+
51
+ ### Using with Hledger
45
52
 
46
- hledger -f transactions.journal -f forecast.journal
53
+ To use the outputs in Hledger:
54
+
55
+ hledger -f transactions.journal -f my_forecast.journal
47
56
 
48
57
  where:
49
58
 
50
59
  - `transactions.journal` might be your bank transactions (your "_actuals_")
51
- - `forecast.journal` is the generated forecast file
60
+ - `my_forecast.journal` is the generated forecast file
52
61
 
53
62
  ### A simple config file
54
63
 
55
- > **Note**: See the [example.yml](https://github.com/olimorris/hledger-forecast/blob/main/example.yml) file for all of the options
64
+ > **Note**: See the [example.yml](https://github.com/olimorris/hledger-forecast/blob/main/example.yml) file for an example of a complex config file
56
65
 
57
66
  Firstly, create a `yml` file which will contain the transactions you'd like to forecast:
58
67
 
@@ -68,6 +77,9 @@ monthly:
68
77
  - amount: 500
69
78
  category: "[Expenses:Food]"
70
79
  description: Food
80
+
81
+ settings:
82
+ currency: GBP
71
83
  ```
72
84
 
73
85
  Let's examine what's going on in this config file:
@@ -81,34 +93,83 @@ Let's examine what's going on in this config file:
81
93
 
82
94
  #### Periods
83
95
 
84
- If you'd like to add quarterly, half-yearly, yearly or one-off transactions, use the following keys:
96
+ Besides monthly recurring transactions, the app also supports the following periods:
97
+
98
+ - `quarterly` - For transactions every _3 months_ from the given start date
99
+ - `half-yearly` - For transactions every _6 months_ from the given start date
100
+ - `yearly` - Generate transactions _once a year_ from the given start date
101
+ - `once` - Generate _one-time_ transactions on a specified date
102
+ - `custom` - Generate transactions every _n days/weeks/months_
103
+
104
+ ##### Custom period
105
+
106
+ A custom period allows you to specify a given number of days, weeks or months for a transaction to repeat within. These can be included in the config file as follows:
107
+
108
+ ```yaml
109
+ # forecast.yml
110
+ custom:
111
+ - description: Fortnightly hair and beauty spend
112
+ recurrence:
113
+ period: weeks
114
+ quantity: 2
115
+ account: "[Assets:Bank]"
116
+ start: "2023-03-01"
117
+ transactions:
118
+ - amount: 80
119
+ category: "[Expenses:Personal Care]"
120
+ description: Hair and beauty
121
+ ```
122
+
123
+ Where `quantity` is an integer and `period` is one of:
124
+
125
+ - days
126
+ - weeks
127
+ - months
85
128
 
86
- - `quarterly`
87
- - `half-yearly`
88
- - `yearly`
89
- - `once`
129
+ #### Date constraints
90
130
 
91
- The structure of the config file remains exactly the same.
131
+ The core of any solid forecast is predicting the correct periods that costs will fall into. When running the app from the CLI, you can specify specific dates to generate transactions over (see the [usage](#rocket-usage) section).
92
132
 
93
- > **Note**: A quarterly transaction will repeat for every 3 months from the start date
133
+ You can further control the dates at a period/top-level as well as at a transaction level:
94
134
 
95
- #### Currency
135
+ ##### Top level
96
136
 
97
- To specify a currency:
137
+ In the example below, all transactions in the `monthly` block will be constrained by the end date:
98
138
 
99
139
  ```yaml
100
140
  # forecast.yml
101
- settings:
102
- currency: GBP
141
+ monthly:
142
+ - account: "[Assets:Bank]"
143
+ start: "2023-03-01"
144
+ end: "2025-01-01"
145
+ transactions:
146
+ # details omitted for brevity
147
+ ```
148
+
149
+ ##### Transaction level
150
+
151
+ In the example below, only the single transaction will be constrained by the end date:
152
+
153
+ ```yaml
154
+ # forecast.yml
155
+ monthly:
156
+ - account: "[Assets:Bank]"
157
+ start: "2023-03-01"
158
+ transactions:
159
+ - amount: 2000
160
+ category: "[Expenses:Mortgage]"
161
+ description: Mortgage
162
+ end: "2025-01-01"
103
163
  ```
104
164
 
105
165
  #### Additional settings
106
166
 
107
- Additional settings in the config file:
167
+ Additional settings in the config file to consider:
108
168
 
109
169
  ```yaml
110
170
  # forecast.yml
111
171
  settings:
172
+ currency: GBP # Specify the currency to use
112
173
  show_symbol: true # Show the currency symbol?
113
174
  sign_before_symbol: true # Show the negative sign before the symbol?
114
175
  thousands_separator: true # Separate thousands with a comma?
@@ -118,9 +179,9 @@ settings:
118
179
 
119
180
  As your config file grows, it can be helpful to sum up the total amounts and output them in the CLI. This can be achieved by:
120
181
 
121
- hledger-forecast -f forecast.yml --summarize
182
+ hledger-forecast -f my_forecast.yml --summarize
122
183
 
123
- where `forecast.yml` is the config file to sum up.
184
+ where `my_forecast.yml` is the config file to sum up.
124
185
 
125
186
  ## :brain: Rationale
126
187
 
@@ -128,8 +189,8 @@ Firstly, I've come to realise from reading countless blog and Reddit posts on [p
128
189
 
129
190
  My days working in financial modelling have meant that a big macro-enabled spreadsheet was my go-to tool. Growing tired with the manual approach of importing transactions, heavily manipulating them, watching Excel become increasingly slower lead me to PTA. It's been a wonderful discovery.
130
191
 
131
- One of the aspects of my previous approach to personal finance that I liked was the monthly recap of my performance and the looking ahead to the future. Am I still on track to hit my year-end savings goal? Am I still on track to hit my savings goal for 12, 24 months time? It was at this point in my shift to PTA that I hit a wall.
192
+ One of the aspects of my previous approach to personal finance that I liked was the monthly recap of my performance and the looking ahead to the future. Am I still on track to hit my year-end savings goal given my future commitments? Am I still on track to hit my savings goal in 12 and 24 months time? It was at this point in my shift to PTA that I found it difficult to answer those questions with Hledger.
132
193
 
133
- While Hledger provides support for [forecasting](https://hledger.org/1.29/hledger.html#forecasting) using periodic transactions, these are computed virtually at runtime. If I notice a big difference in my forecasted year-end balance compared to what I'm expecting, I want to investigate and start reconcilling. Computed transactions make this difficult and tiresome.
194
+ While there is support for [forecasting](https://hledger.org/1.29/hledger.html#forecasting) using periodic transactions in Hledger, these are computed virtually at runtime. If I notice a big difference in my forecasted year-end balance compared to what I'm expecting, I want to investigate and start reconcilling. Computed transactions make this nigh on impossible to unpick. Also, I get a lot of value out of running different forecast scenarios and seeing the impact. For example, _"what's my savings balance looking like in 3 years time if I get the kitchen remodelled?"_.
134
195
 
135
- With this gem, my aim was to make it easy for users to change a config file, re-run a CLI command and be able to open a text file and see the changes. No guesswork. No surprises.
196
+ With this gem, my aim was to make it easy for users to change their config file, regenerate the forecast and open a journal file and see the transactions. Or, use multiple forecast files for different scenarios and pass them in turn to Hledger to observe the impact.
data/bin/hledger-forecast CHANGED
@@ -9,4 +9,4 @@ rescue RuntimeError => e
9
9
  exit(1)
10
10
  end
11
11
 
12
- HledgerForecast::CLI.run(options)
12
+ HledgerForecast::Cli.run(options)
data/example.yml CHANGED
@@ -1,17 +1,18 @@
1
1
  monthly:
2
2
  - account: "[Assets:Bank]"
3
- date: "2023-03-01"
3
+ start: "2023-03-01"
4
4
  transactions:
5
5
  - amount: 1000.00
6
6
  category: "[Expenses:Mortgage]"
7
7
  description: Mortgage
8
+ end: "2024-01-01"
8
9
  - amount: 500.00
9
10
  category: "[Expenses:Food]"
10
11
  description: Food
11
12
 
12
13
  quarterly:
13
14
  - account: "[Assets:Bank]"
14
- date: "2023-04-01"
15
+ start: "2023-04-01"
15
16
  transactions:
16
17
  - amount: -1000.00
17
18
  category: "[Income:Bonus]"
@@ -19,7 +20,7 @@ quarterly:
19
20
 
20
21
  half-yearly:
21
22
  - account: "[Assets:Bank]"
22
- date: "2023-04-01"
23
+ start: "2023-04-01"
23
24
  transactions:
24
25
  - amount: 500
25
26
  category: "[Expenses:Holiday]"
@@ -27,7 +28,7 @@ half-yearly:
27
28
 
28
29
  yearly:
29
30
  - account: "[Assets:Bank]"
30
- date: "2023-04-01"
31
+ start: "2023-04-01"
31
32
  transactions:
32
33
  - amount: -2000.00
33
34
  category: "[Income:Bonus]"
@@ -35,12 +36,24 @@ yearly:
35
36
 
36
37
  once:
37
38
  - account: "[Assets:Bank]"
38
- date: "2024-01-01"
39
+ start: "2024-01-01"
39
40
  transactions:
40
41
  - amount: 5000.00
41
42
  category: "[Expenses:Car]"
42
43
  description: Forecast new car cost
43
44
 
45
+ custom:
46
+ - description: Fuel every 5 days
47
+ recurrence:
48
+ period: days
49
+ quantity: 5
50
+ account: "[Assets:Bank]"
51
+ start: "2023-03-01"
52
+ transactions:
53
+ - amount: 150
54
+ category: "[Expenses:Car:Fuel]"
55
+ description: Car fuel
56
+
44
57
  settings:
45
58
  currency: GBP
46
59
  show_symbol: true
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.add_dependency "highline", "~> 2.1.0"
16
16
  s.add_dependency "money", "~> 6.16.0"
17
17
  s.add_dependency "colorize", "~> 0.8.1"
18
+ s.add_dependency "terminal-table", "~> 3.0.2"
18
19
  s.add_development_dependency 'rspec', '~> 3.12'
19
20
 
20
21
  s.files = `git ls-files`.split("\n")
@@ -1,5 +1,5 @@
1
1
  module HledgerForecast
2
- class CLI
2
+ class Cli
3
3
  def self.run(args)
4
4
  end_date = args[:end_date]
5
5
  start_date = args[:start_date]
@@ -29,12 +29,43 @@ module HledgerForecast
29
29
  Money.from_cents(formatted_transaction['amount'].to_i * 100, @settings[:currency]).format(
30
30
  symbol: @settings[:show_symbol],
31
31
  sign_before_symbol: @settings[:sign_before_symbol],
32
- thousands_separator: @settings[:thousands_separator] ? ',' : nil,
32
+ thousands_separator: @settings[:thousands_separator] ? ',' : nil
33
33
  )
34
34
 
35
35
  formatted_transaction
36
36
  end
37
37
 
38
+ def self.process_custom(output, forecast_data, date)
39
+ forecast_data['custom']&.each do |forecast|
40
+ start_date = Date.parse(forecast['start'])
41
+ end_date = forecast['end'] ? Date.parse(forecast['end']) : nil
42
+ account = forecast['account']
43
+ period = forecast['recurrence']['period']
44
+ quantity = forecast['recurrence']['quantity']
45
+
46
+ next if end_date && date > end_date
47
+
48
+ date_matches = case period
49
+ when 'days'
50
+ (date - start_date).to_i % quantity == 0
51
+ when 'weeks'
52
+ (date - start_date).to_i % (quantity * 7) == 0
53
+ when 'months'
54
+ ((date.year * 12 + date.month) - (start_date.year * 12 + start_date.month)) % quantity == 0 && date.day == start_date.day
55
+ end
56
+
57
+ if date_matches
58
+ forecast['transactions'].each do |transaction|
59
+ end_date = transaction['end'] ? Date.parse(transaction['end']) : nil
60
+
61
+ next unless end_date.nil? || date <= end_date
62
+
63
+ write_transactions(output, date, account, format_transaction(transaction))
64
+ end
65
+ end
66
+ end
67
+ end
68
+
38
69
  def self.process_forecast(output_file, forecast_data, type, date)
39
70
  forecast_data[type]&.each do |forecast|
40
71
  start_date = Date.parse(forecast['start'])
@@ -86,6 +117,7 @@ module HledgerForecast
86
117
  process_forecast(output, forecast_data, 'half-yearly', date)
87
118
  process_forecast(output, forecast_data, 'yearly', date)
88
119
  process_forecast(output, forecast_data, 'once', date)
120
+ process_custom(output, forecast_data, date)
89
121
 
90
122
  date = date.next_day
91
123
  end
@@ -10,6 +10,7 @@ module HledgerForecast
10
10
  opts.on("-f", "--forecast FILE",
11
11
  "The FORECAST yaml file to generate from") do |file|
12
12
  options[:forecast_file] = file
13
+ options[:output_file] ||= file.sub(/\.yml$/, '.journal')
13
14
  end
14
15
 
15
16
  opts.on("-t", "--transaction FILE",
@@ -8,6 +8,7 @@ module HledgerForecast
8
8
  category_totals[transaction['category']] += transaction['amount']
9
9
  end
10
10
  end
11
+
11
12
  category_totals
12
13
  end
13
14
 
@@ -17,13 +18,13 @@ module HledgerForecast
17
18
 
18
19
  category_totals.each do |category, amount|
19
20
  formatted_amount = generator.format_transaction({ 'amount' => amount })['amount']
20
- formatted_amount = amount.to_i < 0 ? formatted_amount.red : formatted_amount.green
21
+ formatted_amount = amount.to_i < 0 ? formatted_amount.green : formatted_amount.red
21
22
  puts " #{category.ljust(40)}#{formatted_amount}"
22
23
  period_total += amount
23
24
  end
24
25
 
25
26
  formatted_period_total = generator.format_transaction({ 'amount' => period_total })['amount']
26
- formatted_period_total = period_total.to_i < 0 ? formatted_period_total.red : formatted_period_total.green
27
+ formatted_period_total = period_total.to_i < 0 ? formatted_period_total.green : formatted_period_total.red
27
28
  puts " TOTAL".ljust(42) + formatted_period_total
28
29
  end
29
30
 
@@ -32,7 +33,7 @@ module HledgerForecast
32
33
  total = {}
33
34
  grand_total = 0
34
35
 
35
- periods.each do |period|
36
+ (periods + ['custom']).each do |period|
36
37
  total[period] = sum_transactions(forecast_data, period)
37
38
  grand_total += total[period]
38
39
  end
@@ -41,11 +42,33 @@ module HledgerForecast
41
42
  total
42
43
  end
43
44
 
45
+ def self.sum_custom_transactions(forecast_data)
46
+ category_totals = Hash.new(0)
47
+ custom_periods = []
48
+
49
+ forecast_data['custom']&.each do |entry|
50
+ period_data = {}
51
+ period_data[:quantity] = entry['recurrence']['quantity']
52
+ period_data[:period] = entry['recurrence']['period']
53
+ period_data[:description] = entry['transactions'].first['description']
54
+ period_data[:category] = entry['transactions'].first['category']
55
+ period_data[:amount] = entry['transactions'].first['amount']
56
+
57
+ entry['transactions'].each do |transaction|
58
+ category_totals[transaction['category']] += transaction['amount']
59
+ end
60
+
61
+ custom_periods << period_data
62
+ end
63
+
64
+ { totals: category_totals, periods: custom_periods }
65
+ end
66
+
44
67
  def self.generate(forecast)
45
68
  forecast_data = YAML.safe_load(forecast)
46
69
 
47
70
  category_totals_by_period = {}
48
- %w[monthly quarterly half-yearly yearly once].each do |period|
71
+ %w[monthly quarterly half-yearly yearly once custom].each do |period|
49
72
  category_totals_by_period[period] = sum_transactions(forecast_data, period)
50
73
  end
51
74
 
@@ -54,15 +77,56 @@ module HledgerForecast
54
77
  generator = HledgerForecast::Generator
55
78
  generator.configure_settings(forecast_data)
56
79
 
57
- puts
80
+ table = Terminal::Table.new
81
+
82
+ table.add_row([{ value: 'FORECAST SUMMARY', colspan: 3, alignment: :center }])
83
+ table.add_separator
84
+
85
+ first_period = true
58
86
  category_totals_by_period.each do |period, category_totals|
59
- print_category_totals(period, category_totals, generator)
60
- puts
87
+ non_zero_totals = category_totals.select { |_, amount| amount != 0 }
88
+ next if non_zero_totals.empty?
89
+
90
+ table.add_separator unless first_period
91
+ table.add_row([{ value: period.capitalize, colspan: 3, alignment: :center }])
92
+
93
+ period_total = 0
94
+
95
+ if period == 'custom'
96
+ custom_periods_data = sum_custom_transactions(forecast_data)
97
+ custom_periods_data[:periods].each do |custom_period|
98
+ formatted_amount = generator.format_transaction({ 'amount' => custom_period[:amount] })['amount']
99
+ formatted_amount = custom_period[:amount].to_i < 0 ? formatted_amount.green : formatted_amount.red
100
+ table.add_row [{ value: custom_period[:category], alignment: :left },
101
+ { value: "every #{custom_period[:quantity]} #{custom_period[:period]}", alignment: :right }, { value: formatted_amount, alignment: :right }]
102
+ period_total += custom_period[:amount]
103
+ end
104
+ else
105
+ non_zero_totals.each do |category, amount|
106
+ formatted_amount = generator.format_transaction({ 'amount' => amount })['amount']
107
+ formatted_amount = amount.to_i < 0 ? formatted_amount.green : formatted_amount.red
108
+
109
+ table.add_row [{ value: category, colspan: 2, alignment: :left },
110
+ { value: formatted_amount, alignment: :right }]
111
+ period_total += amount
112
+ end
113
+ end
114
+
115
+ formatted_period_total = generator.format_transaction({ 'amount' => period_total })['amount']
116
+ formatted_period_total = period_total.to_i < 0 ? formatted_period_total.green : formatted_period_total.red
117
+ table.add_row [{ value: "#{period.capitalize} TOTAL", colspan: 2, alignment: :left },
118
+ { value: formatted_period_total, alignment: :right }]
119
+
120
+ first_period = false
61
121
  end
62
122
 
123
+ table.add_separator
63
124
  formatted_grand_total = generator.format_transaction({ 'amount' => grand_total })['amount']
64
- formatted_grand_total = grand_total.to_i < 0 ? formatted_grand_total.red : formatted_grand_total.green
65
- puts "TOTAL:".ljust(42) + formatted_grand_total
125
+ formatted_grand_total = grand_total.to_i < 0 ? formatted_grand_total.green : formatted_grand_total.red
126
+ table.add_row [{ value: 'TOTAL', colspan: 2, alignment: :left },
127
+ { value: formatted_grand_total, alignment: :right }]
128
+
129
+ puts table
66
130
  end
67
131
  end
68
132
  end
@@ -1,3 +1,3 @@
1
1
  module HledgerForecast
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -5,6 +5,7 @@ require 'date'
5
5
  require 'highline'
6
6
  require 'money'
7
7
  require 'optparse'
8
+ require 'terminal-table'
8
9
  require 'yaml'
9
10
 
10
11
  Money.locale_backend = nil
@@ -14,4 +15,4 @@ require_relative 'hledger_forecast/version'
14
15
  require_relative 'hledger_forecast/options'
15
16
  require_relative 'hledger_forecast/generator'
16
17
  require_relative 'hledger_forecast/summarize'
17
- require_relative 'hledger_forecast/CLI'
18
+ require_relative 'hledger_forecast/cli'
@@ -0,0 +1,47 @@
1
+ require_relative '../lib/hledger_forecast'
2
+
3
+ RSpec.describe 'generate' do
4
+ it 'generates a forecast with correct CUSTOM DAILY transactions' do
5
+ transactions = File.read('spec/stubs/transactions.journal')
6
+ forecast = File.read('spec/stubs/custom/forecast_custom_days.yml')
7
+
8
+ generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
9
+ '2023-03-10')
10
+
11
+ expected_output = File.read('spec/stubs/custom/output_custom_days.journal')
12
+ expect(generated_journal).to eq(expected_output)
13
+ end
14
+
15
+ it 'generates a forecast with correct CUSTOM WEEKlY transactions' do
16
+ transactions = File.read('spec/stubs/transactions.journal')
17
+ forecast = File.read('spec/stubs/custom/forecast_custom_weeks.yml')
18
+
19
+ generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
20
+ '2023-04-30')
21
+
22
+ expected_output = File.read('spec/stubs/custom/output_custom_weeks.journal')
23
+ expect(generated_journal).to eq(expected_output)
24
+ end
25
+
26
+ it 'generates a forecast with MULTIPLE correct CUSTOM WEEKlY transactions' do
27
+ transactions = File.read('spec/stubs/transactions.journal')
28
+ forecast = File.read('spec/stubs/custom/forecast_custom_weeks_twice.yml')
29
+
30
+ generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
31
+ '2023-03-30')
32
+
33
+ expected_output = File.read('spec/stubs/custom/output_custom_weeks_twice.journal')
34
+ expect(generated_journal).to eq(expected_output)
35
+ end
36
+
37
+ it 'generates a forecast with correct CUSTOM MONTHLY transactions' do
38
+ transactions = File.read('spec/stubs/transactions.journal')
39
+ forecast = File.read('spec/stubs/custom/forecast_custom_months.yml')
40
+
41
+ generated_journal = HledgerForecast::Generator.create_journal_entries(transactions, forecast, '2023-03-01',
42
+ '2024-02-28')
43
+
44
+ expected_output = File.read('spec/stubs/custom/output_custom_months.journal')
45
+ expect(generated_journal).to eq(expected_output)
46
+ end
47
+ end
@@ -0,0 +1,14 @@
1
+ custom:
2
+ - description: Repeat every 3 days
3
+ recurrence:
4
+ period: days
5
+ quantity: 3
6
+ account: "[Assets:Bank]"
7
+ start: "2023-03-01"
8
+ transactions:
9
+ - amount: 80
10
+ category: "[Expenses:Personal Care]"
11
+ description: Hair and beauty
12
+
13
+ settings:
14
+ currency: GBP
@@ -0,0 +1,14 @@
1
+ custom:
2
+ - description: Repeat every 5 months
3
+ recurrence:
4
+ period: months
5
+ quantity: 5
6
+ account: "[Assets:Bank]"
7
+ start: "2023-03-01"
8
+ transactions:
9
+ - amount: 100
10
+ category: "[Expenses:Personal Care]"
11
+ description: Hair and beauty
12
+
13
+ settings:
14
+ currency: GBP
@@ -0,0 +1,14 @@
1
+ custom:
2
+ - description: Repeat every 2 weeks
3
+ recurrence:
4
+ period: weeks
5
+ quantity: 2
6
+ account: "[Assets:Bank]"
7
+ start: "2023-03-01"
8
+ transactions:
9
+ - amount: 75
10
+ category: "[Expenses:Personal Care]"
11
+ description: Hair and beauty
12
+
13
+ settings:
14
+ currency: GBP
@@ -0,0 +1,24 @@
1
+ custom:
2
+ - description: Beauty expenses every 2 weeks
3
+ recurrence:
4
+ period: weeks
5
+ quantity: 2
6
+ account: "[Assets:Bank]"
7
+ start: "2023-03-01"
8
+ transactions:
9
+ - amount: 75
10
+ category: "[Expenses:Personal Care]"
11
+ description: Hair and beauty
12
+ - description: Car fuel every 5 days
13
+ recurrence:
14
+ period: days
15
+ quantity: 5
16
+ account: "[Assets:Bank]"
17
+ start: "2023-03-01"
18
+ transactions:
19
+ - amount: 150
20
+ category: "[Expenses:Car:Fuel]"
21
+ description: Car Fuel
22
+
23
+ settings:
24
+ currency: GBP
@@ -0,0 +1,24 @@
1
+ 2023-02-01 * Opening balance
2
+ Assets:Bank £1,000.00
3
+ Equity:Opening balance
4
+
5
+ 2023-02-05 * Mortgage payment
6
+ Expenses:Mortgage £1,500.00
7
+ Assets:Bank
8
+
9
+ 2023-03-01 * Hair and beauty
10
+ [Expenses:Personal Care] £80.00
11
+ [Assets:Bank]
12
+
13
+ 2023-03-04 * Hair and beauty
14
+ [Expenses:Personal Care] £80.00
15
+ [Assets:Bank]
16
+
17
+ 2023-03-07 * Hair and beauty
18
+ [Expenses:Personal Care] £80.00
19
+ [Assets:Bank]
20
+
21
+ 2023-03-10 * Hair and beauty
22
+ [Expenses:Personal Care] £80.00
23
+ [Assets:Bank]
24
+
@@ -0,0 +1,20 @@
1
+ 2023-02-01 * Opening balance
2
+ Assets:Bank £1,000.00
3
+ Equity:Opening balance
4
+
5
+ 2023-02-05 * Mortgage payment
6
+ Expenses:Mortgage £1,500.00
7
+ Assets:Bank
8
+
9
+ 2023-03-01 * Hair and beauty
10
+ [Expenses:Personal Care] £100.00
11
+ [Assets:Bank]
12
+
13
+ 2023-08-01 * Hair and beauty
14
+ [Expenses:Personal Care] £100.00
15
+ [Assets:Bank]
16
+
17
+ 2024-01-01 * Hair and beauty
18
+ [Expenses:Personal Care] £100.00
19
+ [Assets:Bank]
20
+
@@ -0,0 +1,28 @@
1
+ 2023-02-01 * Opening balance
2
+ Assets:Bank £1,000.00
3
+ Equity:Opening balance
4
+
5
+ 2023-02-05 * Mortgage payment
6
+ Expenses:Mortgage £1,500.00
7
+ Assets:Bank
8
+
9
+ 2023-03-01 * Hair and beauty
10
+ [Expenses:Personal Care] £75.00
11
+ [Assets:Bank]
12
+
13
+ 2023-03-15 * Hair and beauty
14
+ [Expenses:Personal Care] £75.00
15
+ [Assets:Bank]
16
+
17
+ 2023-03-29 * Hair and beauty
18
+ [Expenses:Personal Care] £75.00
19
+ [Assets:Bank]
20
+
21
+ 2023-04-12 * Hair and beauty
22
+ [Expenses:Personal Care] £75.00
23
+ [Assets:Bank]
24
+
25
+ 2023-04-26 * Hair and beauty
26
+ [Expenses:Personal Care] £75.00
27
+ [Assets:Bank]
28
+
@@ -0,0 +1,44 @@
1
+ 2023-02-01 * Opening balance
2
+ Assets:Bank £1,000.00
3
+ Equity:Opening balance
4
+
5
+ 2023-02-05 * Mortgage payment
6
+ Expenses:Mortgage £1,500.00
7
+ Assets:Bank
8
+
9
+ 2023-03-01 * Hair and beauty
10
+ [Expenses:Personal Care] £75.00
11
+ [Assets:Bank]
12
+
13
+ 2023-03-01 * Car Fuel
14
+ [Expenses:Car:Fuel] £150.00
15
+ [Assets:Bank]
16
+
17
+ 2023-03-06 * Car Fuel
18
+ [Expenses:Car:Fuel] £150.00
19
+ [Assets:Bank]
20
+
21
+ 2023-03-11 * Car Fuel
22
+ [Expenses:Car:Fuel] £150.00
23
+ [Assets:Bank]
24
+
25
+ 2023-03-15 * Hair and beauty
26
+ [Expenses:Personal Care] £75.00
27
+ [Assets:Bank]
28
+
29
+ 2023-03-16 * Car Fuel
30
+ [Expenses:Car:Fuel] £150.00
31
+ [Assets:Bank]
32
+
33
+ 2023-03-21 * Car Fuel
34
+ [Expenses:Car:Fuel] £150.00
35
+ [Assets:Bank]
36
+
37
+ 2023-03-26 * Car Fuel
38
+ [Expenses:Car:Fuel] £150.00
39
+ [Assets:Bank]
40
+
41
+ 2023-03-29 * Hair and beauty
42
+ [Expenses:Personal Care] £75.00
43
+ [Assets:Bank]
44
+
@@ -8,6 +8,12 @@ monthly:
8
8
  - amount: 100
9
9
  category: "[Expenses:Food]"
10
10
  description: Food
11
+ - account: "[Assets:Savings]"
12
+ start: "2023-03-01"
13
+ transactions:
14
+ - amount: -1000
15
+ category: "[Assets:Bank]"
16
+ description: Savings
11
17
 
12
18
  settings:
13
19
  currency: GBP
@@ -14,6 +14,10 @@
14
14
  [Expenses:Food] £100.00
15
15
  [Assets:Bank]
16
16
 
17
+ 2023-03-01 * Savings
18
+ [Assets:Bank] -£1,000.00
19
+ [Assets:Savings]
20
+
17
21
  2023-04-01 * Mortgage
18
22
  [Expenses:Mortgage] £2,000.00
19
23
  [Assets:Bank]
@@ -22,6 +26,10 @@
22
26
  [Expenses:Food] £100.00
23
27
  [Assets:Bank]
24
28
 
29
+ 2023-04-01 * Savings
30
+ [Assets:Bank] -£1,000.00
31
+ [Assets:Savings]
32
+
25
33
  2023-05-01 * Mortgage
26
34
  [Expenses:Mortgage] £2,000.00
27
35
  [Assets:Bank]
@@ -30,3 +38,7 @@
30
38
  [Expenses:Food] £100.00
31
39
  [Assets:Bank]
32
40
 
41
+ 2023-05-01 * Savings
42
+ [Assets:Bank] -£1,000.00
43
+ [Assets:Savings]
44
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hledger-forecast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oli Morris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-15 00:00:00.000000000 Z
11
+ date: 2023-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 0.8.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: terminal-table
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.2
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rspec
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -90,10 +104,19 @@ files:
90
104
  - lib/hledger_forecast/summarize.rb
91
105
  - lib/hledger_forecast/version.rb
92
106
  - spec/command_spec.rb
107
+ - spec/custom_spec.rb
93
108
  - spec/half-yearly_spec.rb
94
109
  - spec/monthly_spec.rb
95
110
  - spec/once_spec.rb
96
111
  - spec/quarterly_spec.rb
112
+ - spec/stubs/custom/forecast_custom_days.yml
113
+ - spec/stubs/custom/forecast_custom_months.yml
114
+ - spec/stubs/custom/forecast_custom_weeks.yml
115
+ - spec/stubs/custom/forecast_custom_weeks_twice.yml
116
+ - spec/stubs/custom/output_custom_days.journal
117
+ - spec/stubs/custom/output_custom_months.journal
118
+ - spec/stubs/custom/output_custom_weeks.journal
119
+ - spec/stubs/custom/output_custom_weeks_twice.journal
97
120
  - spec/stubs/half-yearly/forecast_half-yearly.yml
98
121
  - spec/stubs/half-yearly/output_half-yearly.journal
99
122
  - spec/stubs/monthly/forecast_monthly.yml
@@ -131,16 +154,25 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
154
  - !ruby/object:Gem::Version
132
155
  version: '0'
133
156
  requirements: []
134
- rubygems_version: 3.4.10
157
+ rubygems_version: 3.4.12
135
158
  signing_key:
136
159
  specification_version: 4
137
160
  summary: Utility to generate forecasts in Hledger
138
161
  test_files:
139
162
  - spec/command_spec.rb
163
+ - spec/custom_spec.rb
140
164
  - spec/half-yearly_spec.rb
141
165
  - spec/monthly_spec.rb
142
166
  - spec/once_spec.rb
143
167
  - spec/quarterly_spec.rb
168
+ - spec/stubs/custom/forecast_custom_days.yml
169
+ - spec/stubs/custom/forecast_custom_months.yml
170
+ - spec/stubs/custom/forecast_custom_weeks.yml
171
+ - spec/stubs/custom/forecast_custom_weeks_twice.yml
172
+ - spec/stubs/custom/output_custom_days.journal
173
+ - spec/stubs/custom/output_custom_months.journal
174
+ - spec/stubs/custom/output_custom_weeks.journal
175
+ - spec/stubs/custom/output_custom_weeks_twice.journal
144
176
  - spec/stubs/half-yearly/forecast_half-yearly.yml
145
177
  - spec/stubs/half-yearly/output_half-yearly.journal
146
178
  - spec/stubs/monthly/forecast_monthly.yml