aboutsummaryrefslogtreecommitdiffstats
path: root/beancount_extras_kris7t/plugins/selective_implicit_prices_test.py
blob: 6ead45dfbae9a83e55b9b18e9fdbaa6cba4a2cbd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
__copyright__ = "Copyright (C) 2015-2017  Martin Blais, " + \
    "2020  Kristóf Marussy <kristof@marussy.com>"
__license__ = "GNU GPLv2"

import unittest

from beancount.core.number import D
from beancount.core import data
from beancount.parser import cmptest
from beancount import loader

from beancount_extras_kris7t.plugins import selective_implicit_prices as implicit_prices


class TestImplicitPrices(cmptest.TestCase):

    @loader.load_doc()
    def test_add_implicit_prices__all_cases(self, entries, _, options_map):
        """
        1702-04-02 commodity USD
          implicit-prices: TRUE

        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2
        2013-01-01 open Assets:Other

        ;; An explicit price directive.
        2013-02-01 price  USD  1.10 CAD

        2013-04-01 * "A transaction with a price conversion."
          Assets:Account1             150 USD @ 1.12 CAD
          Assets:Other

        ;; This should book at price at the cost.
        2013-04-02 * "A transaction with a cost."
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other

        ;; This one should be IGNORED because it books against the above.
        2013-04-03 * "A transaction with a cost that reduces an existing position"
          Assets:Account1            -500 HOOL {520 USD}
          Assets:Other

        ;; This one should generate the price, even if it is reducing.
        2013-04-04 * "A transaction with a cost that reduces existing position, with price"
          Assets:Account1            -100 HOOL {520 USD} @ 530 USD
          Assets:Other

        ;; This is not reducing and should also book a price at cost.
        2013-04-05 * "A transaction with another cost that is not reducing."
          Assets:Account1             500 HOOL {540 USD}
          Assets:Other

        ;; The price here overrides the cost and should create an entry.
        2013-04-06 * "A transaction with a cost and a price."
          Assets:Account1             500 HOOL {540 USD} @ 560 USD
          Assets:Other
        """
        self.assertEqual(12, len(entries))
        new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map)
        price_entries = [entry for entry in new_entries if isinstance(entry, data.Price)]

        self.assertEqualEntries("""
        1702-04-02 commodity USD
          implicit-prices: TRUE

        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2
        2013-01-01 open Assets:Other

        2013-02-01 price USD 1.10 CAD

        2013-04-01 * "A transaction with a price conversion."
          Assets:Account1                        150 USD                        @ 1.12 CAD
          Assets:Other                          -168.00 CAD

        2013-04-01 price USD 1.12 CAD

        2013-04-02 * "A transaction with a cost."
          Assets:Account1                      1500 HOOL     {520 USD}
          Assets:Other                       -780000 USD

        2013-04-02 price HOOL 520 USD

        2013-04-03 * "A transaction with a cost that reduces an existing position"
          Assets:Account1                      -500 HOOL     {520 USD}
          Assets:Other                        260000 USD

        2013-04-04 * "A transaction with a cost that reduces existing position, with price"
          Assets:Account1                      -100 HOOL     {520 USD}     @ 530 USD
          Assets:Other                         52000 USD

        2013-04-04 price HOOL 530 USD

        2013-04-05 * "A transaction with another cost that is not reducing."
          Assets:Account1                       500 HOOL     {540 USD}
          Assets:Other                       -270000 USD

        2013-04-05 price HOOL 540 USD

        2013-04-06 * "A transaction with a cost and a price."
          Assets:Account1                       500 HOOL     {540 USD}     @ 560 USD
          Assets:Other                       -270000 USD

        2013-04-06 price HOOL 560 USD
        """, new_entries)

        self.assertEqual(6, len(price_entries))
        expected_values = [(x[0], x[1], D(x[2])) for x in [
            ('USD', 'CAD', '1.10'),
            ('USD', 'CAD', '1.12'),
            ('HOOL', 'USD', '520.00'),
            ('HOOL', 'USD', '530.00'),
            ('HOOL', 'USD', '540.00'),
            ('HOOL', 'USD', '560.00')
            ]]
        for expected, price in zip(expected_values, price_entries):
            actual = (price.currency, price.amount.currency, price.amount.number)
            self.assertEqual(expected, actual)

    @loader.load_doc()
    def test_add_implicit_prices__other_account(self, entries, errors, options_map):
        """
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2  "NONE"
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other

        2013-04-02 *
          Assets:Account2             1500 HOOL {530 USD}
          Assets:Other

        2013-04-10 * "Reduces existing position in account 1"
          Assets:Account1            -100 HOOL {520 USD}
          Assets:Other              52000 USD

        2013-04-11 * "Does not find an existing position in account 2"
          Assets:Account2            -200 HOOL {531 USD}
          Assets:Other             106200 USD

        """
        new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertEqualEntries("""
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2  "NONE"
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other             -780000 USD

        2013-04-02 *
          Assets:Account2             1500 HOOL {530 USD}
          Assets:Other             -795000 USD

        2013-04-01 price HOOL 520 USD

        2013-04-02 price HOOL 530 USD

        2013-04-10 * "Reduces existing position in account 1"
          Assets:Account1                       -100 HOOL {520 USD}
          Assets:Other                         52000 USD

        2013-04-11 * "Does not find an existing position in account 2"
          Assets:Account2                       -200 HOOL {531 USD}
          Assets:Other                        106200 USD

        ;; Because a match was not found against the inventory, a price will be added.
        2013-04-11 price HOOL 531 USD

        """, new_entries)

    @loader.load_doc()
    def test_add_implicit_prices__duplicates_on_same_transaction(self,
                                                                 entries, _, options_map):
        """
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2
        2013-01-01 open Assets:Other

        2013-04-01 * "Allowed because of same price"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Account2             1500 HOOL {520 USD}
          Assets:Other

        2013-04-02 * "Second one is disallowed because of different price"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Account2             1500 HOOL {530 USD}
          Assets:Other

        """
        new_entries, errors = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertEqual([], [type(error) for error in errors])
        self.assertEqualEntries("""
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2
        2013-01-01 open Assets:Other

        2013-04-01 * "Allowed because of same price"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Account2             1500 HOOL {520 USD}
          Assets:Other            -1560000 USD

        2013-04-01 price HOOL 520 USD

        2013-04-02 * "Second one is disallowed because of different price"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Account2             1500 HOOL {530 USD}
          Assets:Other            -1575000 USD

        2013-04-02 price HOOL 520 USD
        2013-04-02 price HOOL 530 USD  ;; Allowed for now.

        """, new_entries)

    @loader.load_doc()
    def test_add_implicit_prices__duplicates_on_different_transactions(self,
                                                                       entries, _,
                                                                       options_map):
        """
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2
        2013-01-01 open Assets:Other

        2013-04-01 * "Allowed because of same price #1"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other

        2013-04-01 * "Allowed because of same price #2"
          Assets:Account2             1500 HOOL {520 USD}
          Assets:Other

        2013-04-02 * "Second one is disallowed because of different price #1"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other

        2013-04-02 * "Second one is disallowed because of different price #2"
          Assets:Account2             1500 HOOL {530 USD}
          Assets:Other

        """
        new_entries, errors = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertEqual([], [type(error) for error in errors])
        self.assertEqualEntries("""
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Account2
        2013-01-01 open Assets:Other

        2013-04-01 * "Allowed because of same price #1"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other             -780000 USD

        2013-04-01 * "Allowed because of same price #2"
          Assets:Account2             1500 HOOL {520 USD}
          Assets:Other             -780000 USD

        2013-04-01 price HOOL 520 USD

        2013-04-02 * "Second one is disallowed because of different price #1"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other             -780000 USD

        2013-04-02 * "Second one is disallowed because of different price #2"
          Assets:Account2             1500 HOOL {530 USD}
          Assets:Other             -795000 USD

        2013-04-02 price HOOL 520 USD
        2013-04-02 price HOOL 530 USD  ;; Allowed for now.

        """, new_entries)

    @loader.load_doc()
    def test_add_implicit_prices__duplicates_overloaded(self, entries, _, options_map):
        """
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 * "Allowed, sets the price for that day"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other

        2013-04-01 * "Will be ignored, price for the day already set"
          Assets:Account1             1500 HOOL {530 USD}
          Assets:Other

        2013-04-01 * "Should be ignored too, price for the day already set"
          Assets:Account1             1500 HOOL {530 USD}
          Assets:Other
        """
        new_entries, errors = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertEqual([], [type(error) for error in errors])
        self.assertEqualEntries("""
        2013-01-01 commodity HOOL
          implicit-prices: TRUE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 * "Allowed, sets the price for that day"
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other             -780000 USD

        2013-04-01 * "Will be ignored, price for the day already set"
          Assets:Account1             1500 HOOL {530 USD}
          Assets:Other             -795000 USD

        2013-04-01 * "Should be ignored too, price for the day already set"
          Assets:Account1             1500 HOOL {530 USD}
          Assets:Other             -795000 USD

        2013-04-01 price HOOL 520 USD
        2013-04-01 price HOOL 530 USD

        """, new_entries)

    @loader.load_doc()
    def test_add_implicit_prices__not_enabled(self, entries, errors, options_map):
        """
        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other
        """
        new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertEqualEntries("""
        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other             -780000 USD
        """, new_entries)

    @loader.load_doc()
    def test_add_implicit_prices__disabled(self, entries, errors, options_map):
        """
        2013-01-01 commodity HOOL
          implicit-prices: FALSE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other
        """
        new_entries, _ = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertEqualEntries("""
        2013-01-01 commodity HOOL
          implicit-prices: FALSE

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other             -780000 USD
        """, new_entries)

    @loader.load_doc()
    def test_add_implicit_prices__invalid(self, entries, errors, options_map):
        """
        2013-01-01 commodity HOOL
          implicit-prices: "yes"

        2013-01-01 open Assets:Account1
        2013-01-01 open Assets:Other

        2013-04-01 *
          Assets:Account1             1500 HOOL {520 USD}
          Assets:Other
        """
        _, new_errors = implicit_prices.add_implicit_prices(entries, options_map)
        self.assertRegex(new_errors[0].message, '^implicit-prices must be Boolean')


if __name__ == '__main__':
    unittest.main()