rvgp 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +23 -0
  4. data/LICENSE +504 -0
  5. data/README.md +223 -0
  6. data/Rakefile +32 -0
  7. data/bin/rvgp +8 -0
  8. data/lib/rvgp/application/config.rb +159 -0
  9. data/lib/rvgp/application/descendant_registry.rb +122 -0
  10. data/lib/rvgp/application/status_output.rb +139 -0
  11. data/lib/rvgp/application.rb +170 -0
  12. data/lib/rvgp/base/command.rb +457 -0
  13. data/lib/rvgp/base/grid.rb +531 -0
  14. data/lib/rvgp/base/reader.rb +29 -0
  15. data/lib/rvgp/base/reconciler.rb +434 -0
  16. data/lib/rvgp/base/validation.rb +261 -0
  17. data/lib/rvgp/commands/cashflow.rb +160 -0
  18. data/lib/rvgp/commands/grid.rb +70 -0
  19. data/lib/rvgp/commands/ireconcile.rb +95 -0
  20. data/lib/rvgp/commands/new_project.rb +296 -0
  21. data/lib/rvgp/commands/plot.rb +41 -0
  22. data/lib/rvgp/commands/publish_gsheets.rb +83 -0
  23. data/lib/rvgp/commands/reconcile.rb +58 -0
  24. data/lib/rvgp/commands/rotate_year.rb +202 -0
  25. data/lib/rvgp/commands/validate_journal.rb +59 -0
  26. data/lib/rvgp/commands/validate_system.rb +44 -0
  27. data/lib/rvgp/commands.rb +160 -0
  28. data/lib/rvgp/dashboard.rb +252 -0
  29. data/lib/rvgp/fakers/fake_feed.rb +245 -0
  30. data/lib/rvgp/fakers/fake_journal.rb +57 -0
  31. data/lib/rvgp/fakers/fake_reconciler.rb +88 -0
  32. data/lib/rvgp/fakers/faker_helpers.rb +25 -0
  33. data/lib/rvgp/gem.rb +80 -0
  34. data/lib/rvgp/journal/commodity.rb +453 -0
  35. data/lib/rvgp/journal/complex_commodity.rb +214 -0
  36. data/lib/rvgp/journal/currency.rb +101 -0
  37. data/lib/rvgp/journal/journal.rb +141 -0
  38. data/lib/rvgp/journal/posting.rb +156 -0
  39. data/lib/rvgp/journal/pricer.rb +267 -0
  40. data/lib/rvgp/journal.rb +24 -0
  41. data/lib/rvgp/plot/gnuplot.rb +478 -0
  42. data/lib/rvgp/plot/google-drive/output_csv.rb +44 -0
  43. data/lib/rvgp/plot/google-drive/output_google_sheets.rb +434 -0
  44. data/lib/rvgp/plot/google-drive/sheet.rb +67 -0
  45. data/lib/rvgp/plot.rb +293 -0
  46. data/lib/rvgp/pta/hledger.rb +237 -0
  47. data/lib/rvgp/pta/ledger.rb +308 -0
  48. data/lib/rvgp/pta.rb +311 -0
  49. data/lib/rvgp/reconcilers/csv_reconciler.rb +424 -0
  50. data/lib/rvgp/reconcilers/journal_reconciler.rb +41 -0
  51. data/lib/rvgp/reconcilers/shorthand/finance_gem_hacks.rb +48 -0
  52. data/lib/rvgp/reconcilers/shorthand/international_atm.rb +152 -0
  53. data/lib/rvgp/reconcilers/shorthand/investment.rb +144 -0
  54. data/lib/rvgp/reconcilers/shorthand/mortgage.rb +195 -0
  55. data/lib/rvgp/utilities/grid_query.rb +190 -0
  56. data/lib/rvgp/utilities/yaml.rb +131 -0
  57. data/lib/rvgp/utilities.rb +44 -0
  58. data/lib/rvgp/validations/balance_validation.rb +68 -0
  59. data/lib/rvgp/validations/duplicate_tags_validation.rb +48 -0
  60. data/lib/rvgp/validations/uncategorized_validation.rb +15 -0
  61. data/lib/rvgp.rb +66 -0
  62. data/resources/README.MD/2022-cashflow-google.png +0 -0
  63. data/resources/README.MD/2022-cashflow.png +0 -0
  64. data/resources/README.MD/all-wealth-growth-google.png +0 -0
  65. data/resources/README.MD/all-wealth-growth.png +0 -0
  66. data/resources/gnuplot/default.yml +80 -0
  67. data/resources/i18n/en.yml +192 -0
  68. data/resources/iso-4217-currencies.json +171 -0
  69. data/resources/skel/Rakefile +5 -0
  70. data/resources/skel/app/grids/cashflow_grid.rb +27 -0
  71. data/resources/skel/app/grids/monthly_income_and_expenses_grid.rb +25 -0
  72. data/resources/skel/app/grids/wealth_growth_grid.rb +35 -0
  73. data/resources/skel/app/plots/cashflow.yml +33 -0
  74. data/resources/skel/app/plots/monthly-income-and-expenses.yml +17 -0
  75. data/resources/skel/app/plots/wealth-growth.yml +20 -0
  76. data/resources/skel/config/csv-format-acme-checking.yml +9 -0
  77. data/resources/skel/config/google-secrets.yml +5 -0
  78. data/resources/skel/config/rvgp.yml +0 -0
  79. data/resources/skel/journals/prices.db +0 -0
  80. data/rvgp.gemspec +6 -0
  81. data/test/assets/ledger_total_monthly_liabilities_with_empty.xml +383 -0
  82. data/test/assets/ledger_total_monthly_liabilities_with_empty2.xml +428 -0
  83. data/test/test_command_base.rb +61 -0
  84. data/test/test_commodity.rb +270 -0
  85. data/test/test_csv_reconciler.rb +60 -0
  86. data/test/test_currency.rb +24 -0
  87. data/test/test_fake_feed.rb +228 -0
  88. data/test/test_fake_journal.rb +98 -0
  89. data/test/test_fake_reconciler.rb +60 -0
  90. data/test/test_journal_parse.rb +545 -0
  91. data/test/test_ledger.rb +102 -0
  92. data/test/test_plot.rb +133 -0
  93. data/test/test_posting.rb +50 -0
  94. data/test/test_pricer.rb +139 -0
  95. data/test/test_pta_adapter.rb +575 -0
  96. data/test/test_utilities.rb +45 -0
  97. 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
@@ -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