hsoft / moneyguru (http://hardcoded.net/moneyguru)
Future-aware personal finance application for Mac OS X and Windows.
| commit 986: | 096c21184984 |
| parent 985: | a00d52a70d9b |
| branch: | default |
Changed (Δ13.5 KB):
core/document.py (1 lines added, 1 lines removed)
core/gui/account_balance_graph.py (1 lines added, 1 lines removed)
core/gui/account_flow_graph.py (1 lines added, 1 lines removed)
core/gui/account_pie_chart.py (2 lines added, 2 lines removed)
core/gui/balance_sheet.py (4 lines added, 4 lines removed)
core/gui/entry_table.py (3 lines added, 2 lines removed)
core/gui/income_statement.py (4 lines added, 4 lines removed)
core/gui/net_worth_graph.py (1 lines added, 1 lines removed)
core/gui/profit_graph.py (1 lines added, 1 lines removed)
core/model/account.py (5 lines added, 100 lines removed)
core/model/budget.py (1 lines added, 1 lines removed)
core/model/entry.py (200 lines added, 0 lines removed)
core/model/oven.py (7 lines added, 6 lines removed)
core/model/transaction.py (0 lines added, 80 lines removed)
core/saver/qif.py (1 lines added, 1 lines removed)
core/tests/gui/balance_sheet_test.py (0 lines added, 1 lines removed)
core/tests/model/account_test.py (5 lines added, 7 lines removed)
Up to file-list core/document.py:
| … | … | @@ -529,7 +529,7 @@ class Document(Repeater): |
529 |
529 |
return 0 |
530 |
530 |
budgeted_amount = sum(-b.amount_for_date_range(date_range, currency=currency) for b in budgets) |
531 |
531 |
if target is not None: |
532 |
budgeted_amount = target. |
|
532 |
budgeted_amount = target.normalize_amount(budgeted_amount) |
|
533 |
533 |
return budgeted_amount |
534 |
534 |
|
535 |
535 |
def change_budget(self, original, new): |
Up to file-list core/gui/account_balance_graph.py:
| … | … | @@ -20,7 +20,7 @@ class AccountBalanceGraph(BalanceGraph): |
20 |
20 |
def _balance_for_date(self, date): |
21 |
21 |
if self._account is None: |
22 |
22 |
return 0 |
23 |
entry = self._account. |
|
23 |
entry = self._account.entries.last_entry(date=date) |
|
24 |
24 |
return entry.normal_balance() if entry else 0 |
25 |
25 |
|
26 |
26 |
def _budget_for_date(self, date): |
Up to file-list core/gui/account_flow_graph.py:
| … | … | @@ -24,7 +24,7 @@ class AccountFlowGraph(BarGraph): |
24 |
24 |
self.document.oven.continue_cooking(date_range.end) # it's possible that the overflow is not cooked |
25 |
25 |
account = self.mainwindow.shown_account |
26 |
26 |
currency = self._currency() |
27 |
cash_flow = account. |
|
27 |
cash_flow = account.entries.normal_cash_flow(date_range, currency=currency) |
|
28 |
28 |
budgeted = self.document.budgets.normal_amount_for_account(account, date_range, currency=currency) |
29 |
29 |
return cash_flow + budgeted |
30 |
30 |
Up to file-list core/gui/account_pie_chart.py:
| … | … | @@ -65,7 +65,7 @@ class _BalancePieChart(_AccountPieChart) |
65 |
65 |
date = self.document.date_range.end |
66 |
66 |
currency = self.app.default_currency |
67 |
67 |
def get_value(account): |
68 |
balance = account. |
|
68 |
balance = account.entries.normal_balance(date=date, currency=currency) |
|
69 |
69 |
budget_date_range = DateRange(date.min, self.document.date_range.end) |
70 |
70 |
budgeted = self.document.budgeted_amount_for_target(account, budget_date_range) |
71 |
71 |
budgeted = convert_amount(budgeted, currency, date) |
| … | … | @@ -90,7 +90,7 @@ class _CashFlowPieChart(_AccountPieChart |
90 |
90 |
date_range = self.document.date_range |
91 |
91 |
currency = self.app.default_currency |
92 |
92 |
def get_value(account): |
93 |
cash_flow = account. |
|
93 |
cash_flow = account.entries.normal_cash_flow(date_range, currency=currency) |
|
94 |
94 |
budgeted = self.document.budgets.normal_amount_for_account(account, date_range, currency=currency) |
95 |
95 |
return cash_flow + budgeted |
96 |
96 |
Up to file-list core/gui/balance_sheet.py:
| … | … | @@ -36,10 +36,10 @@ class BalanceSheet(Report): |
36 |
36 |
start_date = date_range.start |
37 |
37 |
end_date = date_range.end |
38 |
38 |
currency = self.app.default_currency |
39 |
start_amount = account.normal_balance(start_date - timedelta(1)) |
|
40 |
start_amount_native = account.normal_balance(start_date - timedelta(1), currency=currency) |
|
41 |
end_amount = account.normal_balance(end_date) |
|
42 |
end_amount_native = account.normal_balance(end_date, currency=currency) |
|
39 |
start_amount = account.entries.normal_balance(start_date - timedelta(1)) |
|
40 |
start_amount_native = account.entries.normal_balance(start_date - timedelta(1), currency=currency) |
|
41 |
end_amount = account.entries.normal_balance(end_date) |
|
42 |
end_amount_native = account.entries.normal_balance(end_date, currency=currency) |
|
43 |
43 |
budget_date_range = DateRange(date.today(), end_date) |
44 |
44 |
budgeted_amount = self.document.budgeted_amount_for_target(account, budget_date_range) |
45 |
45 |
budgeted_amount_native = convert_amount(budgeted_amount, currency, date_range.end) |
Up to file-list core/gui/entry_table.py:
| … | … | @@ -10,8 +10,9 @@ import datetime |
10 |
10 |
from operator import attrgetter |
11 |
11 |
|
12 |
12 |
from ..model.amount import convert_amount |
13 |
from ..model.entry import Entry |
|
13 |
14 |
from ..model.recurrence import Spawn |
14 |
from ..model.transaction import Transaction |
|
15 |
from ..model.transaction import Transaction |
|
15 |
16 |
from ..trans import tr |
16 |
17 |
from .column import Column |
17 |
18 |
from .table import RowWithDebitAndCredit, RowWithDate, rowattr |
| … | … | @@ -99,7 +100,7 @@ class EntryTable(TransactionTableBase): |
99 |
100 |
balance = 0 |
100 |
101 |
reconciled_balance = 0 |
101 |
102 |
balance_with_budget = 0 |
102 |
previous_entry = account. |
|
103 |
previous_entry = account.entries.last_entry(date=date) |
|
103 |
104 |
if previous_entry: |
104 |
105 |
balance = previous_entry.balance |
105 |
106 |
reconciled_balance = previous_entry.reconciled_balance |
Up to file-list core/gui/income_statement.py:
| … | … | @@ -29,10 +29,10 @@ class IncomeStatement(Report): |
29 |
29 |
account = node.account |
30 |
30 |
date_range = self.document.date_range |
31 |
31 |
currency = self.app.default_currency |
32 |
cash_flow = account.normal_cash_flow(date_range) |
|
33 |
cash_flow_native = account.normal_cash_flow(date_range, currency) |
|
34 |
last_cash_flow = account.normal_cash_flow(date_range.prev()) |
|
35 |
last_cash_flow_native = account.normal_cash_flow(date_range.prev(), currency) |
|
32 |
cash_flow = account.entries.normal_cash_flow(date_range) |
|
33 |
cash_flow_native = account.entries.normal_cash_flow(date_range, currency) |
|
34 |
last_cash_flow = account.entries.normal_cash_flow(date_range.prev()) |
|
35 |
last_cash_flow_native = account.entries.normal_cash_flow(date_range.prev(), currency) |
|
36 |
36 |
remaining = self.document.budgets.normal_amount_for_account(account, date_range) |
37 |
37 |
remaining_native = self.document.budgets.normal_amount_for_account(account, date_range, currency) |
38 |
38 |
delta = cash_flow - last_cash_flow |
Up to file-list core/gui/net_worth_graph.py:
| … | … | @@ -16,7 +16,7 @@ class NetWorthGraph(BalanceGraph, SheetV |
16 |
16 |
BalanceGraph.__init__(self, view, networth_view) |
17 |
17 |
|
18 |
18 |
def _balance_for_date(self, date): |
19 |
balances = (a. |
|
19 |
balances = (a.entries.balance(date=date, currency=self._currency) for a in self._accounts) |
|
20 |
20 |
return sum(balances) |
21 |
21 |
|
22 |
22 |
def _budget_for_date(self, date): |
Up to file-list core/gui/profit_graph.py:
| … | … | @@ -24,7 +24,7 @@ class ProfitGraph(BarGraph, SheetViewNot |
24 |
24 |
self.document.oven.continue_cooking(date_range.end) # it's possible that the overflow is not cooked |
25 |
25 |
accounts = set(a for a in self.document.accounts if a.is_income_statement_account()) |
26 |
26 |
accounts = accounts - self.document.excluded_accounts |
27 |
cash_flow = -sum(a. |
|
27 |
cash_flow = -sum(a.entries.cash_flow(date_range, currency=self.app.default_currency) for a in accounts) |
|
28 |
28 |
budgeted_amount = self.document.budgeted_amount_for_target(None, date_range) |
29 |
29 |
return cash_flow + budgeted_amount |
30 |
30 |
Up to file-list core/model/account.py:
8 |
8 |
|
9 |
9 |
from __future__ import division, unicode_literals |
10 |
10 |
|
11 |
import bisect |
|
12 |
11 |
from functools import partial |
13 |
from itertools import takewhile |
|
14 |
12 |
|
15 |
from hsutil.misc import flatten |
|
16 |
||
17 |
from . |
|
13 |
from .entry import EntryList |
|
18 |
14 |
from .sort import sort_string |
19 |
15 |
from ..exception import DuplicateAccountNameError |
20 |
16 |
|
| … | … | @@ -38,15 +34,12 @@ class Account(object): |
38 |
34 |
def __init__(self, name, currency, type): |
39 |
35 |
self.name = name |
40 |
36 |
self.currency = currency |
41 |
self.entries = [] |
|
42 |
37 |
self.type = type |
43 |
38 |
self.reference = None |
44 |
39 |
self.group = None |
45 |
40 |
self.account_number = '' |
46 |
self._date2entries = {} |
|
47 |
self._sorted_entry_dates = [] |
|
48 |
# the key for this dict is (date_range, currency) |
|
49 |
self._daterange2cashflow = {} |
|
41 |
# entries are filled by the Oven |
|
42 |
self.entries = EntryList(self) |
|
50 |
43 |
|
51 |
44 |
def __repr__(self): |
52 |
45 |
return '<Account %r>' % self.name |
| … | … | @@ -62,73 +55,10 @@ class Account(object): |
62 |
55 |
# __ne__() are defined for this class too. |
63 |
56 |
return cmp(sort_string(self.name), sort_string(other.name)) |
64 |
57 |
|
65 |
#--- Private |
|
66 |
def _balance(self, balance_attr, date=None, currency=None): |
|
67 |
entry = self.last_entry(date) if date else self.last_entry() |
|
68 |
if entry: |
|
69 |
balance = getattr(entry, balance_attr) |
|
70 |
if currency: |
|
71 |
return convert_amount(balance, currency, date) |
|
72 |
else: |
|
73 |
return balance |
|
74 |
else: |
|
75 |
return 0 |
|
76 |
||
77 |
def _cash_flow(self, date_range, currency): |
|
78 |
cache = self._date2entries |
|
79 |
entries = flatten(cache[date] for date in date_range if date in cache) |
|
80 |
entries = (e for e in entries if not getattr(e.transaction, 'is_budget', False)) |
|
81 |
amounts = (convert_amount(e.amount, currency, e.date) for e in entries) |
|
82 |
return sum(amounts) |
|
83 |
||
84 |
def _normalize_amount(self, amount): |
|
58 |
#--- Public |
|
59 |
def normalize_amount(self, amount): |
|
85 |
60 |
return -amount if self.is_credit_account() else amount |
86 |
61 |
|
87 |
#--- Public |
|
88 |
def add_entry(self, entry): |
|
89 |
# add_entry() calls must *always* be made in order |
|
90 |
self.entries.append(entry) |
|
91 |
date = entry.date |
|
92 |
try: |
|
93 |
self._date2entries[date].append(entry) |
|
94 |
except KeyError: |
|
95 |
self._date2entries[date] = [entry] |
|
96 |
if not self._sorted_entry_dates or self._sorted_entry_dates[-1] < date: |
|
97 |
self._sorted_entry_dates.append(date) |
|
98 |
||
99 |
def balance(self, date=None, currency=None): |
|
100 |
return self._balance('balance', date, currency=currency) |
|
101 |
||
102 |
def balance_of_reconciled(self, date=None, currency=None): |
|
103 |
return self._balance('reconciled_balance', date, currency=currency) |
|
104 |
||
105 |
def balance_with_budget(self, date=None, currency=None): |
|
106 |
return self._balance('balance_with_budget', date, currency=currency) |
|
107 |
||
108 |
def cash_flow(self, date_range, currency=None): |
|
109 |
currency = currency or self.currency |
|
110 |
cache_key = (date_range, currency) |
|
111 |
if cache_key not in self._daterange2cashflow: |
|
112 |
cash_flow = self._cash_flow(date_range, currency) |
|
113 |
self._daterange2cashflow[cache_key] = cash_flow |
|
114 |
return self._daterange2cashflow[cache_key] |
|
115 |
||
116 |
def clear(self, from_date): |
|
117 |
if from_date is None: |
|
118 |
self.entries = [] |
|
119 |
self._date2entries = {} |
|
120 |
self._daterange2cashflow = {} |
|
121 |
self._sorted_entry_dates = [] |
|
122 |
else: |
|
123 |
self.entries = list(takewhile(lambda e: e.date < from_date, self.entries)) |
|
124 |
index = bisect.bisect_left(self._sorted_entry_dates, from_date) |
|
125 |
for date in self._sorted_entry_dates[index:]: |
|
126 |
del self._date2entries[date] |
|
127 |
for date_range, currency in self._daterange2cashflow.keys(): |
|
128 |
if date_range.end >= from_date: |
|
129 |
del self._daterange2cashflow[(date_range, currency)] |
|
130 |
del self._sorted_entry_dates[index:] |
|
131 |
||
132 |
62 |
def is_balance_sheet_account(self): |
133 |
63 |
return self.type in (AccountType.Asset, AccountType.Liability) |
134 |
64 |
|
| … | … | @@ -141,31 +71,6 @@ class Account(object): |
141 |
71 |
def is_income_statement_account(self): |
142 |
72 |
return self.type in (AccountType.Income, AccountType.Expense) |
143 |
73 |
|
144 |
def last_entry(self, date=None): |
|
145 |
if self.entries: |
|
146 |
if date is None: |
|
147 |
return self.entries[-1] |
|
148 |
else: |
|
149 |
if date not in self._date2entries: # find the nearest smaller date |
|
150 |
index = bisect.bisect_right(self._sorted_entry_dates, date) - 1 |
|
151 |
if index < 0: |
|
152 |
return None |
|
153 |
date = self._sorted_entry_dates[index] |
|
154 |
return self._date2entries[date][-1] |
|
155 |
return None |
|
156 |
||
157 |
def normal_balance(self, date=None, currency=None): |
|
158 |
balance = self.balance(date=date, currency=currency) |
|
159 |
return self._normalize_amount(balance) |
|
160 |
||
161 |
def normal_balance_of_reconciled(self, date=None, currency=None): |
|
162 |
balance = self.balance_of_reconciled(date=date, currency=currency) |
|
163 |
return self._normalize_amount(balance) |
|
164 |
||
165 |
def normal_cash_flow(self, date_range, currency=None): |
|
166 |
cash_flow = self.cash_flow(date_range, currency) |
|
167 |
return self._normalize_amount(cash_flow) |
|
168 |
||
169 |
74 |
#--- Properties |
170 |
75 |
@property |
171 |
76 |
def combined_display(self): |
Up to file-list core/model/budget.py:
| … | … | @@ -106,5 +106,5 @@ class BudgetList(list): |
106 |
106 |
|
107 |
107 |
def normal_amount_for_account(self, account, date_range, currency=None): |
108 |
108 |
budgeted_amount = self.amount_for_account(account, date_range, currency) |
109 |
return account. |
|
109 |
return account.normalize_amount(budgeted_amount) |
|
110 |
110 |
Up to file-list core/model/entry.py:
1 |
# -*- coding: utf-8 -*- |
|
2 |
# Created By: Virgil Dupras |
|
3 |
# Created On: 2010-07-29 |
|
4 |
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net) |
|
5 |
# |
|
6 |
# This software is licensed under the "HS" License as described in the "LICENSE" file, |
|
7 |
# which should be included with this package. The terms are also available at |
|
8 |
# http://www.hardcoded.net/licenses/hs_license |
|
9 |
||
10 |
import bisect |
|
11 |
from collections import Sequence |
|
12 |
from itertools import takewhile |
|
13 |
||
14 |
from hsutil.misc import flatten |
|
15 |
from .amount import convert_amount, same_currency |
|
16 |
||
17 |
class Entry(object): |
|
18 |
def __init__(self, split, amount, balance, reconciled_balance, balance_with_budget): |
|
19 |
self.split = split |
|
20 |
self.amount = amount |
|
21 |
self.balance = balance |
|
22 |
self.reconciled_balance = reconciled_balance |
|
23 |
self.balance_with_budget = balance_with_budget |
|
24 |
||
25 |
def __repr__(self): |
|
26 |
return '<Entry %r %r>' % (self.date, self.description) |
|
27 |
||
28 |
def change_amount(self, amount): |
|
29 |
assert len(self.splits) == 1 |
|
30 |
self.split.amount = amount |
|
31 |
other_split = self.splits[0] |
|
32 |
is_mct = False |
|
33 |
if not same_currency(amount, other_split.amount): |
|
34 |
is_asset = self.account is not None and self.account.is_balance_sheet_account() |
|
35 |
other_is_asset = other_split.account is not None and other_split.account.is_balance_sheet_account() |
|
36 |
if is_asset and other_is_asset: |
|
37 |
is_mct = True |
|
38 |
if is_mct: # don't touch other side unless we have a logical imbalance |
|
39 |
if self.split.is_on_same_side(other_split): |
|
40 |
other_split.amount *= -1 |
|
41 |
else: |
|
42 |
other_split.amount = -amount |
|
43 |
||
44 |
def normal_balance(self): |
|
45 |
is_credit = self.account is not None and self.account.is_credit_account() |
|
46 |
return -self.balance if is_credit else self.balance |
|
47 |
||
48 |
@property |
|
49 |
def account(self): |
|
50 |
return self.split.account |
|
51 |
||
52 |
@property |
|
53 |
def checkno(self): |
|
54 |
return self.transaction.checkno |
|
55 |
||
56 |
@property |
|
57 |
def date(self): |
|
58 |
return self.transaction.date |
|
59 |
||
60 |
@property |
|
61 |
def description(self): |
|
62 |
return self.transaction.description |
|
63 |
||
64 |
@property |
|
65 |
def payee(self): |
|
66 |
return self.transaction.payee |
|
67 |
||
68 |
@property |
|
69 |
def mtime(self): |
|
70 |
return self.transaction.mtime |
|
71 |
||
72 |
@property |
|
73 |
def splits(self): |
|
74 |
return [x for x in self.split.transaction.splits if x is not self.split] |
|
75 |
||
76 |
@property |
|
77 |
def transaction(self): |
|
78 |
return self.split.transaction |
|
79 |
||
80 |
@property |
|
81 |
def transfer(self): |
|
82 |
return [split.account for split in self.splits if split.account is not None] |
|
83 |
||
84 |
@property |
|
85 |
def reconciled(self): |
|
86 |
return self.split.reconciled |
|
87 |
||
88 |
@property |
|
89 |
def reconciliation_date(self): |
|
90 |
return self.split.reconciliation_date |
|
91 |
||
92 |
@property |
|
93 |
def reference(self): |
|
94 |
return self.split.reference |
|
95 |
||
96 |
||
97 |
class EntryList(Sequence): |
|
98 |
def __init__(self, account): |
|
99 |
self.account = account |
|
100 |
self._entries = [] |
|
101 |
self._date2entries = {} |
|
102 |
self._sorted_entry_dates = [] |
|
103 |
# the key for this dict is (date_range, currency) |
|
104 |
self._daterange2cashflow = {} |
|
105 |
||
106 |
def __getitem__(self, key): |
|
107 |
return self._entries.__getitem__(key) |
|
108 |
||
109 |
def __len__(self): |
|
110 |
return len(self._entries) |
|
111 |
||
112 |
#--- Private |
|
113 |
def _balance(self, balance_attr, date=None, currency=None): |
|
114 |
entry = self.last_entry(date) if date else self.last_entry() |
|
115 |
if entry: |
|
116 |
balance = getattr(entry, balance_attr) |
|
117 |
if currency: |
|
118 |
return convert_amount(balance, currency, date) |
|
119 |
else: |
|
120 |
return balance |
|
121 |
else: |
|
122 |
return 0 |
|
123 |
||
124 |
def _cash_flow(self, date_range, currency): |
|
125 |
cache = self._date2entries |
|
126 |
entries = flatten(cache[date] for date in date_range if date in cache) |
|
127 |
entries = (e for e in entries if not getattr(e.transaction, 'is_budget', False)) |
|
128 |
amounts = (convert_amount(e.amount, currency, e.date) for e in entries) |
|
129 |
return sum(amounts) |
|
130 |
||
131 |
#--- Public |
|
132 |
def add_entry(self, entry): |
|
133 |
# add_entry() calls must *always* be made in order |
|
134 |
self._entries.append(entry) |
|
135 |
date = entry.date |
|
136 |
try: |
|
137 |
self._date2entries[date].append(entry) |
|
138 |
except KeyError: |
|
139 |
self._date2entries[date] = [entry] |
|
140 |
if not self._sorted_entry_dates or self._sorted_entry_dates[-1] < date: |
|
141 |
self._sorted_entry_dates.append(date) |
|
142 |
||
143 |
def balance(self, date=None, currency=None): |
|
144 |
return self._balance('balance', date, currency=currency) |
|
145 |
||
146 |
def balance_of_reconciled(self, date=None, currency=None): |
|
147 |
return self._balance('reconciled_balance', date, currency=currency) |
|
148 |
||
149 |
def balance_with_budget(self, date=None, currency=None): |
|
150 |
return self._balance('balance_with_budget', date, currency=currency) |
|
151 |
||
152 |
def cash_flow(self, date_range, currency=None): |
|
153 |
currency = currency or self.account.currency |
|
154 |
cache_key = (date_range, currency) |
|
155 |
if cache_key not in self._daterange2cashflow: |
|
156 |
cash_flow = self._cash_flow(date_range, currency) |
|
157 |
self._daterange2cashflow[cache_key] = cash_flow |
|
158 |
return self._daterange2cashflow[cache_key] |
|
159 |
||
160 |
def clear(self, from_date): |
|
161 |
if from_date is None: |
|
162 |
self._entries = [] |
|
163 |
self._date2entries = {} |
|
164 |
self._daterange2cashflow = {} |
|
165 |
self._sorted_entry_dates = [] |
|
166 |
else: |
|
167 |
self._entries = list(takewhile(lambda e: e.date < from_date, self._entries)) |
|
168 |
index = bisect.bisect_left(self._sorted_entry_dates, from_date) |
|
169 |
for date in self._sorted_entry_dates[index:]: |
|
170 |
del self._date2entries[date] |
|
171 |
for date_range, currency in self._daterange2cashflow.keys(): |
|
172 |
if date_range.end >= from_date: |
|
173 |
del self._daterange2cashflow[(date_range, currency)] |
|
174 |
del self._sorted_entry_dates[index:] |
|
175 |
||
176 |
def last_entry(self, date=None): |
|
177 |
if self._entries: |
|
178 |
if date is None: |
|
179 |
return self._entries[-1] |
|
180 |
else: |
|
181 |
if date not in self._date2entries: # find the nearest smaller date |
|
182 |
index = bisect.bisect_right(self._sorted_entry_dates, date) - 1 |
|
183 |
if index < 0: |
|
184 |
return None |
|
185 |
date = self._sorted_entry_dates[index] |
|
186 |
return self._date2entries[date][-1] |
|
187 |
return None |
|
188 |
||
189 |
def normal_balance(self, date=None, currency=None): |
|
190 |
balance = self.balance(date=date, currency=currency) |
|
191 |
return self.account.normalize_amount(balance) |
|
192 |
||
193 |
def normal_balance_of_reconciled(self, date=None, currency=None): |
|
194 |
balance = self.balance_of_reconciled(date=date, currency=currency) |
|
195 |
return self.account.normalize_amount(balance) |
|
196 |
||
197 |
def normal_cash_flow(self, date_range, currency=None): |
|
198 |
cash_flow = self.cash_flow(date_range, currency) |
|
199 |
return self.account.normalize_amount(cash_flow) |
|
200 |
Up to file-list core/model/oven.py:
| … | … | @@ -14,9 +14,9 @@ from operator import attrgetter |
14 |
14 |
from hsutil.misc import flatten |
15 |
15 |
|
16 |
16 |
from .amount import convert_amount |
17 |
from .entry import Entry, EntryList |
|
17 |
18 |
from .budget import Budget, BudgetSpawn |
18 |
19 |
from .date import inc_month |
19 |
from .transaction import Entry |
|
20 |
20 |
|
21 |
21 |
class Oven(object): |
22 |
22 |
"""The Oven takes "raw data" from accounts and transactions and "cooks" calculated data out of |
| … | … | @@ -52,17 +52,18 @@ class Oven(object): |
52 |
52 |
account = split.account |
53 |
53 |
if account is None: |
54 |
54 |
return |
55 |
entries = account.entries |
|
55 |
56 |
amount = split.amount |
56 |
57 |
converted_amount = convert_amount(amount, account.currency, split.transaction.date) |
57 |
balance = account.balance() |
|
58 |
reconciled_balance = account.balance_of_reconciled() |
|
59 |
balance |
|
58 |
balance = entries.balance() |
|
59 |
reconciled_balance = entries.balance_of_reconciled() |
|
60 |
balance_with_budget = entries.balance_with_budget() |
|
60 |
61 |
balance_with_budget += converted_amount |
61 |
62 |
if not isinstance(split.transaction, BudgetSpawn): |
62 |
63 |
balance += converted_amount |
63 |
64 |
if split.reconciled: |
64 |
65 |
reconciled_balance += split.amount |
65 |
|
|
66 |
entries.add_entry(Entry(split, amount, balance, reconciled_balance, balance_with_budget)) |
|
66 |
67 |
|
67 |
68 |
def _cook_transaction(self, txn): |
68 |
69 |
for split in txn.splits: |
| … | … | @@ -80,7 +81,7 @@ class Oven(object): |
80 |
81 |
If we don't, we might end up in an infinite loop. |
81 |
82 |
""" |
82 |
83 |
for account in self._accounts: |
83 |
account. |
|
84 |
account.entries.clear(from_date) |
|
84 |
85 |
if from_date is None: |
85 |
86 |
self.transactions = [] |
86 |
87 |
else: |
Up to file-list core/model/transaction.py:
| … | … | @@ -309,83 +309,3 @@ class Split(object): |
309 |
309 |
def reconciled(self): |
310 |
310 |
return self.reconciliation_date is not None |
311 |
311 |
|
312 |
||
313 |
class Entry(object): |
|
314 |
def __init__(self, split, amount, balance, reconciled_balance, balance_with_budget): |
|
315 |
self.split = split |
|
316 |
self.amount = amount |
|
317 |
self.balance = balance |
|
318 |
self.reconciled_balance = reconciled_balance |
|
319 |
self.balance_with_budget = balance_with_budget |
|
320 |
||
321 |
def __repr__(self): |
|
322 |
return '<Entry %r %r>' % (self.date, self.description) |
|
323 |
||
324 |
def change_amount(self, amount): |
|
325 |
assert len(self.splits) == 1 |
|
326 |
self.split.amount = amount |
|
327 |
other_split = self.splits[0] |
|
328 |
is_mct = False |
|
329 |
if not same_currency(amount, other_split.amount): |
|
330 |
is_asset = self.account is not None and self.account.is_balance_sheet_account() |
|
331 |
other_is_asset = other_split.account is not None and other_split.account.is_balance_sheet_account() |
|
332 |
if is_asset and other_is_asset: |
|
333 |
is_mct = True |
|
334 |
if is_mct: # don't touch other side unless we have a logical imbalance |
|
335 |
if self.split.is_on_same_side(other_split): |
|
336 |
other_split.amount *= -1 |
|
337 |
else: |
|
338 |
other_split.amount = -amount |
|
339 |
||
340 |
def normal_balance(self): |
|
341 |
is_credit = self.account is not None and self.account.is_credit_account() |
|
342 |
return -self.balance if is_credit else self.balance |
|
343 |
||
344 |
@property |
|
345 |
def account(self): |
|
346 |
return self.split.account |
|
347 |
||
348 |
@property |
|
349 |
def checkno(self): |
|
350 |
return self.transaction.checkno |
|
351 |
||
352 |
@property |
|
353 |
def date(self): |
|
354 |
return self.transaction.date |
|
355 |
||
356 |
@property |
|
357 |
def description(self): |
|
358 |
return self.transaction.description |
|
359 |
||
360 |
@property |
|
361 |
def payee(self): |
|
362 |
return self.transaction.payee |
|
363 |
||
364 |
@property |
|
365 |
def mtime(self): |
|
366 |
return self.transaction.mtime |
|
367 |
||
368 |
@property |
|
369 |
def splits(self): |
|
370 |
return [x for x in self.split.transaction.splits if x is not self.split] |
|
371 |
||
372 |
@property |
|
373 |
def transaction(self): |
|
374 |
return self.split.transaction |
|
375 |
||
376 |
@property |
|
377 |
def transfer(self): |
|
378 |
return [split.account for split in self.splits if split.account is not None] |
|
379 |
||
380 |
@property |
|
381 |
def reconciled(self): |
|
382 |
return self.split.reconciled |
|
383 |
||
384 |
@property |
|
385 |
def reconciliation_date(self): |
|
386 |
return self.split.reconciliation_date |
|
387 |
||
388 |
@property |
|
389 |
def reference(self): |
|
390 |
return self.split.reference |
|
391 |
Up to file-list core/saver/qif.py:
| … | … | @@ -21,7 +21,7 @@ def save(filename, accounts): |
21 |
21 |
qif_account_type = 'Oth L' if account.type == AccountType.Liability else 'Bank' |
22 |
22 |
lines.append('!Account') |
23 |
23 |
lines.append('N%s' % account.name) |
24 |
lines.append('B%s' % format_amount_for_qif(account. |
|
24 |
lines.append('B%s' % format_amount_for_qif(account.entries.balance())) |
|
25 |
25 |
lines.append('T%s' % qif_account_type) |
26 |
26 |
lines.append('^') |
27 |
27 |
lines.append('!Type:%s' % qif_account_type) |
Up to file-list core/tests/gui/balance_sheet_test.py:
| … | … | @@ -491,7 +491,6 @@ class MultipleCurrencies(TestCase): |
491 |
491 |
self.add_entry('1/1/2007', 'USD entry', increase='50.00') |
492 |
492 |
self.add_entry('1/1/2008', 'USD entry', increase='80.00') |
493 |
493 |
self.add_entry('31/1/2008', 'USD entry', increase='20.00') |
494 |
eq_(self.mainwindow.shown_account.balance(), Amount(150, USD)) |
|
495 |
494 |
self.add_account('CAD account', currency=CAD, group_name='Group') |
496 |
495 |
self.mainwindow.show_account() |
497 |
496 |
self.add_entry('1/1/2008', 'USD entry', increase='100.00') |
Up to file-list core/tests/model/account_test.py:
5 |
5 |
# which should be included with this package. The terms are also available at |
6 |
6 |
# http://www.hardcoded.net/licenses/hs_license |
7 |
7 |
|
8 |
import xmlrpclib |
|
9 |
8 |
from datetime import date |
10 |
9 |
|
11 |
10 |
from hscommon.currency import USD, CAD |
12 |
11 |
|
13 |
12 |
from ..base import TestCase |
14 |
from ...model import currency |
|
15 |
13 |
from ...model.account import Account, Group, AccountList, AccountType |
16 |
14 |
from ...model.amount import Amount |
17 |
15 |
from ...model.date import MonthRange |
18 |
16 |
from ...model.oven import Oven |
19 |
from ...model.transaction import Transaction |
|
17 |
from ...model.transaction import Transaction |
|
20 |
18 |
from ...model.transaction_list import TransactionList |
21 |
19 |
|
22 |
20 |
class AccountComparison(TestCase): |
| … | … | @@ -76,16 +74,16 @@ class OneAccount(TestCase): |
76 |
74 |
oven.cook(date.min, date.max) |
77 |
75 |
|
78 |
76 |
def test_balance(self): |
79 |
self.assertEqual(self.account. |
|
77 |
self.assertEqual(self.account.entries.balance(date(2007, 12, 31)), Amount(20, USD)) |
|
80 |
78 |
|
81 |
79 |
# The balance is converted using the rate on the day the balance is |
82 |
80 |
# requested. |
83 |
self.assertEqual(self.account. |
|
81 |
self.assertEqual(self.account.entries.balance(date(2007, 12, 31), currency=CAD), Amount(20 * 1.1, CAD)) |
|
84 |
82 |
|
85 |
83 |
def test_cash_flow(self): |
86 |
84 |
range = MonthRange(date(2008, 1, 1)) |
87 |
self.assertEqual(self.account. |
|
85 |
self.assertEqual(self.account.entries.cash_flow(range), Amount(252, USD)) |
|
88 |
86 |
|
89 |
87 |
# Each entry is converted using the entry's day rate. |
90 |
self.assertEqual(self.account. |
|
88 |
self.assertEqual(self.account.entries.cash_flow(range, CAD), Amount(201.40, CAD)) |
|
91 |
89 |
