コード例 #1
0
ファイル: balance.py プロジェクト: quixotique/ABO
 def __init__(self, transactions, date_range=None, chart=None, entry_pred=None, acc_map=None, use_edate=False):
     self.date_range = date_range
     self.first_date = None
     self.last_date = None
     self._raw_balances = defaultdict(lambda: struct(cdate=defaultdict(lambda: 0), total=0))
     if chart:
         for acc in chart.substantial_accounts():
             self._raw_balances[acc].total = 0
     for t in transactions:
         date = t.edate if use_edate else t.date
         if self.date_range is None or date in self.date_range:
             if self.first_date is None or date < self.first_date:
                 self.first_date = date
             if self.last_date is None or date > self.last_date:
                 self.last_date = date
             for e in t.entries:
                 if entry_pred is None or entry_pred(e):
                     acc = e.account
                     if chart:
                         acc = chart[acc]
                         assert acc is not None
                         assert acc.is_substantial(), 'acc=%r' % (acc,)
                     if acc_map is not None:
                         mapped = acc_map(acc)
                         if mapped is not None:
                             acc = mapped
                     cdate = None if e.cdate is None or self.date_range is None or e.cdate in self.date_range else e.cdate
                     rb = self._raw_balances[acc]
                     rb.cdate[cdate] += e.amount
                     rb.total += e.amount
     self.pred = lambda a, m: True
     self._balances = None
コード例 #2
0
def compute_dues(due_accounts, when):
    due_all = []
    for account, entries in due_accounts.items():
        entries.sort(key=lambda e: e.cdate or e.transaction.date)
        due = defaultdict(lambda: struct(account=account, entries=[]))
        for e in entries:
            date = e.cdate or when #e.transaction.date
            amount = e.amount
            while amount and due:
                earliest = sorted(due)[0]
                if sign(due[earliest].entries[0].amount) == sign(amount):
                    break
                while abs(amount) >= abs(due[earliest].entries[0].amount):
                    amount += due[earliest].entries.pop(0).amount
                    if not due[earliest].entries:
                        del due[earliest]
                        break
                if amount and earliest in due:
                    e1 = due[earliest].entries[0]
                    assert abs(amount) < abs(e1.amount)
                    due[earliest].entries[0] = e1.replace(amount= e1.amount + amount)._attach(e1.transaction)
                    amount = 0
            if amount:
                due[date].entries.append(e.replace(amount= amount)._attach(e.transaction))
        due_all += list(due.items())
    due_all.sort(key=lambda a: (a[0], sum(e.amount for e in a[1].entries)))
    return due_all
