Added transaction IDs and metadata
This commit is contained in:
parent
266aa455f9
commit
032175cd26
5 changed files with 94 additions and 29 deletions
|
@ -119,23 +119,37 @@ class Ledger:
|
||||||
writing a ledger transaction based on the Transaction instance in
|
writing a ledger transaction based on the Transaction instance in
|
||||||
``transaction``.
|
``transaction``.
|
||||||
'''
|
'''
|
||||||
|
if not transaction.metadata.get('Id'):
|
||||||
|
transaction.generate_id()
|
||||||
|
|
||||||
transaction_template = ('\n{date} {t.payee}\n'
|
transaction_template = ('\n{date} {t.payee}\n'
|
||||||
|
'{tags}'
|
||||||
'{postings}')
|
'{postings}')
|
||||||
|
|
||||||
|
metadata_template = ' ;{0}: {1}\n'
|
||||||
|
|
||||||
|
# TODO: Generate metadata for postings
|
||||||
posting_template = (' {account} {p.amount.symbol}'
|
posting_template = (' {account} {p.amount.symbol}'
|
||||||
' {p.amount.amount}\n')
|
' {p.amount.amount}\n')
|
||||||
|
|
||||||
output = b''
|
output = b''
|
||||||
|
|
||||||
|
# XXX: Even I hardly understands what this does, however I indent it it
|
||||||
|
# stays unreadable.
|
||||||
output += transaction_template.format(
|
output += transaction_template.format(
|
||||||
date=transaction.date.strftime('%Y-%m-%d'),
|
date=transaction.date.strftime('%Y-%m-%d'),
|
||||||
t=transaction,
|
t=transaction,
|
||||||
|
tags=''.join([
|
||||||
|
metadata_template.format(k, v) \
|
||||||
|
for k, v in transaction.metadata.items()]),
|
||||||
postings=''.join([posting_template.format(
|
postings=''.join([posting_template.format(
|
||||||
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(str(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:
|
||||||
f.write(output)
|
f.write(output)
|
||||||
|
@ -209,12 +223,41 @@ class Ledger:
|
||||||
symbol = posting.find(
|
symbol = posting.find(
|
||||||
'./post-amount/amount/commodity/symbol').text
|
'./post-amount/amount/commodity/symbol').text
|
||||||
|
|
||||||
|
# Get the posting metadata
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
|
values = posting.findall('./metadata/value')
|
||||||
|
if values:
|
||||||
|
for value in values:
|
||||||
|
key = value.get('key')
|
||||||
|
value = value.find('./string').text
|
||||||
|
|
||||||
|
_log.debug('metadata: %s: %s', key, value)
|
||||||
|
|
||||||
|
metadata.update({key: value})
|
||||||
|
|
||||||
postings.append(
|
postings.append(
|
||||||
Posting(account=account,
|
Posting(account=account,
|
||||||
|
metadata=metadata,
|
||||||
amount=Amount(amount=amount, symbol=symbol)))
|
amount=Amount(amount=amount, symbol=symbol)))
|
||||||
|
|
||||||
|
# Get the transaction metadata
|
||||||
|
metadata = {}
|
||||||
|
|
||||||
|
values = transaction.findall('./metadata/value')
|
||||||
|
if values:
|
||||||
|
for value in values:
|
||||||
|
key = value.get('key')
|
||||||
|
value = value.find('./string').text
|
||||||
|
|
||||||
|
_log.debug('metadata: %s: %s', key, value)
|
||||||
|
|
||||||
|
metadata.update({key: value})
|
||||||
|
|
||||||
|
# Add a Transaction instance to the list
|
||||||
entries.append(
|
entries.append(
|
||||||
Transaction(date=date, payee=payee, postings=postings))
|
Transaction(date=date, payee=payee, postings=postings,
|
||||||
|
metadata=metadata))
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,23 @@ def _recurse_accounts(accounts, level=0):
|
||||||
_recurse_accounts(account.accounts, level+1)
|
_recurse_accounts(account.accounts, level+1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_register():
|
||||||
|
response = requests.get(HOST + '/register')
|
||||||
|
|
||||||
|
register = response.json(cls=AccountingDecoder)
|
||||||
|
|
||||||
|
for transaction in register['register_report']:
|
||||||
|
print('{date} {t.payee:.<69}'.format(
|
||||||
|
date=transaction.date.strftime('%Y-%m-%d'),
|
||||||
|
t=transaction))
|
||||||
|
|
||||||
|
for posting in transaction.postings:
|
||||||
|
print(' ' + posting.account +
|
||||||
|
' ' * (80 - len(posting.account) - len(posting.amount.symbol) -
|
||||||
|
len(str(posting.amount.amount)) - 1 - 1) +
|
||||||
|
posting.amount.symbol + ' ' + str(posting.amount.amount))
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None, prog=None):
|
def main(argv=None, prog=None):
|
||||||
global HOST
|
global HOST
|
||||||
if argv is None:
|
if argv is None:
|
||||||
|
@ -62,11 +79,12 @@ def main(argv=None, prog=None):
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog=prog)
|
parser = argparse.ArgumentParser(prog=prog)
|
||||||
parser.add_argument('-p', '--paypal', type=Decimal)
|
parser.add_argument('-p', '--paypal', type=Decimal)
|
||||||
|
parser.add_argument('-b', '--balance', action='store_true')
|
||||||
|
parser.add_argument('-r', '--register', action='store_true')
|
||||||
parser.add_argument('-v', '--verbosity',
|
parser.add_argument('-v', '--verbosity',
|
||||||
default='WARNING',
|
default='WARNING',
|
||||||
help=('Filter logging output. Possible values:' +
|
help=('Filter logging output. Possible values:' +
|
||||||
' CRITICAL, ERROR, WARNING, INFO, DEBUG'))
|
' CRITICAL, ERROR, WARNING, INFO, DEBUG'))
|
||||||
parser.add_argument('-b', '--balance', action='store_true')
|
|
||||||
parser.add_argument('--host', default='http://localhost:5000')
|
parser.add_argument('--host', default='http://localhost:5000')
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
@ -78,6 +96,8 @@ def main(argv=None, prog=None):
|
||||||
insert_paypal_transaction(args.paypal)
|
insert_paypal_transaction(args.paypal)
|
||||||
elif args.balance:
|
elif args.balance:
|
||||||
get_balance()
|
get_balance()
|
||||||
|
elif args.register:
|
||||||
|
get_register()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
|
import uuid
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
class Transaction:
|
class Transaction:
|
||||||
def __init__(self, date=None, payee=None, postings=None):
|
def __init__(self, date=None, payee=None, postings=None, metadata=None,
|
||||||
|
_generate_id=False):
|
||||||
self.date = date
|
self.date = date
|
||||||
self.payee = payee
|
self.payee = payee
|
||||||
self.postings = postings
|
self.postings = postings
|
||||||
|
self.metadata = metadata if metadata is not None else {}
|
||||||
|
|
||||||
|
if _generate_id:
|
||||||
|
self.generate_id()
|
||||||
|
|
||||||
|
def generate_id(self):
|
||||||
|
self.metadata.update({'Id': uuid.uuid4()})
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('<{self.__class__.__name__} {date}' +
|
return ('<{self.__class__.__name__} {date}' +
|
||||||
|
@ -15,9 +24,10 @@ class Transaction:
|
||||||
|
|
||||||
|
|
||||||
class Posting:
|
class Posting:
|
||||||
def __init__(self, account=None, amount=None):
|
def __init__(self, account=None, amount=None, metadata=None):
|
||||||
self.account = account
|
self.account = account
|
||||||
self.amount = amount
|
self.amount = amount
|
||||||
|
self.metadata = metadata if metadata is not None else {}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('<{self.__class__.__name__} "{self.account}"' +
|
return ('<{self.__class__.__name__} "{self.account}"' +
|
||||||
|
|
|
@ -18,13 +18,15 @@ class AccountingEncoder(json.JSONEncoder):
|
||||||
__type__=o.__class__.__name__,
|
__type__=o.__class__.__name__,
|
||||||
date=o.date.strftime('%Y-%m-%d'),
|
date=o.date.strftime('%Y-%m-%d'),
|
||||||
payee=o.payee,
|
payee=o.payee,
|
||||||
postings=o.postings
|
postings=o.postings,
|
||||||
|
metadata=o.metadata
|
||||||
)
|
)
|
||||||
elif isinstance(o, Posting):
|
elif isinstance(o, Posting):
|
||||||
return dict(
|
return dict(
|
||||||
__type__=o.__class__.__name__,
|
__type__=o.__class__.__name__,
|
||||||
account=o.account,
|
account=o.account,
|
||||||
amount=o.amount,
|
amount=o.amount,
|
||||||
|
metadata=o.metadata
|
||||||
)
|
)
|
||||||
elif isinstance(o, Amount):
|
elif isinstance(o, Amount):
|
||||||
return dict(
|
return dict(
|
||||||
|
|
|
@ -1,26 +1,29 @@
|
||||||
|
|
||||||
2010/01/01 Kindly T. Donor
|
2010/01/01 Kindly T. Donor
|
||||||
|
;Id: Ids can be anything
|
||||||
Income:Foo:Donation $-100.00
|
Income:Foo:Donation $-100.00
|
||||||
;Invoice: Projects/Foo/Invoices/Invoice20100101.pdf
|
;Invoice: Projects/Foo/Invoices/Invoice20100101.pdf
|
||||||
Assets:Checking $100.00
|
Assets:Checking $100.00
|
||||||
|
|
||||||
|
|
||||||
2011/03/15 Another J. Donor
|
2011/03/15 Another J. Donor
|
||||||
|
;Id: but mind you if they collide.
|
||||||
Income:Foo:Donation $-400.00
|
Income:Foo:Donation $-400.00
|
||||||
;Approval: Projects/Foo/earmark-record.txt
|
;Approval: Projects/Foo/earmark-record.txt
|
||||||
Assets:Checking $400.00
|
Assets:Checking $400.00
|
||||||
|
|
||||||
2011/04/20 (1) Baz Hosting Services, LLC
|
2011/04/20 (1) Baz Hosting Services, LLC
|
||||||
|
;Id: always make sure your IDs are unique
|
||||||
Expenses:Foo:Hosting $250.00
|
Expenses:Foo:Hosting $250.00
|
||||||
;Receipt: Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf
|
;Receipt: Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf
|
||||||
Assets:Checking $-250.00
|
Assets:Checking $-250.00
|
||||||
|
|
||||||
2011/05/10 Donation to General Fund
|
2011/05/10 Donation to General Fund
|
||||||
|
;Id: if you have two transactions with the same ID, bad things may happen
|
||||||
Income:Donation $-50.00
|
Income:Donation $-50.00
|
||||||
;Invoice: Financial/Invoices/Invoice20110510.pdf
|
;Invoice: Financial/Invoices/Invoice20110510.pdf
|
||||||
Assets:Checking $50.00
|
Assets:Checking $50.00
|
||||||
|
|
||||||
2011/04/20 (2) Baz Hosting Services, LLC
|
2011/04/20 (2) Baz Hosting Services, LLC
|
||||||
|
;Id: this is probably unique
|
||||||
Expenses:Blah:Hosting $250.00
|
Expenses:Blah:Hosting $250.00
|
||||||
;Receipt: Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf
|
;Receipt: Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf
|
||||||
;Invoice: Projects/Blah/Expenses/hosting/april-invoice.pdf
|
;Invoice: Projects/Blah/Expenses/hosting/april-invoice.pdf
|
||||||
|
@ -28,29 +31,16 @@
|
||||||
;Statement: Financial/BankStuff/bank-statement.pdf
|
;Statement: Financial/BankStuff/bank-statement.pdf
|
||||||
|
|
||||||
2011-04-25 A transaction with ISO date
|
2011-04-25 A transaction with ISO date
|
||||||
|
;Id: I'm a snowflake!
|
||||||
Income:Karma KARMA-10
|
Income:Karma KARMA-10
|
||||||
Assets:Karma Account KARMA 10
|
Assets:Karma Account KARMA 10
|
||||||
|
|
||||||
2013-01-01 Kindly T. Donor
|
2013-12-11 PayPal donation
|
||||||
Income:Foo:Donation $ -100
|
;Id: bd7f6781-fdc6-4111-b3ad-bee2247e426d
|
||||||
Assets:Checking $ 100
|
Income:Donations:PayPal $ -20.17
|
||||||
|
Assets:Checking $ 20.17
|
||||||
2013-03-15 Another J. Donor
|
|
||||||
Income:Foo:Donation $ -400
|
|
||||||
Assets:Checking $ 400
|
|
||||||
|
|
||||||
2013-12-11 PayPal donation
|
2013-12-11 PayPal donation
|
||||||
Income:Donations:PayPal $ -100
|
;Id: 31048b9d-a5b6-41d7-951a-e7128e7c53c0
|
||||||
Assets:Checking $ 100
|
Income:Donations:PayPal $ -20.18
|
||||||
|
Assets:Checking $ 20.18
|
||||||
2013-12-11 PayPal donation
|
|
||||||
Income:Donations:PayPal $ -1000
|
|
||||||
Assets:Checking $ 1000
|
|
||||||
|
|
||||||
2013-12-11 PayPal donation
|
|
||||||
Income:Donations:PayPal $ -0.25
|
|
||||||
Assets:Checking $ 0.25
|
|
||||||
|
|
||||||
2013-12-11 PayPal donation
|
|
||||||
Income:Donations:PayPal $ -0.252
|
|
||||||
Assets:Checking $ 0.252
|
|
||||||
|
|
Loading…
Reference in a new issue