Skip to content

Commit

Permalink
Merge pull request #96 from /issues/74
Browse files Browse the repository at this point in the history
Issues/74
  • Loading branch information
jantman committed Jul 8, 2017
2 parents 0327e7c + 66b90a5 commit c7a6c38
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Changelog
* Refactor form generation in UI to use new FormBuilder javascript class (DRY).
* Fix date-sensitive acceptance test.
* `Issue #87 <https://github.com/jantman/biweeklybudget/issues/87>`_ - Add fuel log / fuel economy tracking.
* `Issue #88 <https://github.com/jantman/biweeklybudget/issues/88>`_ - Add tracking of cost for Projects and Bills of Materials (BoM) for them.
* `Issue #74 <https://github.com/jantman/biweeklybudget/issues/74>`_ - Another attempt at working over-balance notification.

0.1.2 (2017-05-28)
------------------
Expand Down
55 changes: 37 additions & 18 deletions biweeklybudget/flaskapp/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,13 @@

import logging
from sqlalchemy import func
from sqlalchemy.sql.expression import null
from locale import currency

from biweeklybudget.db import db_session
from biweeklybudget.utils import dtnow
from biweeklybudget.models.account import Account
from biweeklybudget.models.budget_model import Budget
from biweeklybudget.models.ofx_transaction import OFXTransaction
from biweeklybudget.models.transaction import Transaction
from biweeklybudget.biweeklypayperiod import BiweeklyPayPeriod

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -90,6 +88,24 @@ def budget_account_sum(sess=None):
sum += float(acct.balance.ledger)
return sum

@staticmethod
def budget_account_unreconciled(sess=None):
"""
Return the sum of unreconciled txns for all is_budget_source accounts.
:return: Combined unreconciled amount of all budget source accounts
:rtype: float
"""
if sess is None:
sess = db_session
sum = 0
for acct in sess.query(Account).filter(
Account.is_budget_source.__eq__(True),
Account.is_active.__eq__(True)
):
sum += acct.unreconciled_sum
return sum