コード例 #3
0
def cmd_acc(config, opts):
    chart = get_chart(config, opts)
    try:
        accounts = filter_accounts(chart, opts['<PRED>'].lstrip())
    except ValueError as e:
        raise InvalidArg('<PRED>', e)
    logging.debug('accounts = %r' % list(map(str, accounts)))
    common_root_account = abo.account.common_root(accounts)
    logging.debug('common_root_account = %r' % str(common_root_account))
    all_transactions = get_transactions(chart, config, opts)
    range, bf, transactions = filter_period(chart, all_transactions, opts)
    if opts['--control']:
        entries = [e for e in chain(*(t.entries for t in all_transactions)) if chart[e.account] in accounts and (e.cdate or e.transaction.date) in range]
        entries.sort(key=lambda e: e.cdate or e.transaction.date)
    else:
        entries = [e for e in chain(*(t.entries for t in transactions)) if chart[e.account] in accounts]
    if opts['--omit-empty'] and not entries:
        return
    dw = 11
    mw = config.money_column_width()
    bw = config.balance_column_width()
    if config.output_width():
        width = max(50, config.output_width())
        pw = max(1, width - (dw + 2 + 2 * (mw + 1) + 1 + bw))
    else:
        pw = 35
        width = dw + 2 + pw + 2 * (mw + 1) + 1 + bw
    fmt = '%-{dw}.{dw}s  %-{pw}.{pw}s %{mw}s %{mw}s %{bw}s'.format(**locals())
    if not opts['--bare']:
        if config.heading:
            yield config.heading.center(width)
        yield 'STATEMENT OF ACCOUNT'.center(width)
        yield range_line(range).center(width)
        if opts['--title']:
            yield opts['--title'].center(width)
        elif common_root_account is not None:
            yield common_root_account.full_name(prefix='', separator=': ').center(width)
        yield ''
    yield fmt % ('Date', 'Particulars', 'Debit', 'Credit', 'Balance')
    yield fmt % ('-' * dw, '-' * pw, '-' * mw, '-' * mw, '-' * bw)
    tally = struct()
    tally.balance = 0
    tally.totdb = 0
    tally.totcr = 0
    if bf:
        def bflines():
            for account in accounts:
                if account.is_substantial() and account.atype is not abo.account.AccountType.ProfitLoss or opts['--bring-forward']:
                    if opts['--control']:
                        amount = bf.cbalance(account)
                        if amount != 0:
                            tally.balance += amount
                            yield fmt % ('', '; '.join(filter(bool, ['Brought forward', account.relative_name(common_root_account)])),
                                    config.format_money(-amount) if amount < 0 else '',
                                    config.format_money(amount) if amount > 0 else '',
                                    config.format_money(tally.balance))
                    else:
                        for e in sorted(bf.entries(), key=lambda e: (e.cdate or datetime.date.min, e.amount, e.account)):
                            if chart[e.account] is account and e.amount != 0:
                                tally.balance += e.amount
                                yield fmt % ('', '; '.join(filter(bool, ['Brought forward',
                                                                        'due ' + e.cdate.strftime(r'%-d-%b-%Y') if e.cdate else '',
                                                                        account.relative_name(common_root_account)])),
                                        config.format_money(-e.amount) if e.amount < 0 else '',
                                        config.format_money(e.amount) if e.amount > 0 else '',
                                        config.format_money(tally.balance))

        lines = list(bflines())
        if not opts['--bare'] or tally.balance != 0:
            for line in lines:
                yield line
    for entry in entries:
        date = entry.cdate if opts['--control'] and entry.cdate else entry.transaction.edate if opts['--effective'] else entry.transaction.date
        tally.balance += entry.amount
        if entry.amount < 0:
            tally.totdb += entry.amount
        elif entry.amount > 0:
            tally.totcr += entry.amount
        desc = entry.description(with_due=not opts['--control'], config=config)
        acc = chart[entry.account]
        if not opts['--short'] and acc is not common_root_account:
            rel = []
            for par in chain(reversed(list(acc.parents_not_in_common_with(common_root_account))), (acc,)):
                b = par.bare_name()
                for w in b.split():
                    if w not in desc:
                        rel.append(b)
                        break
            if rel:
                desc = '; '.join(s for s in [':'.join(rel), desc] if s)
        if not desc and len(entry.transaction.entries) == 2:
            oe = [e for e in entry.transaction.entries if e is not entry]
            assert len(oe) == 1
            oe = oe[0]
            desc = chart[oe.account].bare_name()
        if opts['--control'] or (opts['--effective'] and entry.transaction.edate != entry.transaction.date):
            desc = config.format_date_short(entry.transaction.date, relative_to=date) + ' ' + desc
        desc = textwrap.wrap(desc, width=pw)
        yield fmt % (date.strftime(r'%_d-%b-%Y'),
                desc.pop(0) if desc else '',
                config.format_money(-entry.amount) if entry.amount < 0 else '',
                config.format_money(entry.amount) if entry.amount > 0 else '',
                config.format_money(tally.balance))
        if opts['--wrap']:
            while desc:
                yield fmt % ('', desc.pop(0), '', '', '')
    yield fmt % ('-' * dw, '-' * pw, '-' * mw, '-' * mw, '-' * bw)
    yield fmt % ('', 'Totals for period',
            config.format_money(-tally.totdb),
            config.format_money(tally.totcr),
            '')
    yield fmt % ('', 'Balance', '', '', config.format_money(tally.balance))
