Added client, other fixes

- Switched from int to decimal.Decimal
- Moved models to accounting.models
This commit is contained in:
Joar Wandborg 2013-12-11 09:23:44 +01:00
parent 6f2c875c7b
commit 9656c39e91
5 changed files with 147 additions and 47 deletions

View file

@ -7,6 +7,8 @@ from datetime import datetime
from xml.etree import ElementTree from xml.etree import ElementTree
from contextlib import contextmanager from contextlib import contextmanager
from accounting.models import Account, Transaction, Posting, Amount
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
class Ledger: class Ledger:
@ -112,6 +114,11 @@ class Ledger:
return output return output
def add_transaction(self, transaction): def add_transaction(self, transaction):
'''
Writes a transaction to the ledger file by opening it in 'ab' mode and
writing a ledger transaction based on the Transaction instance in
``transaction``.
'''
transaction_template = ('\n{date} {t.payee}\n' transaction_template = ('\n{date} {t.payee}\n'
'{postings}') '{postings}')
@ -127,7 +134,7 @@ class Ledger:
p=p, p=p,
account=p.account + ' ' * ( account=p.account + ' ' * (
80 - (len(p.account) + len(p.amount.symbol) + 80 - (len(p.account) + len(p.amount.symbol) +
len(p.amount.amount) + 1 + 2) len(str(p.amount.amount)) + 1 + 2)
)) for p in transaction.postings])).encode('utf8') )) for p in transaction.postings])).encode('utf8')
with open(self.ledger_file, 'ab') as f: with open(self.ledger_file, 'ab') as f:
@ -212,50 +219,6 @@ class Ledger:
return entries return entries
class Transaction:
def __init__(self, date=None, payee=None, postings=None):
self.date = date
self.payee = payee
self.postings = postings
def __repr__(self):
return ('<{self.__class__.__name__} {date}' +
' {self.payee} {self.postings}').format(
self=self,
date=self.date.strftime('%Y-%m-%d'))
class Posting:
def __init__(self, account=None, amount=None):
self.account = account
self.amount = amount
def __repr__(self):
return ('<{self.__class__.__name__} "{self.account}"' +
' {self.amount}>').format(self=self)
class Amount:
def __init__(self, amount=None, symbol=None):
self.amount = amount
self.symbol = symbol
def __repr__(self):
return ('<{self.__class__.__name__} {self.symbol}' +
' {self.amount}>').format(self=self)
class Account:
def __init__(self, name=None, amounts=None, accounts=None):
self.name = name
self.amounts = amounts
self.accounts = accounts
def __repr__(self):
return ('<{self.__class__.__name__} "{self.name}" {self.amounts}' +
' {self.accounts}>').format(self=self)
def main(argv=None): def main(argv=None):
import argparse import argparse
if argv is None: if argv is None:

83
accounting/client.py Normal file
View file

@ -0,0 +1,83 @@
import sys
import argparse
import json
import logging
from datetime import datetime
from decimal import Decimal
import requests
from accounting.models import Transaction, Posting, Amount
from accounting.transport import AccountingDecoder, AccountingEncoder
# TODO: Client should be a class
HOST = None
def insert_paypal_transaction(amount):
t = Transaction(
date=datetime.today(),
payee='PayPal donation',
postings=[
Posting(account='Income:Donations:PayPal',
amount=Amount(symbol='$', amount=-amount)),
Posting(account='Assets:Checking',
amount=Amount(symbol='$', amount=amount))
]
)
response = requests.post(HOST + '/transaction',
headers={'Content-Type': 'application/json'},
data=json.dumps({'transactions': [t]},
cls=AccountingEncoder))
print(response.json(cls=AccountingDecoder))
def get_balance():
response = requests.get(HOST + '/balance')
balance = response.json(cls=AccountingDecoder)
_recurse_accounts(balance['balance_report'])
def _recurse_accounts(accounts, level=0):
for account in accounts:
print(' ' * level + ' + {account.name}'.format(account=account) +
' ' + '-' * (80 - len(str(account.name)) - level))
for amount in account.amounts:
print(' ' * level + ' {amount.symbol} {amount.amount}'.format(
amount=amount))
_recurse_accounts(account.accounts, level+1)
def main(argv=None, prog=None):
global HOST
if argv is None:
prog = sys.argv[0]
argv = sys.argv[1:]
parser = argparse.ArgumentParser(prog=prog)
parser.add_argument('-p', '--paypal', type=Decimal)
parser.add_argument('-v', '--verbosity',
default='WARNING',
help=('Filter logging output. Possible values:' +
' CRITICAL, ERROR, WARNING, INFO, DEBUG'))
parser.add_argument('-b', '--balance', action='store_true')
parser.add_argument('--host', default='http://localhost:5000')
args = parser.parse_args(argv)
HOST = args.host
logging.basicConfig(level=getattr(logging, args.verbosity))
if args.paypal:
insert_paypal_transaction(args.paypal)
elif args.balance:
get_balance()
if __name__ == '__main__':
sys.exit(main())

45
accounting/models.py Normal file
View file

@ -0,0 +1,45 @@
from decimal import Decimal
class Transaction:
def __init__(self, date=None, payee=None, postings=None):
self.date = date
self.payee = payee
self.postings = postings
def __repr__(self):
return ('<{self.__class__.__name__} {date}' +
' {self.payee} {self.postings}').format(
self=self,
date=self.date.strftime('%Y-%m-%d'))
class Posting:
def __init__(self, account=None, amount=None):
self.account = account
self.amount = amount
def __repr__(self):
return ('<{self.__class__.__name__} "{self.account}"' +
' {self.amount}>').format(self=self)
class Amount:
def __init__(self, amount=None, symbol=None):
self.amount = Decimal(amount)
self.symbol = symbol
def __repr__(self):
return ('<{self.__class__.__name__} {self.symbol}' +
' {self.amount}>').format(self=self)
class Account:
def __init__(self, name=None, amounts=None, accounts=None):
self.name = name
self.amounts = amounts
self.accounts = accounts
def __repr__(self):
return ('<{self.__class__.__name__} "{self.name}" {self.amounts}' +
' {self.accounts}>').format(self=self)

View file

@ -2,7 +2,7 @@ from datetime import datetime
from flask import json from flask import json
from accounting import Amount, Transaction, Posting, Account from accounting.models import Amount, Transaction, Posting, Account
class AccountingEncoder(json.JSONEncoder): class AccountingEncoder(json.JSONEncoder):
def default(self, o): def default(self, o):
@ -29,7 +29,7 @@ class AccountingEncoder(json.JSONEncoder):
elif isinstance(o, Amount): elif isinstance(o, Amount):
return dict( return dict(
__type__=o.__class__.__name__, __type__=o.__class__.__name__,
amount=o.amount, amount=str(o.amount),
symbol=o.symbol symbol=o.symbol
) )
elif isinstance(o, Exception): elif isinstance(o, Exception):

9
bin/client Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env python3
import sys
sys.path.append('./')
from accounting.client import main
if __name__ == '__main__':
sys.exit(main())