aboutsummaryrefslogtreecommitdiffstats
path: root/beancount_extras_kris7t/plugins/closing_balance.py
diff options
context:
space:
mode:
Diffstat (limited to 'beancount_extras_kris7t/plugins/closing_balance.py')
-rw-r--r--beancount_extras_kris7t/plugins/closing_balance.py70
1 files changed, 70 insertions, 0 deletions
diff --git a/beancount_extras_kris7t/plugins/closing_balance.py b/beancount_extras_kris7t/plugins/closing_balance.py
new file mode 100644
index 0000000..a22e712
--- /dev/null
+++ b/beancount_extras_kris7t/plugins/closing_balance.py
@@ -0,0 +1,70 @@
1'''
2Plugin that closes an account by transferring its whole balance to another account.
3'''
4__copyright__ = 'Copyright (c) 2020 Kristóf Marussy <kristof@marussy.com>'
5__license__ = 'GNU GPLv2'
6
7from typing import Any, Dict, List, NamedTuple, Optional, Tuple
8
9from beancount.core.data import Balance, Close, Directive, Entries, Meta, Posting, Transaction
10from beancount.core.flags import FLAG_OKAY
11from beancount.core.number import ZERO
12
13__plugins__ = ('close_with_balance_assertions',)
14
15CLOSE_TO_META = 'close-to'
16CLOSING_META = 'closing'
17
18
19class ClosingBalanceError(NamedTuple):
20 source: Optional[Meta]
21 message: str
22 entry: Directive
23
24
25def close_with_balance_assertions(entries: Entries,
26 options_map: Dict[str, Any],
27 config_str: Optional[str] = None) -> \
28 Tuple[Entries, List[ClosingBalanceError]]:
29 new_entries: Entries = []
30 errors: List[ClosingBalanceError] = []
31 for entry in entries:
32 new_entries.append(entry)
33 if isinstance(entry, Balance) and CLOSE_TO_META in entry.meta:
34 close_to_account = entry.meta[CLOSE_TO_META]
35 if not isinstance(close_to_account, str):
36 errors.append(ClosingBalanceError(
37 entry.meta,
38 f'{CLOSE_TO_META} must be a string, got {close_to_account} instead',
39 entry))
40 continue
41 if entry.tolerance is not None and entry.tolerance != ZERO:
42 errors.append(ClosingBalanceError(
43 entry.meta,
44 f'Closing an account requires {ZERO} tolerance, got {entry.tolerance} instead',
45 entry))
46 continue
47 if entry.diff_amount is not None:
48 errors.append(ClosingBalanceError(
49 entry.meta,
50 f'Not closing {entry.account} with {entry.diff_amount} failed balance check',
51 entry))
52 continue
53 new_meta = dict(entry.meta)
54 del new_meta[CLOSE_TO_META]
55 if entry.amount.number != ZERO:
56 new_entries.append(Transaction(
57 new_meta,
58 entry.date,
59 FLAG_OKAY,
60 None,
61 f'Closing {entry.account}',
62 set(),
63 set(),
64 [
65 Posting(entry.account, -entry.amount, None, None, None,
66 {CLOSING_META: True}),
67 Posting(close_to_account, entry.amount, None, None, None, None)
68 ]))
69 new_entries.append(Close(new_meta, entry.date, entry.account))
70 return new_entries, errors