diff --git a/accounting/__init__.py b/accounting/__init__.py index d9cbaa256cd5462a1a9211d1498221b16b1116ba..b2f8aa1d0be2e4c2bff1680dae5df2794aa0951a 100644 --- a/accounting/__init__.py +++ b/accounting/__init__.py @@ -119,23 +119,37 @@ class Ledger: writing a ledger transaction based on the Transaction instance in ``transaction``. ''' + if not transaction.metadata.get('Id'): + transaction.generate_id() + transaction_template = ('\n{date} {t.payee}\n' + '{tags}' '{postings}') + metadata_template = ' ;{0}: {1}\n' + + # TODO: Generate metadata for postings posting_template = (' {account} {p.amount.symbol}' ' {p.amount.amount}\n') output = b'' + # XXX: Even I hardly understands what this does, however I indent it it + # stays unreadable. output += transaction_template.format( date=transaction.date.strftime('%Y-%m-%d'), t=transaction, + tags=''.join([ + metadata_template.format(k, v) \ + for k, v in transaction.metadata.items()]), postings=''.join([posting_template.format( p=p, account=p.account + ' ' * ( 80 - (len(p.account) + len(p.amount.symbol) + 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: f.write(output) @@ -209,12 +223,41 @@ class Ledger: symbol = posting.find( './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( Posting(account=account, + metadata=metadata, 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( - Transaction(date=date, payee=payee, postings=postings)) + Transaction(date=date, payee=payee, postings=postings, + metadata=metadata)) return entries diff --git a/accounting/client.py b/accounting/client.py index ef5d62b11cc443978d00364d2c14886ebaedb942..bcaf5642447b71ae95e2c6fcd8d379286f89b9ff 100644 --- a/accounting/client.py +++ b/accounting/client.py @@ -54,6 +54,23 @@ def _recurse_accounts(accounts, level=0): _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): global HOST if argv is None: @@ -62,11 +79,12 @@ def main(argv=None, prog=None): parser = argparse.ArgumentParser(prog=prog) 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', 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) @@ -78,6 +96,8 @@ def main(argv=None, prog=None): insert_paypal_transaction(args.paypal) elif args.balance: get_balance() + elif args.register: + get_register() if __name__ == '__main__': sys.exit(main()) diff --git a/accounting/models.py b/accounting/models.py index f4aa9f290531b19e427d087679b51688de06ce13..4e37984769865b1cb7c8399db859ae04fa76e346 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -1,11 +1,20 @@ +import uuid from decimal import Decimal 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.payee = payee 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): return ('<{self.__class__.__name__} {date}' + @@ -15,9 +24,10 @@ class Transaction: class Posting: - def __init__(self, account=None, amount=None): + def __init__(self, account=None, amount=None, metadata=None): self.account = account self.amount = amount + self.metadata = metadata if metadata is not None else {} def __repr__(self): return ('<{self.__class__.__name__} "{self.account}"' + diff --git a/accounting/transport.py b/accounting/transport.py index 0ddb5c1dcac22c1481cd32bbf07ff71c195a2b28..4e0e98e9d12ef7b297de2fdc5683dd5d7ed3bccd 100644 --- a/accounting/transport.py +++ b/accounting/transport.py @@ -18,13 +18,15 @@ class AccountingEncoder(json.JSONEncoder): __type__=o.__class__.__name__, date=o.date.strftime('%Y-%m-%d'), payee=o.payee, - postings=o.postings + postings=o.postings, + metadata=o.metadata ) elif isinstance(o, Posting): return dict( __type__=o.__class__.__name__, account=o.account, amount=o.amount, + metadata=o.metadata ) elif isinstance(o, Amount): return dict( diff --git a/non-profit-test-data.ledger b/non-profit-test-data.ledger index 8d12f0a1cbeb76e512ebd590fb4c49233236b577..ebf48a1926088114a06cc9877ded5607184b8d15 100644 --- a/non-profit-test-data.ledger +++ b/non-profit-test-data.ledger @@ -1,26 +1,29 @@ - 2010/01/01 Kindly T. Donor + ;Id: Ids can be anything Income:Foo:Donation $-100.00 ;Invoice: Projects/Foo/Invoices/Invoice20100101.pdf Assets:Checking $100.00 - 2011/03/15 Another J. Donor + ;Id: but mind you if they collide. Income:Foo:Donation $-400.00 ;Approval: Projects/Foo/earmark-record.txt Assets:Checking $400.00 2011/04/20 (1) Baz Hosting Services, LLC + ;Id: always make sure your IDs are unique Expenses:Foo:Hosting $250.00 ;Receipt: Projects/Foo/Expenses/hosting/AprilHostingReceipt.pdf Assets:Checking $-250.00 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 ;Invoice: Financial/Invoices/Invoice20110510.pdf Assets:Checking $50.00 2011/04/20 (2) Baz Hosting Services, LLC + ;Id: this is probably unique Expenses:Blah:Hosting $250.00 ;Receipt: Projects/Blah/Expenses/hosting/AprilHostingReceipt.pdf ;Invoice: Projects/Blah/Expenses/hosting/april-invoice.pdf @@ -28,29 +31,16 @@ ;Statement: Financial/BankStuff/bank-statement.pdf 2011-04-25 A transaction with ISO date + ;Id: I'm a snowflake! Income:Karma KARMA-10 Assets:Karma Account KARMA 10 -2013-01-01 Kindly T. Donor - Income:Foo:Donation $ -100 - Assets:Checking $ 100 - -2013-03-15 Another J. Donor - Income:Foo:Donation $ -400 - Assets:Checking $ 400 - -2013-12-11 PayPal donation - Income:Donations:PayPal $ -100 - Assets:Checking $ 100 - -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 + ;Id: bd7f6781-fdc6-4111-b3ad-bee2247e426d + Income:Donations:PayPal $ -20.17 + Assets:Checking $ 20.17 2013-12-11 PayPal donation - Income:Donations:PayPal $ -0.252 - Assets:Checking $ 0.252 + ;Id: 31048b9d-a5b6-41d7-951a-e7128e7c53c0 + Income:Donations:PayPal $ -20.18 + Assets:Checking $ 20.18