rvgp 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +23 -0
- data/LICENSE +504 -0
- data/README.md +223 -0
- data/Rakefile +32 -0
- data/bin/rvgp +8 -0
- data/lib/rvgp/application/config.rb +159 -0
- data/lib/rvgp/application/descendant_registry.rb +122 -0
- data/lib/rvgp/application/status_output.rb +139 -0
- data/lib/rvgp/application.rb +170 -0
- data/lib/rvgp/base/command.rb +457 -0
- data/lib/rvgp/base/grid.rb +531 -0
- data/lib/rvgp/base/reader.rb +29 -0
- data/lib/rvgp/base/reconciler.rb +434 -0
- data/lib/rvgp/base/validation.rb +261 -0
- data/lib/rvgp/commands/cashflow.rb +160 -0
- data/lib/rvgp/commands/grid.rb +70 -0
- data/lib/rvgp/commands/ireconcile.rb +95 -0
- data/lib/rvgp/commands/new_project.rb +296 -0
- data/lib/rvgp/commands/plot.rb +41 -0
- data/lib/rvgp/commands/publish_gsheets.rb +83 -0
- data/lib/rvgp/commands/reconcile.rb +58 -0
- data/lib/rvgp/commands/rotate_year.rb +202 -0
- data/lib/rvgp/commands/validate_journal.rb +59 -0
- data/lib/rvgp/commands/validate_system.rb +44 -0
- data/lib/rvgp/commands.rb +160 -0
- data/lib/rvgp/dashboard.rb +252 -0
- data/lib/rvgp/fakers/fake_feed.rb +245 -0
- data/lib/rvgp/fakers/fake_journal.rb +57 -0
- data/lib/rvgp/fakers/fake_reconciler.rb +88 -0
- data/lib/rvgp/fakers/faker_helpers.rb +25 -0
- data/lib/rvgp/gem.rb +80 -0
- data/lib/rvgp/journal/commodity.rb +453 -0
- data/lib/rvgp/journal/complex_commodity.rb +214 -0
- data/lib/rvgp/journal/currency.rb +101 -0
- data/lib/rvgp/journal/journal.rb +141 -0
- data/lib/rvgp/journal/posting.rb +156 -0
- data/lib/rvgp/journal/pricer.rb +267 -0
- data/lib/rvgp/journal.rb +24 -0
- data/lib/rvgp/plot/gnuplot.rb +478 -0
- data/lib/rvgp/plot/google-drive/output_csv.rb +44 -0
- data/lib/rvgp/plot/google-drive/output_google_sheets.rb +434 -0
- data/lib/rvgp/plot/google-drive/sheet.rb +67 -0
- data/lib/rvgp/plot.rb +293 -0
- data/lib/rvgp/pta/hledger.rb +237 -0
- data/lib/rvgp/pta/ledger.rb +308 -0
- data/lib/rvgp/pta.rb +311 -0
- data/lib/rvgp/reconcilers/csv_reconciler.rb +424 -0
- data/lib/rvgp/reconcilers/journal_reconciler.rb +41 -0
- data/lib/rvgp/reconcilers/shorthand/finance_gem_hacks.rb +48 -0
- data/lib/rvgp/reconcilers/shorthand/international_atm.rb +152 -0
- data/lib/rvgp/reconcilers/shorthand/investment.rb +144 -0
- data/lib/rvgp/reconcilers/shorthand/mortgage.rb +195 -0
- data/lib/rvgp/utilities/grid_query.rb +190 -0
- data/lib/rvgp/utilities/yaml.rb +131 -0
- data/lib/rvgp/utilities.rb +44 -0
- data/lib/rvgp/validations/balance_validation.rb +68 -0
- data/lib/rvgp/validations/duplicate_tags_validation.rb +48 -0
- data/lib/rvgp/validations/uncategorized_validation.rb +15 -0
- data/lib/rvgp.rb +66 -0
- data/resources/README.MD/2022-cashflow-google.png +0 -0
- data/resources/README.MD/2022-cashflow.png +0 -0
- data/resources/README.MD/all-wealth-growth-google.png +0 -0
- data/resources/README.MD/all-wealth-growth.png +0 -0
- data/resources/gnuplot/default.yml +80 -0
- data/resources/i18n/en.yml +192 -0
- data/resources/iso-4217-currencies.json +171 -0
- data/resources/skel/Rakefile +5 -0
- data/resources/skel/app/grids/cashflow_grid.rb +27 -0
- data/resources/skel/app/grids/monthly_income_and_expenses_grid.rb +25 -0
- data/resources/skel/app/grids/wealth_growth_grid.rb +35 -0
- data/resources/skel/app/plots/cashflow.yml +33 -0
- data/resources/skel/app/plots/monthly-income-and-expenses.yml +17 -0
- data/resources/skel/app/plots/wealth-growth.yml +20 -0
- data/resources/skel/config/csv-format-acme-checking.yml +9 -0
- data/resources/skel/config/google-secrets.yml +5 -0
- data/resources/skel/config/rvgp.yml +0 -0
- data/resources/skel/journals/prices.db +0 -0
- data/rvgp.gemspec +6 -0
- data/test/assets/ledger_total_monthly_liabilities_with_empty.xml +383 -0
- data/test/assets/ledger_total_monthly_liabilities_with_empty2.xml +428 -0
- data/test/test_command_base.rb +61 -0
- data/test/test_commodity.rb +270 -0
- data/test/test_csv_reconciler.rb +60 -0
- data/test/test_currency.rb +24 -0
- data/test/test_fake_feed.rb +228 -0
- data/test/test_fake_journal.rb +98 -0
- data/test/test_fake_reconciler.rb +60 -0
- data/test/test_journal_parse.rb +545 -0
- data/test/test_ledger.rb +102 -0
- data/test/test_plot.rb +133 -0
- data/test/test_posting.rb +50 -0
- data/test/test_pricer.rb +139 -0
- data/test/test_pta_adapter.rb +575 -0
- data/test/test_utilities.rb +45 -0
- metadata +268 -0
@@ -0,0 +1,545 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
|
6
|
+
require_relative '../lib/rvgp'
|
7
|
+
|
8
|
+
# Tests for RVGP::Journal
|
9
|
+
class TestJournalParse < Minitest::Test
|
10
|
+
SAMPLE_TAG_FORMATS_JOURNAL = <<~JOURNAL
|
11
|
+
2021-01-20 Lawn Mowing
|
12
|
+
Personal:Expenses:Home:LawnMaintenance $ 100.00
|
13
|
+
; intention: HomeMaintenance
|
14
|
+
; property: 123GreenSt
|
15
|
+
Personal:Assets:Bankname:Checking
|
16
|
+
|
17
|
+
2021-01-20 Fancy Restaurant
|
18
|
+
;:SmallBusiness:
|
19
|
+
Personal:Expenses:Dining $ 50.00
|
20
|
+
; wine: red
|
21
|
+
; :Pasta:Italian:Vegetarian:
|
22
|
+
Personal:Assets:Bankname:Checking
|
23
|
+
|
24
|
+
; These next four were pulled from: https://hledger.org/tags-tutorial.html
|
25
|
+
|
26
|
+
2016/09/25 ACME Costume ; Halloween:
|
27
|
+
Expenses:Entertainment $45.99
|
28
|
+
Liabilities:CreditCard
|
29
|
+
|
30
|
+
2020/05/20 AcmeWrappings.com
|
31
|
+
Expenses:Clothing $58.99 ; fabric:wool, width:20, color:ancient white
|
32
|
+
Liabilities:MonsterCard
|
33
|
+
|
34
|
+
2016/10/31 Grocery Store
|
35
|
+
Expenses $3.52 ; on sale today item:candy
|
36
|
+
Liabilities:CreditCard
|
37
|
+
|
38
|
+
2016/10/31 Grocery Store
|
39
|
+
Expenses $3.52 ; item:candy, on sale today
|
40
|
+
Liabilities:CreditCard
|
41
|
+
JOURNAL
|
42
|
+
|
43
|
+
SAMPLE_CURRENCY_DECLARATION_JOURNAL = <<~JOURNAL
|
44
|
+
2021-06-16 1100 HNL
|
45
|
+
Personal:Assets:Cash 1100 HNL @@ $ 45.83
|
46
|
+
; intention: Ignored
|
47
|
+
Personal:Expenses:Banking:Fees:IrlExchangeSlippage $ 4.17
|
48
|
+
; intention: Personal
|
49
|
+
Personal:Assets:Cash
|
50
|
+
|
51
|
+
; These are from: https://www.ledger-cli.org/3.0/doc/ledger3.html
|
52
|
+
2012-04-10 My Broker
|
53
|
+
Assets:Brokerage 10 AAPL {$50.00}
|
54
|
+
Assets:Brokerage:Cash $-500.00
|
55
|
+
|
56
|
+
2012-04-10 My Broker
|
57
|
+
Assets:Brokerage:Cash $750.00
|
58
|
+
Assets:Brokerage -10 AAPL {{$500.00}} @@ $750.00
|
59
|
+
Income:Capital Gains $-250.00
|
60
|
+
|
61
|
+
2012-04-10 My Broker
|
62
|
+
Assets:Brokerage:Cash $375.00
|
63
|
+
Assets:Brokerage -5 AAPL {$50.00} [2012-04-10] @@ $375.00
|
64
|
+
Income:Capital Gains $-125.00
|
65
|
+
|
66
|
+
2012-04-10 My Broker
|
67
|
+
Assets:Brokerage:Cash $375.00
|
68
|
+
Assets:Brokerage -5 AAPL {$50.00} [2012-04-10] (Oh my!) @@ $375.00
|
69
|
+
Income:Capital Gains $-125.00
|
70
|
+
|
71
|
+
2012-04-10 My Broker
|
72
|
+
Assets:Brokerage:Cash $375.00
|
73
|
+
Assets:Brokerage -5 AAPL {$50.00} ((ten_dollars)) @@ $375.00
|
74
|
+
Income:Capital Gains $-125.00
|
75
|
+
|
76
|
+
2012-04-10 My Broker
|
77
|
+
A:B:Cash $375.00
|
78
|
+
A:B -5 AAPL {$50.00} ((s, d, t -> market(0, date, t))) @@ $375.00
|
79
|
+
Income:Capital Gains $-125.00
|
80
|
+
|
81
|
+
2010/05/31 Farmer's Market
|
82
|
+
Assets:My Larder 100 apples @ $0.200000
|
83
|
+
Assets:My Larder 100 pineapples @ $0.33
|
84
|
+
Assets:My Larder 100 "crab apples" @ $0.04
|
85
|
+
Assets:Checking
|
86
|
+
|
87
|
+
2012-04-10 My Broker
|
88
|
+
Assets:Brokerage 10 AAPL @ =$50.00
|
89
|
+
Assets:Brokerage:Cash $-500.00
|
90
|
+
|
91
|
+
2012-04-10 My Broker
|
92
|
+
Assets:Brokerage 10 AAPL {=$50.00}
|
93
|
+
Assets:Brokerage:Cash $-500.00
|
94
|
+
|
95
|
+
2012-03-10 My Broker
|
96
|
+
Assets:Brokerage (5 AAPL * 2) @ ($500.00 / 10)
|
97
|
+
Assets:Brokerage:Cash
|
98
|
+
JOURNAL
|
99
|
+
|
100
|
+
def test_journal_tag_parsing
|
101
|
+
journal = RVGP::Journal.parse SAMPLE_TAG_FORMATS_JOURNAL
|
102
|
+
|
103
|
+
assert_equal 6, journal.postings.length
|
104
|
+
|
105
|
+
# Posting 0
|
106
|
+
posting0 = journal.postings[0]
|
107
|
+
assert_equal Date.new(2021, 1, 20), posting0.date
|
108
|
+
assert_equal 'Lawn Mowing', posting0.description
|
109
|
+
assert_equal 0, posting0.tags.length
|
110
|
+
assert_equal 2, posting0.transfers.length
|
111
|
+
|
112
|
+
# Posting 0, Transfer 0
|
113
|
+
assert_equal 'Personal:Expenses:Home:LawnMaintenance', posting0.transfers[0].account
|
114
|
+
assert_equal '$ 100.00', posting0.transfers[0].commodity.to_s
|
115
|
+
assert_equal 2, posting0.transfers[0].tags.length
|
116
|
+
assert_equal ['intention: HomeMaintenance', 'property: 123GreenSt'], posting0.transfers[0].tags.collect(&:to_s)
|
117
|
+
|
118
|
+
# Posting 0, Transfer 1
|
119
|
+
assert_equal 'Personal:Assets:Bankname:Checking', posting0.transfers[1].account
|
120
|
+
assert_nil posting0.transfers[1].commodity
|
121
|
+
|
122
|
+
# Posting 1
|
123
|
+
posting1 = journal.postings[1]
|
124
|
+
|
125
|
+
assert_equal Date.new(2021, 1, 20), posting1.date
|
126
|
+
assert_equal 'Fancy Restaurant', posting1.description
|
127
|
+
assert_equal ['SmallBusiness'], posting1.tags.collect(&:to_s)
|
128
|
+
assert_equal 2, posting1.transfers.length
|
129
|
+
|
130
|
+
# Posting 1, Transfer 0
|
131
|
+
assert_equal 'Personal:Expenses:Dining', posting1.transfers[0].account
|
132
|
+
assert_equal '$ 50.00', posting1.transfers[0].commodity.to_s
|
133
|
+
assert_equal ['wine: red', 'Pasta', 'Italian', 'Vegetarian'], posting1.transfers[0].tags.collect(&:to_s)
|
134
|
+
|
135
|
+
# Posting 1, Transfer 1
|
136
|
+
assert_equal 'Personal:Assets:Bankname:Checking', posting1.transfers[1].account
|
137
|
+
assert_nil posting1.transfers[1].commodity
|
138
|
+
|
139
|
+
# Posting 2
|
140
|
+
posting2 = journal.postings[2]
|
141
|
+
|
142
|
+
assert_equal Date.new(2016, 9, 25), posting2.date
|
143
|
+
assert_equal 'ACME Costume', posting2.description
|
144
|
+
assert_equal ['Halloween'], posting2.tags.collect(&:to_s)
|
145
|
+
assert_equal 2, posting2.transfers.length
|
146
|
+
|
147
|
+
# Posting 2, Transfer 0
|
148
|
+
assert_equal 'Expenses:Entertainment', posting2.transfers[0].account
|
149
|
+
assert_equal '$ 45.99', posting2.transfers[0].commodity.to_s
|
150
|
+
assert_equal 0, posting2.transfers[0].tags.length
|
151
|
+
|
152
|
+
# Posting 2, Transfer 1
|
153
|
+
assert_equal 'Liabilities:CreditCard', posting2.transfers[1].account
|
154
|
+
assert_nil posting2.transfers[1].commodity
|
155
|
+
|
156
|
+
# Posting 3
|
157
|
+
posting3 = journal.postings[3]
|
158
|
+
|
159
|
+
assert_equal Date.new(2020, 5, 20), posting3.date
|
160
|
+
assert_equal 'AcmeWrappings.com', posting3.description
|
161
|
+
assert_equal 0, posting3.tags.length
|
162
|
+
assert_equal 2, posting3.transfers.length
|
163
|
+
|
164
|
+
# Posting 3, Transfer 0
|
165
|
+
assert_equal 'Expenses:Clothing', posting3.transfers[0].account
|
166
|
+
assert_equal '$ 58.99', posting3.transfers[0].commodity.to_s
|
167
|
+
assert_equal ['fabric: wool', 'width: 20', 'color: ancient white'], posting3.transfers[0].tags.collect(&:to_s)
|
168
|
+
|
169
|
+
# Posting 3, Transfer 1
|
170
|
+
assert_equal 'Liabilities:MonsterCard', posting3.transfers[1].account
|
171
|
+
assert_nil posting3.transfers[1].commodity
|
172
|
+
|
173
|
+
# Posting 4
|
174
|
+
posting4 = journal.postings[4]
|
175
|
+
|
176
|
+
assert_equal Date.new(2016, 10, 31), posting4.date
|
177
|
+
assert_equal 'Grocery Store', posting4.description
|
178
|
+
assert_equal 0, posting4.tags.length
|
179
|
+
assert_equal 2, posting4.transfers.length
|
180
|
+
|
181
|
+
# Posting 4, Transfer 0
|
182
|
+
assert_equal 'Expenses', posting4.transfers[0].account
|
183
|
+
assert_equal '$ 3.52', posting4.transfers[0].commodity.to_s
|
184
|
+
assert_equal ['item: candy'], posting4.transfers[0].tags.collect(&:to_s)
|
185
|
+
|
186
|
+
# Posting 4, Transfer 1
|
187
|
+
assert_equal 'Liabilities:CreditCard', posting4.transfers[1].account
|
188
|
+
assert_nil posting4.transfers[1].commodity
|
189
|
+
|
190
|
+
# Posting 5
|
191
|
+
posting5 = journal.postings[5]
|
192
|
+
|
193
|
+
assert_equal Date.new(2016, 10, 31), posting5.date
|
194
|
+
assert_equal 'Grocery Store', posting5.description
|
195
|
+
assert_equal 0, posting5.tags.length
|
196
|
+
assert_equal 2, posting5.transfers.length
|
197
|
+
|
198
|
+
# Posting 5, Transfer 0
|
199
|
+
assert_equal 'Expenses', posting5.transfers[0].account
|
200
|
+
assert_equal '$ 3.52', posting5.transfers[0].commodity.to_s
|
201
|
+
assert_equal ['item: candy'], posting5.transfers[0].tags.collect(&:to_s)
|
202
|
+
|
203
|
+
# Posting 5, Transfer 1
|
204
|
+
assert_equal 'Liabilities:CreditCard', posting5.transfers[1].account
|
205
|
+
assert_nil posting5.transfers[1].commodity
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_currency_purchase_parsing
|
209
|
+
journal = RVGP::Journal.parse SAMPLE_CURRENCY_DECLARATION_JOURNAL
|
210
|
+
|
211
|
+
assert_equal 11, journal.postings.length
|
212
|
+
|
213
|
+
# Posting 0
|
214
|
+
posting0 = journal.postings[0]
|
215
|
+
assert_equal Date.new(2021, 6, 16), posting0.date
|
216
|
+
assert_equal '1100 HNL', posting0.description
|
217
|
+
assert_equal 0, posting0.tags.length
|
218
|
+
assert_equal 3, posting0.transfers.length
|
219
|
+
|
220
|
+
# Posting 0, Transfer 0
|
221
|
+
assert_equal 'Personal:Assets:Cash', posting0.transfers[0].account
|
222
|
+
assert_nil posting0.transfers[0].commodity
|
223
|
+
assert_equal '1100.00 HNL', posting0.transfers[0].complex_commodity.left.to_s
|
224
|
+
assert_equal :per_lot, posting0.transfers[0].complex_commodity.operation
|
225
|
+
assert_equal '$ 45.83', posting0.transfers[0].complex_commodity.right.to_s
|
226
|
+
assert_equal ['intention: Ignored'], posting0.transfers[0].tags.collect(&:to_s)
|
227
|
+
|
228
|
+
# Posting 0, Transfer 1
|
229
|
+
assert_equal 'Personal:Expenses:Banking:Fees:IrlExchangeSlippage', posting0.transfers[1].account
|
230
|
+
assert_equal '$ 4.17', posting0.transfers[1].commodity.to_s
|
231
|
+
|
232
|
+
# Posting 0, Transfer 2
|
233
|
+
assert_equal 'Personal:Assets:Cash', posting0.transfers[2].account
|
234
|
+
assert_nil posting0.transfers[2].commodity
|
235
|
+
assert_nil posting0.transfers[2].complex_commodity
|
236
|
+
|
237
|
+
assert_equal true, posting0.valid?
|
238
|
+
|
239
|
+
assert_equal [
|
240
|
+
'2021-06-16 1100 HNL',
|
241
|
+
' Personal:Assets:Cash 1100.00 HNL @@ $ 45.83',
|
242
|
+
' ; intention: Ignored',
|
243
|
+
' Personal:Expenses:Banking:Fees:IrlExchangeSlippage $ 4.17',
|
244
|
+
' ; intention: Personal',
|
245
|
+
' Personal:Assets:Cash'
|
246
|
+
].join("\n"), posting0.to_ledger
|
247
|
+
|
248
|
+
# Posting 1
|
249
|
+
posting1 = journal.postings[1]
|
250
|
+
assert_equal Date.new(2012, 4, 10), posting1.date
|
251
|
+
assert_equal 'My Broker', posting1.description
|
252
|
+
assert_equal 0, posting1.tags.length
|
253
|
+
assert_equal 2, posting1.transfers.length
|
254
|
+
|
255
|
+
# Posting 1, Transfer 0
|
256
|
+
assert_equal 'Assets:Brokerage', posting1.transfers[0].account
|
257
|
+
assert_nil posting1.transfers[0].commodity
|
258
|
+
assert_equal '10 AAPL', posting1.transfers[0].complex_commodity.left.to_s
|
259
|
+
assert_nil posting1.transfers[0].complex_commodity.operation
|
260
|
+
assert_nil posting1.transfers[0].complex_commodity.right
|
261
|
+
assert_equal :per_unit, posting1.transfers[0].complex_commodity.left_lot_operation
|
262
|
+
assert_equal '$ 50.00', posting1.transfers[0].complex_commodity.left_lot.to_s
|
263
|
+
assert_equal 0, posting1.transfers[0].tags.length
|
264
|
+
|
265
|
+
# Posting 1, Transfer 1
|
266
|
+
assert_equal 'Assets:Brokerage:Cash', posting1.transfers[1].account
|
267
|
+
assert_equal '$ -500.00', posting1.transfers[1].commodity.to_s
|
268
|
+
assert_equal [
|
269
|
+
'2012-04-10 My Broker',
|
270
|
+
' Assets:Brokerage 10 AAPL {$ 50.00}',
|
271
|
+
' Assets:Brokerage:Cash $ -500.00'
|
272
|
+
].join("\n"), posting1.to_ledger
|
273
|
+
|
274
|
+
# Posting 2
|
275
|
+
posting2 = journal.postings[2]
|
276
|
+
assert_equal Date.new(2012, 4, 10), posting2.date
|
277
|
+
assert_equal 'My Broker', posting2.description
|
278
|
+
assert_equal 0, posting2.tags.length
|
279
|
+
assert_equal 3, posting2.transfers.length
|
280
|
+
|
281
|
+
# Posting 2, Transfer 0
|
282
|
+
assert_equal 'Assets:Brokerage:Cash', posting2.transfers[0].account
|
283
|
+
assert_equal '$ 750.00', posting2.transfers[0].commodity.to_s
|
284
|
+
|
285
|
+
# Posting 2, Transfer 1
|
286
|
+
assert_equal 'Assets:Brokerage', posting2.transfers[1].account
|
287
|
+
assert_nil posting2.transfers[1].commodity
|
288
|
+
assert_equal '-10 AAPL', posting2.transfers[1].complex_commodity.left.to_s
|
289
|
+
assert_equal :per_lot, posting2.transfers[1].complex_commodity.left_lot_operation
|
290
|
+
assert_equal '$ 500.00', posting2.transfers[1].complex_commodity.left_lot.to_s
|
291
|
+
|
292
|
+
assert_equal :per_lot, posting2.transfers[1].complex_commodity.operation
|
293
|
+
assert_equal '$ 750.00', posting2.transfers[1].complex_commodity.right.to_s
|
294
|
+
assert_equal 0, posting2.transfers[1].tags.length
|
295
|
+
|
296
|
+
# Posting 2, Transfer 2
|
297
|
+
assert_equal 'Income:Capital Gains', posting2.transfers[2].account
|
298
|
+
assert_equal '$ -250.00', posting2.transfers[2].commodity.to_s
|
299
|
+
assert_equal [
|
300
|
+
'2012-04-10 My Broker',
|
301
|
+
' Assets:Brokerage:Cash $ 750.00',
|
302
|
+
' Assets:Brokerage -10 AAPL {{$ 500.00}} @@ $ 750.00',
|
303
|
+
' Income:Capital Gains $ -250.00'
|
304
|
+
].join("\n"), posting2.to_ledger
|
305
|
+
|
306
|
+
# Posting 3
|
307
|
+
posting3 = journal.postings[3]
|
308
|
+
assert_equal Date.new(2012, 4, 10), posting3.date
|
309
|
+
assert_equal 'My Broker', posting3.description
|
310
|
+
assert_equal 0, posting3.tags.length
|
311
|
+
assert_equal 3, posting3.transfers.length
|
312
|
+
|
313
|
+
# Posting 3, Transfer 0
|
314
|
+
assert_equal 'Assets:Brokerage:Cash', posting3.transfers[0].account
|
315
|
+
assert_equal '$ 375.00', posting3.transfers[0].commodity.to_s
|
316
|
+
|
317
|
+
# Posting 3, Transfer 1
|
318
|
+
assert_equal 'Assets:Brokerage', posting3.transfers[1].account
|
319
|
+
assert_nil posting3.transfers[1].commodity
|
320
|
+
assert_equal '-5 AAPL', posting3.transfers[1].complex_commodity.left.to_s
|
321
|
+
assert_equal :per_unit, posting3.transfers[1].complex_commodity.left_lot_operation
|
322
|
+
assert_equal '$ 50.00', posting3.transfers[1].complex_commodity.left_lot.to_s
|
323
|
+
assert_equal '2012-04-10', posting3.transfers[1].complex_commodity.left_date.to_s
|
324
|
+
assert_equal :per_lot, posting3.transfers[1].complex_commodity.operation
|
325
|
+
assert_equal '$ 375.00', posting3.transfers[1].complex_commodity.right.to_s
|
326
|
+
assert_equal 0, posting3.transfers[1].tags.length
|
327
|
+
|
328
|
+
# Posting 3, Transfer 2
|
329
|
+
assert_equal 'Income:Capital Gains', posting3.transfers[2].account
|
330
|
+
assert_equal '$ -125.00', posting3.transfers[2].commodity.to_s
|
331
|
+
|
332
|
+
assert_equal [
|
333
|
+
'2012-04-10 My Broker',
|
334
|
+
' Assets:Brokerage:Cash $ 375.00',
|
335
|
+
' Assets:Brokerage -5 AAPL {$ 50.00} [2012-04-10] @@ $ 375.00',
|
336
|
+
' Income:Capital Gains $ -125.00'
|
337
|
+
].join("\n"), posting3.to_ledger
|
338
|
+
|
339
|
+
# Posting 4
|
340
|
+
posting4 = journal.postings[4]
|
341
|
+
assert_equal Date.new(2012, 4, 10), posting4.date
|
342
|
+
assert_equal 'My Broker', posting4.description
|
343
|
+
assert_equal 0, posting4.tags.length
|
344
|
+
assert_equal 3, posting4.transfers.length
|
345
|
+
|
346
|
+
# Posting 4, Transfer 0
|
347
|
+
assert_equal 'Assets:Brokerage:Cash', posting4.transfers[0].account
|
348
|
+
assert_equal '$ 375.00', posting4.transfers[0].commodity.to_s
|
349
|
+
|
350
|
+
# Posting 4, Transfer 1
|
351
|
+
assert_equal 'Assets:Brokerage', posting4.transfers[1].account
|
352
|
+
assert_nil posting4.transfers[1].commodity
|
353
|
+
assert_equal '-5 AAPL', posting4.transfers[1].complex_commodity.left.to_s
|
354
|
+
assert_equal :per_unit, posting4.transfers[1].complex_commodity.left_lot_operation
|
355
|
+
assert_equal '$ 50.00', posting4.transfers[1].complex_commodity.left_lot.to_s
|
356
|
+
assert_equal '2012-04-10', posting4.transfers[1].complex_commodity.left_date.to_s
|
357
|
+
assert_equal 'Oh my!', posting4.transfers[1].complex_commodity.left_expression
|
358
|
+
assert_equal :per_lot, posting4.transfers[1].complex_commodity.operation
|
359
|
+
assert_equal '$ 375.00', posting4.transfers[1].complex_commodity.right.to_s
|
360
|
+
assert_equal 0, posting4.transfers[1].tags.length
|
361
|
+
|
362
|
+
# Posting 4, Transfer 2
|
363
|
+
assert_equal 'Income:Capital Gains', posting4.transfers[2].account
|
364
|
+
assert_equal '$ -125.00', posting4.transfers[2].commodity.to_s
|
365
|
+
|
366
|
+
assert_equal [
|
367
|
+
'2012-04-10 My Broker',
|
368
|
+
' Assets:Brokerage:Cash $ 375.00',
|
369
|
+
' Assets:Brokerage -5 AAPL {$ 50.00} [2012-04-10] (Oh my!) @@ $ 375.00',
|
370
|
+
' Income:Capital Gains $ -125.00'
|
371
|
+
].join("\n"), posting4.to_ledger
|
372
|
+
|
373
|
+
# Posting 5
|
374
|
+
posting5 = journal.postings[5]
|
375
|
+
assert_equal Date.new(2012, 4, 10), posting5.date
|
376
|
+
assert_equal 'My Broker', posting5.description
|
377
|
+
assert_equal 0, posting5.tags.length
|
378
|
+
assert_equal 3, posting5.transfers.length
|
379
|
+
|
380
|
+
# Posting 5, Transfer 0
|
381
|
+
assert_equal 'Assets:Brokerage:Cash', posting5.transfers[0].account
|
382
|
+
assert_equal '$ 375.00', posting5.transfers[0].commodity.to_s
|
383
|
+
|
384
|
+
# Posting 5, Transfer 1
|
385
|
+
assert_equal 'Assets:Brokerage', posting5.transfers[1].account
|
386
|
+
assert_nil posting5.transfers[1].commodity
|
387
|
+
assert_equal '-5 AAPL', posting5.transfers[1].complex_commodity.left.to_s
|
388
|
+
assert_equal :per_unit, posting5.transfers[1].complex_commodity.left_lot_operation
|
389
|
+
assert_equal '$ 50.00', posting5.transfers[1].complex_commodity.left_lot.to_s
|
390
|
+
assert_equal 'ten_dollars', posting5.transfers[1].complex_commodity.left_lambda.to_s
|
391
|
+
assert_equal :per_lot, posting5.transfers[1].complex_commodity.operation
|
392
|
+
assert_equal '$ 375.00', posting5.transfers[1].complex_commodity.right.to_s
|
393
|
+
assert_equal 0, posting5.transfers[1].tags.length
|
394
|
+
|
395
|
+
# Posting 5, Transfer 2
|
396
|
+
assert_equal 'Income:Capital Gains', posting5.transfers[2].account
|
397
|
+
assert_equal '$ -125.00', posting5.transfers[2].commodity.to_s
|
398
|
+
|
399
|
+
assert_equal [
|
400
|
+
'2012-04-10 My Broker',
|
401
|
+
' Assets:Brokerage:Cash $ 375.00',
|
402
|
+
' Assets:Brokerage -5 AAPL {$ 50.00} ((ten_dollars)) @@ $ 375.00',
|
403
|
+
' Income:Capital Gains $ -125.00'
|
404
|
+
].join("\n"), posting5.to_ledger
|
405
|
+
|
406
|
+
# Posting 6
|
407
|
+
posting6 = journal.postings[6]
|
408
|
+
assert_equal Date.new(2012, 4, 10), posting6.date
|
409
|
+
assert_equal 'My Broker', posting6.description
|
410
|
+
assert_equal 0, posting6.tags.length
|
411
|
+
assert_equal 3, posting6.transfers.length
|
412
|
+
|
413
|
+
# Posting 6, Transfer 0
|
414
|
+
assert_equal 'A:B:Cash', posting6.transfers[0].account
|
415
|
+
assert_equal '$ 375.00', posting6.transfers[0].commodity.to_s
|
416
|
+
|
417
|
+
# Posting 6, Transfer 1
|
418
|
+
assert_equal 'A:B', posting6.transfers[1].account
|
419
|
+
assert_nil posting6.transfers[1].commodity
|
420
|
+
assert_equal '-5 AAPL', posting6.transfers[1].complex_commodity.left.to_s
|
421
|
+
assert_equal :per_unit, posting6.transfers[1].complex_commodity.left_lot_operation
|
422
|
+
assert_equal '$ 50.00', posting6.transfers[1].complex_commodity.left_lot.to_s
|
423
|
+
|
424
|
+
assert_equal 's, d, t -> market(0, date, t)', posting6.transfers[1].complex_commodity.left_lambda.to_s
|
425
|
+
assert_equal :per_lot, posting6.transfers[1].complex_commodity.operation
|
426
|
+
assert_equal '$ 375.00', posting6.transfers[1].complex_commodity.right.to_s
|
427
|
+
assert_equal 0, posting6.transfers[1].tags.length
|
428
|
+
|
429
|
+
# Posting 6, Transfer 2
|
430
|
+
assert_equal 'Income:Capital Gains', posting6.transfers[2].account
|
431
|
+
assert_equal '$ -125.00', posting6.transfers[2].commodity.to_s
|
432
|
+
|
433
|
+
assert_equal [
|
434
|
+
'2012-04-10 My Broker',
|
435
|
+
' A:B:Cash $ 375.00',
|
436
|
+
' A:B -5 AAPL {$ 50.00} ((s, d, t -> market(0, date, t))) @@ $ 375.00',
|
437
|
+
' Income:Capital Gains $ -125.00'
|
438
|
+
].join("\n"), posting6.to_ledger
|
439
|
+
|
440
|
+
# Posting 7
|
441
|
+
posting7 = journal.postings[7]
|
442
|
+
assert_equal Date.new(2010, 5, 31), posting7.date
|
443
|
+
assert_equal "Farmer's Market", posting7.description
|
444
|
+
assert_equal 0, posting7.tags.length
|
445
|
+
assert_equal 4, posting7.transfers.length
|
446
|
+
|
447
|
+
# Posting 7, Transfer 0
|
448
|
+
assert_equal 'Assets:My Larder', posting7.transfers[0].account
|
449
|
+
assert_equal :per_unit, posting7.transfers[0].complex_commodity.operation
|
450
|
+
assert_equal '100 apples', posting7.transfers[0].complex_commodity.left.to_s
|
451
|
+
assert_equal '$ 0.200000', posting7.transfers[0].complex_commodity.right.to_s
|
452
|
+
|
453
|
+
# Posting 7, Transfer 1
|
454
|
+
assert_equal 'Assets:My Larder', posting7.transfers[1].account
|
455
|
+
assert_equal :per_unit, posting7.transfers[1].complex_commodity.operation
|
456
|
+
assert_equal '100 pineapples', posting7.transfers[1].complex_commodity.left.to_s
|
457
|
+
assert_equal '$ 0.33', posting7.transfers[1].complex_commodity.right.to_s
|
458
|
+
|
459
|
+
# Posting 7, Transfer 2
|
460
|
+
assert_equal 'Assets:My Larder', posting7.transfers[2].account
|
461
|
+
assert_equal :per_unit, posting7.transfers[2].complex_commodity.operation
|
462
|
+
assert_equal '100 "crab apples"', posting7.transfers[2].complex_commodity.left.to_s
|
463
|
+
assert_equal '$ 0.04', posting7.transfers[2].complex_commodity.right.to_s
|
464
|
+
|
465
|
+
# Posting 7, Transfer 3
|
466
|
+
assert_equal 'Assets:Checking', posting7.transfers[3].account
|
467
|
+
assert_nil posting7.transfers[3].commodity
|
468
|
+
|
469
|
+
assert_equal [
|
470
|
+
"2010-05-31 Farmer's Market",
|
471
|
+
' Assets:My Larder 100 apples @ $ 0.200000',
|
472
|
+
' Assets:My Larder 100 pineapples @ $ 0.33',
|
473
|
+
' Assets:My Larder 100 "crab apples" @ $ 0.04',
|
474
|
+
' Assets:Checking'
|
475
|
+
].join("\n"), posting7.to_ledger
|
476
|
+
|
477
|
+
# Posting 8
|
478
|
+
posting8 = journal.postings[8]
|
479
|
+
assert_equal Date.new(2012, 4, 10), posting8.date
|
480
|
+
assert_equal 'My Broker', posting8.description
|
481
|
+
assert_equal 0, posting8.tags.length
|
482
|
+
assert_equal 2, posting8.transfers.length
|
483
|
+
|
484
|
+
# Posting 8, Transfer 0
|
485
|
+
assert_equal 'Assets:Brokerage', posting8.transfers[0].account
|
486
|
+
assert_equal '10 AAPL', posting8.transfers[0].complex_commodity.left.to_s
|
487
|
+
assert_equal :per_unit, posting8.transfers[0].complex_commodity.operation
|
488
|
+
assert_equal true, posting8.transfers[0].complex_commodity.right_is_equal
|
489
|
+
assert_equal '$ 50.00', posting8.transfers[0].complex_commodity.right.to_s
|
490
|
+
|
491
|
+
# Posting 8, Transfer 1
|
492
|
+
assert_equal 'Assets:Brokerage:Cash', posting8.transfers[1].account
|
493
|
+
assert_equal '$ -500.00', posting8.transfers[1].commodity.to_s
|
494
|
+
|
495
|
+
assert_equal [
|
496
|
+
'2012-04-10 My Broker',
|
497
|
+
' Assets:Brokerage 10 AAPL @ = $ 50.00',
|
498
|
+
' Assets:Brokerage:Cash $ -500.00'
|
499
|
+
].join("\n"), posting8.to_ledger
|
500
|
+
|
501
|
+
# Posting 9
|
502
|
+
posting9 = journal.postings[9]
|
503
|
+
assert_equal Date.new(2012, 4, 10), posting9.date
|
504
|
+
assert_equal 'My Broker', posting9.description
|
505
|
+
assert_equal 0, posting9.tags.length
|
506
|
+
assert_equal 2, posting9.transfers.length
|
507
|
+
|
508
|
+
# Posting 9, Transfer 0
|
509
|
+
assert_equal 'Assets:Brokerage', posting9.transfers[0].account
|
510
|
+
assert_equal '10 AAPL', posting9.transfers[0].complex_commodity.left.to_s
|
511
|
+
assert_equal '$ 50.00', posting9.transfers[0].complex_commodity.left_lot.to_s
|
512
|
+
assert_equal true, posting9.transfers[0].complex_commodity.left_lot_is_equal
|
513
|
+
|
514
|
+
# Posting 9, Transfer 1
|
515
|
+
assert_equal 'Assets:Brokerage:Cash', posting9.transfers[1].account
|
516
|
+
assert_equal '$ -500.00', posting9.transfers[1].commodity.to_s
|
517
|
+
|
518
|
+
assert_equal [
|
519
|
+
'2012-04-10 My Broker',
|
520
|
+
' Assets:Brokerage 10 AAPL {=$ 50.00}',
|
521
|
+
' Assets:Brokerage:Cash $ -500.00'
|
522
|
+
].join("\n"), posting9.to_ledger
|
523
|
+
|
524
|
+
# Posting 10
|
525
|
+
posting10 = journal.postings[10]
|
526
|
+
assert_equal Date.new(2012, 3, 10), posting10.date
|
527
|
+
assert_equal 'My Broker', posting10.description
|
528
|
+
assert_equal 0, posting10.tags.length
|
529
|
+
assert_equal 2, posting10.transfers.length
|
530
|
+
|
531
|
+
# Posting 10, Transfer 0
|
532
|
+
assert_equal 'Assets:Brokerage', posting10.transfers[0].account
|
533
|
+
assert_equal '5 AAPL * 2', posting10.transfers[0].complex_commodity.left_expression
|
534
|
+
assert_equal '$500.00 / 10', posting10.transfers[0].complex_commodity.right_expression
|
535
|
+
assert_equal :per_unit, posting10.transfers[0].complex_commodity.operation
|
536
|
+
|
537
|
+
# Posting 10, Transfer 1
|
538
|
+
assert_equal 'Assets:Brokerage:Cash', posting10.transfers[1].account
|
539
|
+
assert_equal [
|
540
|
+
'2012-03-10 My Broker',
|
541
|
+
' Assets:Brokerage (5 AAPL * 2) @ ($500.00 / 10)',
|
542
|
+
' Assets:Brokerage:Cash'
|
543
|
+
].join("\n"), posting10.to_ledger
|
544
|
+
end
|
545
|
+
end
|
data/test/test_ledger.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'csv'
|
5
|
+
require 'minitest/autorun'
|
6
|
+
|
7
|
+
require_relative '../lib/rvgp'
|
8
|
+
|
9
|
+
# These tests ensure that RVGP::Pta::Ledger parses the xml output, as we'd expect.
|
10
|
+
# Atm, most of these tests are in a private repo, since they test 'my' output,
|
11
|
+
# that I can't share. However, as this projects is becoming public, we now have
|
12
|
+
# the ability to start running these tests against faker output. So, for the time
|
13
|
+
# being, I'm porting my personal tests over to faker/public tests. And what's here,
|
14
|
+
# is a subset of that suite.
|
15
|
+
class TestLedger < Minitest::Test
|
16
|
+
def test_balance_multiple_with_empty
|
17
|
+
# We'll use this feature here:
|
18
|
+
opts = { translate_meta_accounts: true }
|
19
|
+
|
20
|
+
# This command was used to produce the ledger_total_monthly_liabilities_with_empty.xml:
|
21
|
+
# note the '--empty', which, forces the output to include all months in the range, even
|
22
|
+
# when there's no activity in a given month:
|
23
|
+
#
|
24
|
+
# /usr/bin/ledger xml Liabilities --sort date --monthly --empty \
|
25
|
+
# --file /tmp/test_ledger/test-user.journal --collapse --display 'date>=[2020-01-01]' \
|
26
|
+
# --end 2020-12-31 > ledger_total_monthly_liabilities_with_empty.xml
|
27
|
+
#
|
28
|
+
# This table came by running the above ledger command as 'reg' instead of 'xml', and adjusting cell 0 to use
|
29
|
+
# the date format we generate
|
30
|
+
assert_register [
|
31
|
+
['2020-01-01', '- 20-Jan-31', nil, '0', '$ -10675.18'],
|
32
|
+
['2020-02-01', '- 20-Feb-29', 'Personal:Liabilities:AmericanExpress', '$ 303.72', '$ -10371.46'],
|
33
|
+
['2020-03-01', '- 20-Mar-31', nil, '0', '$ -10371.46'],
|
34
|
+
['2020-04-01', '- 20-Apr-30', 'Personal:Liabilities:AmericanExpress', '$ 661.01', '$ -9710.45'],
|
35
|
+
['2020-05-01', '- 20-May-31', 'Personal:Liabilities:AmericanExpress', '$ 912.36', '$ -8798.09'],
|
36
|
+
['2020-06-01', '- 20-Jun-30', 'Personal:Liabilities:AmericanExpress', '$ 322.98', '$ -8475.11'],
|
37
|
+
['2020-07-01', '- 20-Jul-31', 'Personal:Liabilities:AmericanExpress', '$ 279.26', '$ -8195.85'],
|
38
|
+
['2020-08-01', '- 20-Aug-31', nil, '0', '$ -8195.85'],
|
39
|
+
['2020-09-01', '- 20-Sep-30', 'Personal:Liabilities:AmericanExpress', '$ 330.54', '$ -7865.31'],
|
40
|
+
['2020-10-01', '- 20-Oct-31', nil, '0', '$ -7865.31'],
|
41
|
+
['2020-11-01', '- 20-Nov-30', 'Personal:Liabilities:AmericanExpress', '$ 262.42', '$ -7602.89']
|
42
|
+
# NOTE: We're missing december's output, from ledger. This is... 'the bug' that necessitated switching
|
43
|
+
# from --end to --display , in the reduce_postings_by_month
|
44
|
+
], RVGP::Pta::Ledger::Output::Register.new(asset_contents('ledger_total_monthly_liabilities_with_empty.xml'), opts)
|
45
|
+
|
46
|
+
# This is really no different than the above register, other than the values are different, I guess
|
47
|
+
#
|
48
|
+
# /usr/bin/ledger reg Income --sort date --monthly --empty \
|
49
|
+
# --file /tmp/test_ledger/ronnie-peterson.journal --collapse --begin 2023-01-01 \
|
50
|
+
# --end 2023-12-31 > 'ledger_total_monthly_liabilities_with_empty2.xml'
|
51
|
+
#
|
52
|
+
assert_register [
|
53
|
+
['2023-01-01', '- 23-Jan-31', 'Personal:Income:ThompsonHegmann', '$ -5565.74', '$ -5565.74'],
|
54
|
+
['2023-02-01', '- 23-Feb-28', :total, '$ -18028.51', '$ -23594.25'],
|
55
|
+
['2023-03-01', '- 23-Mar-31', nil, '0', '$ -23594.25'],
|
56
|
+
['2023-04-01', '- 23-Apr-30', 'Personal:Income:VonRuedenMosciski', '$ -5356.77', '$ -28951.02'],
|
57
|
+
['2023-05-01', '- 23-May-31', 'Personal:Income:ThompsonHegmann', '$ -12371.24', '$ -41322.26'],
|
58
|
+
['2023-06-01', '- 23-Jun-30', 'Personal:Income:ThompsonHegmann', '$ -6075.64', '$ -47397.90'],
|
59
|
+
['2023-07-01', '- 23-Jul-31', nil, '0', '$ -47397.90'],
|
60
|
+
['2023-08-01', '- 23-Aug-31', 'Personal:Income:VonRuedenMosciski', '$ -5784.12', '$ -53182.02'],
|
61
|
+
['2023-09-01', '- 23-Sep-30', nil, '0', '$ -53182.02'],
|
62
|
+
['2023-10-01', '- 23-Oct-31', nil, '0', '$ -53182.02'],
|
63
|
+
['2023-11-01', '- 23-Nov-30', 'Personal:Income:ThompsonHegmann', '$ -5485.09', '$ -58667.11'],
|
64
|
+
['2023-12-01', '- 23-Dec-31', 'Personal:Income:ThompsonHegmann', '$ -6297.26', '$ -64964.37']
|
65
|
+
], RVGP::Pta::Ledger::Output::Register.new(asset_contents('ledger_total_monthly_liabilities_with_empty2.xml'), opts)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def assert_register(expectations, register)
|
71
|
+
assert_equal expectations.length, register.transactions.length
|
72
|
+
|
73
|
+
# First:
|
74
|
+
0.upto(expectations.length - 1) do |i|
|
75
|
+
assert_equal expectations[i][0], register.transactions[i].date.to_s
|
76
|
+
assert_equal expectations[i][1], register.transactions[i].payee
|
77
|
+
assert_equal 1, register.transactions[i].postings.length
|
78
|
+
if expectations[i][2].nil?
|
79
|
+
assert_nil register.transactions[i].postings[0].account
|
80
|
+
else
|
81
|
+
assert_equal expectations[i][2], register.transactions[i].postings[0].account
|
82
|
+
end
|
83
|
+
assert_equal 1, register.transactions[i].postings[0].amounts.length
|
84
|
+
assert_equal expectations[i][3], register.transactions[i].postings[0].amounts.first.to_s
|
85
|
+
assert_equal 1, register.transactions[i].postings[0].totals.length
|
86
|
+
assert_equal expectations[i][3], register.transactions[i].postings[0].amounts.first.to_s
|
87
|
+
assert_equal expectations[i][4], register.transactions[i].postings[0].totals.first.to_s
|
88
|
+
amount_in_usd = register.transactions[i].postings[0].amount_in('$')
|
89
|
+
if amount_in_usd.nil?
|
90
|
+
assert_equal '0', expectations[i][3].to_s
|
91
|
+
else
|
92
|
+
assert_equal amount_in_usd.to_s, register.transactions[i].postings[0].amount_in('$').to_s
|
93
|
+
end
|
94
|
+
assert_equal expectations[i][4], register.transactions[i].postings[0].total_in('$').to_s
|
95
|
+
assert_equal({}, register.transactions[i].postings[0].tags)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def asset_contents(filename)
|
100
|
+
File.read [File.dirname(__FILE__), 'assets', filename].join('/')
|
101
|
+
end
|
102
|
+
end
|