Added client, other fixes
- Switched from int to decimal.Decimal - Moved models to accounting.models
This commit is contained in:
parent
6f2c875c7b
commit
9656c39e91
5 changed files with 147 additions and 47 deletions
|
@ -7,6 +7,8 @@ from datetime import datetime
|
|||
from xml.etree import ElementTree
|
||||
from contextlib import contextmanager
|
||||
|
||||
from accounting.models import Account, Transaction, Posting, Amount
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
class Ledger:
|
||||
|
@ -112,6 +114,11 @@ class Ledger:
|
|||
return output
|
||||
|
||||
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'
|
||||
'{postings}')
|
||||
|
||||
|
@ -127,7 +134,7 @@ class Ledger:
|
|||
p=p,
|
||||
account=p.account + ' ' * (
|
||||
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')
|
||||
|
||||
with open(self.ledger_file, 'ab') as f:
|
||||
|
@ -212,50 +219,6 @@ class Ledger:
|
|||
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):
|
||||
import argparse
|
||||
if argv is None:
|
||||
|
|
83
accounting/client.py
Normal file
83
accounting/client.py
Normal 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
45
accounting/models.py
Normal 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)
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||
|
||||
from flask import json
|
||||
|
||||
from accounting import Amount, Transaction, Posting, Account
|
||||
from accounting.models import Amount, Transaction, Posting, Account
|
||||
|
||||
class AccountingEncoder(json.JSONEncoder):
|
||||
def default(self, o):
|
||||
|
@ -29,7 +29,7 @@ class AccountingEncoder(json.JSONEncoder):
|
|||
elif isinstance(o, Amount):
|
||||
return dict(
|
||||
__type__=o.__class__.__name__,
|
||||
amount=o.amount,
|
||||
amount=str(o.amount),
|
||||
symbol=o.symbol
|
||||
)
|
||||
elif isinstance(o, Exception):
|
||||
|
|
9
bin/client
Executable file
9
bin/client
Executable 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())
|
Loading…
Reference in a new issue