コード例 #4
0
def remove_account(chart, pred, transactions):
    from itertools import chain
    from collections import defaultdict
    logging.debug("remove")
    queues = defaultdict(list)
    todo = list(transactions)
    done = []
    while todo:
        t = todo.pop(0)
        remove = defaultdict(lambda: struct(amount=0, entries=[]))
        keep = []
        keep_total = 0
        for e in t.entries:
            if pred(chart[e.account]):
                s = sign(e.amount)
                remove[s].amount += e.amount
                remove[s].entries.append(e)
            else:
                keep.append(e)
                keep_total += e.amount
        # If the transaction involves exclusively removed accounts, then remove
        # the entire transaction.
        if not keep:
            continue
        # Cancel removed entries against each other, leaving removable entries
        # with only one sign.
        if remove[1].entries and remove[-1].entries:
            remove_amount = remove[-1].amount + remove[1].amount
            assert remove_amount == -keep_total
            if remove_amount == 0:
                assert keep
                assert keep_total == 0
                done.append(t.replace(entries=keep))
                logging.debug("   done %r" % (done[-1],))
                continue
            else:
                s = sign(remove_amount)
                e1, e2 = abo.transaction._divide_entries(remove[s].entries, -remove[-s].amount)
                assert e1
                assert e2
                assert sum(e.amount for e in e1) == -remove[-s].amount
                assert sum(e.amount for e in e2) == remove_amount
                remove = e2
                t = t.replace(entries= chain(e2 + keep))
        elif remove[1].entries:
            remove_amount = remove[1].amount
            remove = remove[1].entries
        elif remove[-1].entries:
            remove_amount = remove[-1].amount
            remove = remove[-1].entries
        else:
            done.append(t)
            logging.debug("   done %r" % (done[-1],))
            continue
        logging.debug("remove %u entries from t = %s %s" % (len(remove), t.amount(), t.date))
        while remove:
            account = remove[0].account
            entries = [e for e in remove if e.account == account]
            assert entries
            remove = [e for e in remove if e.account != account]
            assert t is not None
            assert entries == [e for e in t.entries if e.account == account]
            amount = sum(e.amount for e in entries)
            assert sign(amount) == sign(remove_amount)
            assert abs(amount) <= abs(remove_amount)
            # If this account is not the only one to be removed from this
            # transaction, then split this transaction into one containing only
            # this account and a remainder containg all the other removable
            # accounts.
            if remove:
                k1, k2 = abo.transaction._divide_entries(keep, -amount)
                assert sum(e.amount for e in k1) == -amount
                assert k2
                tr = t.replace(entries= chain(entries + k1))
                t = t.replace(entries= chain(remove + k2))
            else:
                tr, t = t, None
            assert tr is not None
            queue = queues[account]
            if not queue or sign(queue[0].amount) == sign(amount):
                logging.debug("   enqueue %s" % (account,))
                queue.append(struct(amount=amount, transaction=tr)) # TODO sort by due date
            else:
                while queue and abs(queue[0].amount) <= abs(amount):
                    logging.debug("   amount=%s queue[0].amount=%s" % (amount, queue[0].amount))
                    assert sign(queue[0].amount) != sign(amount)
                    if abs(queue[0].amount) == abs(amount):
                        k1, keep = keep, None
                    else:
                        k1, k2 = abo.transaction._divide_entries(keep, queue[0].amount)
                        assert k1
                        assert k2
                        assert len(k1) <= len(keep)
                        keep = k2
                        keep_total = sum(e.amount for e in keep)
                    assert sum(e.amount for e in k1) == queue[0].amount
                    done.append(tr.replace(entries= [e for e in chain(k1, queue[0].transaction.entries) if e.account != account]))
                    logging.debug("   done %r" % (done[-1],))
                    amount += queue[0].amount
                    e1, e2 = abo.transaction._divide_entries(entries, -queue[0].amount)
                    assert sum(e.amount for e in e1) == -queue[0].amount
                    assert sum(e.amount for e in e2) == amount
                    entries = e2
                    queue.pop(0)
                if amount and queue:
                    assert entries
                    assert keep
                    logging.debug("   amount=%s queue[0].amount=%s" % (amount, queue[0].amount))
                    assert abs(amount) < abs(queue[0].amount)
                    assert sign(amount) != sign(queue[0].amount)
                    assert abs(amount) <= abs(keep_total)
                    if keep_total == -amount:
                        k1, keep = keep, None
                    else:
                        k1, k2 = abo.transaction._divide_entries(keep, -amount)
                        assert k1
                        assert k2
                        assert len(k1) <= len(keep)
                        keep = k2
                    assert sum(e.amount for e in k1) == -amount
                    qa = [e for e in queue[0].transaction.entries if e.account == account]
                    qo = [e for e in queue[0].transaction.entries if e.account != account]
                    assert sum(e.amount for e in qa) == queue[0].amount
                    assert sum(e.amount for e in qo) == -queue[0].amount
                    qa1, qa2 = abo.transaction._divide_entries(qa, -amount)
                    qo1, qo2 = abo.transaction._divide_entries(qo, amount)
                    assert sum(e.amount for e in qa1) == -amount
                    assert sum(e.amount for e in qo1) == amount
                    done.append(tr.replace(entries= list(chain(k1, qo1))))
                    logging.debug("   done %r" % (done[-1],))
                    queue[0].amount += amount
                    queue[0].transaction = tr.replace(entries= list(chain(qa2, qo2)))
    return done