Beispiel #1
0
 def setUp(self):
     self.u = model.User(u'chaz', u'Charles Root', False)
     self.e = model.Expenditure(self.u, Currency('444.88'),
                                u'chaz buys lunch')
     meta.Session.add(self.u)
     meta.Session.add(self.e)
     meta.Session.commit()
Beispiel #2
0
    def test_simpleSplit(self):
        """
        Test simply splitting a $100 expenditure amongst 4 people
        """
        createUsers(4)

        e = model.Expenditure(
            meta.Session.query(model.User).first(), Currency("100"))
        meta.Session.add(e)
        e.even_split()
        meta.Session.commit()

        for s in meta.Session.query(model.Split).\
                filter(model.Split.expenditure==e):
            self.assertEqual(s.share, Currency("25.00"))

        deleteExpenditures()
        deleteUsers()
Beispiel #3
0
    def test_index_description_blank_oops(self):
        response = self.app.get(url_for(controller='spend',
                                        action='index'))
        # Test response...
        response.mustcontain('Add a New Expenditure')
        form = response.form

        user = meta.Session.query(model.User).\
                filter_by(name=u'Charlie Root').one()

        form['spender_id'] = user.id
        form['amount'] = '74.04'
        # Make sure date is today.
        today = date.today()
        assert form['date'].value == today.strftime('%m/%d/%Y')
        form['description'] = ''
        for ii in range(4):
            if int(form['shares-%d.user_id' % ii].value) in (1, 4):
                form['shares-%d.amount' % ii] = '1'
            else:
                form['shares-%d.amount' % ii] = '2'

        response = form.submit()
        response.mustcontain('<!-- for: description -->', 'Please enter a value')
        form = response.form
        form['description'] = 'Another test'
        response = form.submit()
        response = response.follow()

        e = meta.Session.query(model.Expenditure).\
                order_by(model.Expenditure.id.desc()).first()
        assert e.spender.name == u'Charlie Root'
        assert e.amount == 7404
        assert e.date == today
        assert e.description == u'Another test'

        # Test the split.
        shares = dict(((sp.user_id, sp.share)
                       for sp in e.splits))
        assert shares[1] == Currency('12.34')
        assert shares[2] == Currency('24.68')
        assert shares[3] == Currency('24.68')
        assert shares[4] == Currency('12.34')
Beispiel #4
0
def createExpenditures(n=None):
    if n is None:
        n = random.randint(5, 20)
    users = meta.Session.query(bluechips.model.User).all()
    for i in xrange(n):
        e = bluechips.model.Expenditure(random.choice(users),
                                        Currency(random.randint(1000, 100000)))
        meta.Session.add(e)
        e.even_split()
    meta.Session.commit()
Beispiel #5
0
 def __init__(self,
              spender=None,
              amount=Currency(0),
              description=u"",
              date=None):
     self.spender = spender
     self.amount = amount
     self.description = description
     if self.date == None:
         self.date = datetime.now()
Beispiel #6
0
    def split(self, split_dict):
        """
        Split up an expenditure.
        
        split_dict should be a dict mapping from bluechips.model:User
        objects to a decimal:Decimal object representing the percentage
        that user is responsible for.
        
        Percentages will be normalized to sum to 100%.
        
        If the split leaks or gains money due to rounding errors, the
        pennies will be randomly distributed to a subset of the users.
        
        I mean, come on. You're already living together. Are you really
        going to squabble over a few pennies?
        """

        map(meta.Session.delete, meta.Session.query(Split).\
                filter_by(expenditure_id=self.id))

        total = sum(split_dict.itervalues())

        for user, share in split_dict.items():
            if share == 0:
                del split_dict[user]

        amounts_dict = dict()

        for user, share in split_dict.iteritems():
            amounts_dict[user] = Currency((share * self.amount) / total)

        difference = self.amount - sum(amounts_dict.itervalues())

        winners = random.sample(amounts_dict.keys(), abs(int(difference)))
        for winner in winners:
            amounts_dict[winner] += Currency(
                1) if difference > 0 else Currency(-1)

        for user, share in amounts_dict.iteritems():
            s = Split(self, user, share)
            meta.Session.add(s)
Beispiel #7
0
    def test_uneven(self):
        """
        Test that expenditures can be split non-evenly
        """
        createUsers(2)

        users = meta.Session.query(model.User).all()

        e = model.Expenditure(users[0], Currency("100"))
        meta.Session.add(e)

        split_dict = {users[0]: Decimal("20"), users[1]: Decimal("80")}

        amount_dict = {users[0]: Currency("20"), users[1]: Currency("80")}

        e.split(split_dict)
        meta.Session.commit()

        for s in meta.Session.query(model.Split):
            self.assertEqual(s.share, amount_dict[s.user])

        deleteExpenditures()
        deleteUsers()
Beispiel #8
0
    def test_sevenPeople(self):
        """
        Test that expenses are split as evenly as possible with lots of people
        """
        createUsers(7)

        users = meta.Session.query(model.User).all()

        e = model.Expenditure(users[0], Currency("24.00"))
        meta.Session.add(e)
        e.even_split()
        meta.Session.commit()

        splits = meta.Session.query(model.Split).all()
        self.assertEqual(e.amount, sum(s.share for s in splits))

        max_split = max(s.share for s in splits)
        min_split = min(s.share for s in splits)

        self.assertTrue(max_split - min_split <= Currency(1))

        deleteExpenditures()
        deleteUsers()
