Changeset - 032175cd26d9
[Not reviewed]
0 5 0
Joar Wandborg - 10 years ago 2013-12-11 14:12:08
joar@wandborg.se
Added transaction IDs and metadata
5 files changed with 93 insertions and 28 deletions:
0 comments (0 inline, 0 general)
accounting/__init__.py
Show inline comments
...
 
@@ -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
 

	
accounting/client.py
Show inline comments
...
 
@@ -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())
accounting/models.py
Show inline comments
 
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}"' +
accounting/transport.py
Show inline comments
...
 
@@ -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(
non-profit-test-data.ledger
Show inline comments
 

	
 
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
0 comments (0 inline, 0 general)