__copyright__ = "Copyright (C) 2015-2017 Martin Blais, " + \ "2020 Kristóf Marussy " __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()