rvgp 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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