Exemplo n.º 1
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
Exemplo n.º 2
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