Beispiel #9
0
 def share_name(self):
     "Return the share name that matches the splitting"
     shares = dict((split.user, split.share) for split in self.splits
                   if split.share != 0)
     if len(shares) == 1:
         user = shares.keys()[0]
         return 'For %s' % user.name
     for oname, oshares in share_dict.items():
         ratio = float(self.amount) * 1. / sum(oshares.values())
         difference = sum(
             abs(shares[u] - oshares.get(u.username, 0) * ratio)
             for u in shares)
         if difference <= Currency(len(shares)):
             return oname
Beispiel #10
0
    def test_unevenBadTotal(self):
        """
        Test that transactions get split up properly when the uneven
        split shares don't add to 100%
        """
        createUsers(2)

        users = meta.Session.query(model.User).all()

        e = model.Expenditure(users[0], Currency("100.00"))
        meta.Session.add(e)

        split_dict = {users[0]: Decimal(10), users[1]: Decimal(15)}

        amount_dict = {users[0]: Currency("40"), users[1]: Currency("60")}

        e.split(split_dict)
        meta.Session.commit()

        for s in meta.Session.query(model.Split):
            self.assertEqual(s.share, amount_dict[s.user])

        deleteExpenditures()
        deleteUsers()
Beispiel #11
0
def debts():
    # In this scheme, negative numbers represent money the house owes
    # the user, and positive numbers represent money the user owes the
    # house
    users = meta.Session.query(model.User)

    debts_dict = dict((u, Currency(0)) for u in users)

    # First, credit everyone for expenditures they've made
    total_expenditures = meta.Session.query(model.Expenditure).\
        add_column(sqlalchemy.func.sum(model.Expenditure.amount).label('total_spend')).\
        group_by(model.Expenditure.spender_id)
    for expenditure, total_spend in total_expenditures:
        debts_dict[expenditure.spender] -= total_spend

    # Next, debit everyone for expenditures that they have an
    # investment in (i.e. splits)

    total_splits = meta.Session.query(model.Split).\
        add_column(sqlalchemy.func.sum(model.Split.share).label('total_split')).\
        group_by(model.Split.user_id)

    for split, total_cents in total_splits:
        debts_dict[split.user] += total_cents

    # Finally, move transfers around appropriately
    #
    # To keep this from getting to be expensive, have SQL sum up
    # transfers for us

    transfer_q = meta.Session.query(model.Transfer).\
        add_column(sqlalchemy.func.sum(model.Transfer.amount).label('total_amount'))
    total_debits = transfer_q.group_by(model.Transfer.debtor_id)
    total_credits = transfer_q.group_by(model.Transfer.creditor_id)

    for transfer, total_amount in total_debits:
        debts_dict[transfer.debtor] -= total_amount
    for transfer, total_amount in total_credits:
        debts_dict[transfer.creditor] += total_amount

    return debts_dict
Beispiel #12
0
    def test_negativeExpenditure(self):
        """
        Test that negative expenditures get split correctly
        """
        createUsers(2)

        users = meta.Session.query(model.User).all()

        e = model.Expenditure(users[0], Currency("100.00"))
        meta.Session.add(e)

        # Force a split that will result in needing to distribute
        # pennies
        split_dict = {users[0]: Decimal(1), users[1]: Decimal(2)}
        e.split(split_dict)
        meta.Session.commit()

        self.assertEqual(e.amount,
                         sum(s.share for s in meta.Session.query(model.Split)))

        deleteExpenditures()
        deleteUsers()
Beispiel #13
0
 def share(self, user):
     "Return the share corresponding to ``user``."
     shares = dict((split.user, split.share) for split in self.splits)
     return shares.get(user, Currency(0))
Beispiel #14
0
 def test_split_small_negative(self):
     self._two_way_split_test(Currency('-0.01'), Currency('-0.01'),
                              Currency('-0.00'))
Beispiel #15
0
 def test_even_split(self):
     self.e.even_split()
     meta.Session.commit()
     for sp in self.e.splits:
         assert sp.share == Currency('111.22')
Beispiel #16
0
 def setUp(self):
     self.u = model.User('chaz', u'Charles Root', False)
     self.e = model.Expenditure(self.u, Currency('12.34'),
                                u'A test expenditure')
     self.sp = model.Split(self.e, self.u, Currency('5.55'))
Beispiel #17
0
 def test_constructor(self):
     assert self.sp.expenditure == self.e
     assert self.sp.user == self.u
     assert self.sp.share == Currency('5.55')
Beispiel #18
0
 def test_split_rounds_up(self):
     self._two_way_split_test(Currency('39.99'), Currency('19.99'),
                              Currency('20.00'))
Beispiel #19
0
 def test_split_rounds_down(self):
     self._two_way_split_test(Currency('40.01'), Currency('20.00'),
                              Currency('20.01'))
Beispiel #20
0
 def test_constructor(self):
     assert self.e.spender == self.u
     assert self.e.amount == Currency('444.88')
     assert self.e.description == u'chaz buys lunch'
Beispiel #21
0
 def test_split_small(self):
     self._two_way_split_test(Currency('0.01'), Currency('0.00'),
                              Currency('0.01'))