]> asedeno.scripts.mit.edu Git - bluechips.git/blob - bluechips/model/expenditure.py
Expenditures can now be given a list of "tags"
[bluechips.git] / bluechips / model / expenditure.py
1 from bluechips.model.user import User
2 from bluechips.model.split import Split
3 from bluechips.model import meta
4 from bluechips.model.types import Currency
5 from bluechips.model.tag import Tag
6 from decimal import Decimal
7 from datetime import datetime
8 import random
9
10 class Expenditure(object):
11     def __init__(self, spender=None, amount=Currency(0), description=u"",
12                  date=None):
13         self.spender = spender
14         self.amount = amount
15         self.description = description
16         if self.date == None:
17             self.date = datetime.now()
18     
19     def __repr__(self):
20         return '<Expenditure: spender: %s spent: %s>' % (self.spender,
21                                                          self.amount)
22
23     def even_split(self):
24         """
25         Split up an expenditure evenly among the resident users
26         """
27         
28         residents = meta.Session.query(User).filter(User.resident==True)
29         split_percentage = Decimal(100) / Decimal(residents.count())
30         self.split(dict((resident, split_percentage) for resident in residents))
31     
32     def split(self, split_dict, split_text_dict):
33         """
34         Split up an expenditure.
35         
36         split_dict should be a dict mapping from bluechips.model:User
37         objects to a decimal:Decimal object representing the percentage
38         that user is responsible for.
39         
40         Percentages will be normalized to sum to 100%.
41         
42         If the split leaks or gains money due to rounding errors, the
43         pennies will be randomly distributed to one of the users.
44         
45         I mean, come on. You're already living together. Are you really
46         going to squabble over a few pennies?
47         """
48         
49         map(meta.Session.delete, meta.Session.query(Split).\
50                 filter_by(expenditure_id=self.id))
51         
52         total = sum(split_dict.itervalues())
53         
54         for user, share in split_dict.items():
55             if share == 0:
56                 del split_dict[user]
57             
58         amounts_dict = dict()
59         
60         for user, share in split_dict.iteritems():
61             amounts_dict[user] = Currency((share * self.amount) / total)
62         
63         difference = self.amount - sum(amounts_dict.itervalues())
64         
65         if difference > 0:
66             for i in xrange(difference):
67                 winner = random.choice(amounts_dict.keys())
68                 amounts_dict[winner] += Currency(1)
69         elif difference < 0:
70             for i in xrange(-difference):
71                 winner = random.choice(amounts_dict.keys())
72                 amounts_dict[winner] -= Currency(1)
73         
74         for user, share in amounts_dict.iteritems():
75             s = Split(self, user, share, split_text_dict[user])
76             meta.Session.add(s)
77
78     def tag(self, tags):
79         map(meta.Session.delete,
80             meta.Session.query(Tag).filter_by(expenditure_id=self.id))
81
82         for tag in tags:
83             t = Tag(self, tag)
84             meta.Session.add(t)
85
86     def involves(self, user):
87         "Returns True if ``user`` is involved in this expenditure."
88         return (any((split.user == user) and (split.share != 0)
89                     for split in self.splits) or
90                 (self.spender == user))
91
92     def share(self, user):
93         "Return the share corresponding to ``user``."
94         shares = dict((split.user, split.share)
95                       for split in self.splits)
96         return shares.get(user, Currency(0))
97
98 __all__ = ['Expenditure']