@staticmethod
def standing_budgets_sum(sess=None):
"""
Expand Down Expand Up @@ -122,16 +138,10 @@ def pp_sum(sess=None):
sess = db_session
pp = BiweeklyPayPeriod.period_for_date(dtnow(), sess)
allocated = float(pp.overall_sums['allocated'])
rec = 0.0
t = pp.filter_query(
sess.query(Transaction),
Transaction.date
).filter(Transaction.reconcile.__ne__(null()))
for txn in t:
rec += float(txn.actual_amount)
logger.debug('PayPeriod=%s; allocated=%s; reconciled=%s',
pp, allocated, rec)
return allocated - rec
spent = float(pp.overall_sums['spent'])
logger.debug('PayPeriod=%s; allocated=%s; spent=%s',
pp, allocated, spent)
return allocated - spent

@staticmethod
def num_unreconciled_ofx(sess=None):
Expand Down Expand Up @@ -167,21 +177,30 @@ def get_notifications():
a)
})
accounts_bal = NotificationsController.budget_account_sum()
unrec_amt = NotificationsController.budget_account_unreconciled()
standing_bal = NotificationsController.standing_budgets_sum()
curr_pp = NotificationsController.pp_sum()
if accounts_bal < (standing_bal + curr_pp):
logger.info('accounts_bal=%s standing_bal=%s curr_pp=%s unrec=%s',
accounts_bal, standing_bal, curr_pp, unrec_amt)
if accounts_bal < (standing_bal + curr_pp + unrec_amt):
res.append({
'classes': 'alert alert-danger',
'content': 'Combined balance of all <a href="/accounts">'
'budget-funding accounts</a> '
'(%s) is less than balance of all <a href='
'"/budgets">standing budgets</a> (%s) plus the '
'current pay period allocated and unreconciled '
'transactions (%s)!'
'(%s) is less than all allocated funds total of '
'%s (%s <a href="/budgets">standing budgets</a>; '
'%s <a href="/pay_period_for">current pay '
'period remaining</a>; %s <a href="/reconcile">'
'unreconciled</a>)!'
'' % (
currency(accounts_bal, grouping=True),
currency(
(standing_bal + curr_pp + unrec_amt),
grouping=True
),
currency(standing_bal, grouping=True),
currency(curr_pp, grouping=True)
currency(curr_pp, grouping=True),
currency(unrec_amt, grouping=True)
)
})
unreconciled_ofx = NotificationsController.num_unreconciled_ofx()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ def test_2_confirm_pp(self, testdb):
assert stand_bal == 132939.07
pp_bal = NotificationsController.pp_sum(testdb)
# floating point awfulness
assert "%.2f" % pp_bal == '444.42'
assert "%.2f" % pp_bal == '222.20'
unrec_amt = NotificationsController.budget_account_unreconciled(testdb)
assert unrec_amt == -333.33

def test_3_notification(self, base_url, selenium):
self.baseurl = base_url
Expand All @@ -247,15 +249,19 @@ def test_3_notification(self, base_url, selenium):
"//div[@id='notifications-row']/div/div"
)[1]
assert div.text == 'Combined balance of all budget-funding accounts ' \
'($12,889.24) is less than balance of all ' \
'standing budgets ($132,939.07) plus the current ' \
'pay period allocated and unreconciled ' \
'transactions ($444.42)!'
'($12,889.24) is less than all allocated funds ' \
'total of $132,827.94 ($132,939.07 standing ' \
'budgets; $222.20 current pay period remaining; ' \
'-$333.33 unreconciled)!'
a = div.find_elements_by_tag_name('a')
assert self.relurl(a[0].get_attribute('href')) == '/accounts'
assert a[0].text == 'budget-funding accounts'
assert self.relurl(a[1].get_attribute('href')) == '/budgets'
assert a[1].text == 'standing budgets'
assert self.relurl(a[2].get_attribute('href')) == '/pay_period_for'
assert a[2].text == 'current pay period remaining'
assert self.relurl(a[3].get_attribute('href')) == '/reconcile'
assert a[3].text == 'unreconciled'


@pytest.mark.acceptance
Expand Down Expand Up @@ -317,7 +323,9 @@ def test_1_confirm_pp(self, testdb):
assert stand_bal == 11099.85
pp_bal = NotificationsController.pp_sum(testdb)
# floating point awfulness
assert "%.2f" % pp_bal == '34444.42'
assert "%.2f" % pp_bal == '222.20'
unrec_amt = NotificationsController.budget_account_unreconciled(testdb)
assert unrec_amt == 33666.67

def test_2_notification(self, base_url, selenium):
self.baseurl = base_url
Expand All @@ -326,12 +334,16 @@ def test_2_notification(self, base_url, selenium):
"//div[@id='notifications-row']/div/div"
)[1]
assert div.text == 'Combined balance of all budget-funding accounts ' \
'($12,889.24) is less than balance of all ' \
'standing budgets ($11,099.85) plus the current ' \
'pay period allocated and unreconciled ' \
'transactions ($34,444.42)!'
'($12,889.24) is less than all allocated funds ' \
'total of $44,988.72 ($11,099.85 standing ' \
'budgets; $222.20 current pay period remaining; ' \
'$33,666.67 unreconciled)!'
a = div.find_elements_by_tag_name('a')
assert self.relurl(a[0].get_attribute('href')) == '/accounts'
assert a[0].text == 'budget-funding accounts'
assert self.relurl(a[1].get_attribute('href')) == '/budgets'
assert a[1].text == 'standing budgets'
assert self.relurl(a[2].get_attribute('href')) == '/pay_period_for'
assert a[2].text == 'current pay period remaining'
assert self.relurl(a[3].get_attribute('href')) == '/reconcile'
assert a[3].text == 'unreconciled'
18 changes: 15 additions & 3 deletions biweeklybudget/tests/unit/flaskapp/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,16 @@ def test_get_notifications_no_stale(self):
num_stale_accounts=DEFAULT,
budget_account_sum=DEFAULT,
standing_budgets_sum=DEFAULT,
num_unreconciled_ofx=DEFAULT
num_unreconciled_ofx=DEFAULT,
budget_account_unreconciled=DEFAULT,
pp_sum=DEFAULT
) as mocks:
mocks['num_stale_accounts'].return_value = 0
mocks['budget_account_sum'].return_value = 1000
mocks['standing_budgets_sum'].return_value = 1
mocks['num_unreconciled_ofx'].return_value = 0
mocks['budget_account_unreconciled'].return_value = 0
mocks['pp_sum'].return_value = 0
res = NotificationsController.get_notifications()
assert res == []

Expand All @@ -89,12 +93,16 @@ def test_get_notifications_one_stale(self):
num_stale_accounts=DEFAULT,
budget_account_sum=DEFAULT,
standing_budgets_sum=DEFAULT,
num_unreconciled_ofx=DEFAULT
num_unreconciled_ofx=DEFAULT,
budget_account_unreconciled=DEFAULT,
pp_sum=DEFAULT
) as mocks:
mocks['num_stale_accounts'].return_value = 1
mocks['budget_account_sum'].return_value = 1000
mocks['standing_budgets_sum'].return_value = 1
mocks['num_unreconciled_ofx'].return_value = 28
mocks['budget_account_unreconciled'].return_value = 0
mocks['pp_sum'].return_value = 0
res = NotificationsController.get_notifications()
assert res == [
{
Expand All @@ -115,12 +123,16 @@ def test_get_notifications_three_stale(self):
num_stale_accounts=DEFAULT,
budget_account_sum=DEFAULT,
standing_budgets_sum=DEFAULT,
num_unreconciled_ofx=DEFAULT
num_unreconciled_ofx=DEFAULT,
budget_account_unreconciled=DEFAULT,
pp_sum=DEFAULT
) as mocks:
mocks['num_stale_accounts'].return_value = 3
mocks['budget_account_sum'].return_value = 1000
mocks['standing_budgets_sum'].return_value = 1
mocks['num_unreconciled_ofx'].return_value = 28
mocks['budget_account_unreconciled'].return_value = 0
mocks['pp_sum'].return_value = 0
res = NotificationsController.get_notifications()
assert res == [
{
Expand Down

0 comments on commit c7a6c38

Please sign in to comment.