source: remit/finance_core/models.py @ 45a52c6

Last change on this file since 45a52c6 was 4c6a731, checked in by Alex Dehnert <adehnert@…>, 11 years ago

Add my guess for what the layers mean

  • Property mode set to 100644
File size: 7.5 KB
Line 
1from django.db import models, connection
2import settings
3import treebeard.mp_tree
4
5import datetime
6
7class BudgetArea(treebeard.mp_tree.MP_Node):
8    indent_str = u"\u00A0\u00A0"
9
10    name = models.CharField(max_length=50)
11    comment = models.TextField(blank=True)
12
13    # Applicable to every term?
14    always = models.BooleanField(blank=True, default=False)
15    budget_term = models.ManyToManyField('BudgetTerm', through='BudgetAreaTerm')
16
17    # Contact / ACL information
18    # If not specified, inherit from parent
19    owner = models.EmailField(help_text = 'Email address of the officer responsible for the area', blank=True) # owner of the budget area
20    interested = models.EmailField(help_text='Email address of parties interested in the area', blank=True) # interested parties (ie, whole committee)
21    use_owner = models.BooleanField(default=False, blank=True)
22    account_number = models.IntegerField(help_text='Account number: for example, cost object or GL', blank=True, null=True)
23
24    def contact_address(self,):
25        address = ''
26        if self.use_owner:
27            address = self.owner or self.interested
28        else:
29            address = self.interested or self.owner
30
31    def owner_address(self,):
32        address = self.owner
33        if address: return address
34        else:
35            parent = self.get_parent()
36            if parent: return self.get_parent().owner_address()
37            else: return settings.ADMINS[0][1]
38
39    def get_account_number(self):
40        """Retrieve the account number for this account.
41
42        This properly recurses through the hierarchy until reaching the root
43        or an account with the account number set."""
44
45        print self.account_number
46        if self.account_number:
47            return self.account_number
48        else:
49            parent = self.get_parent()
50            if parent:
51                return parent.get_account_number()
52            else:
53                return 0
54
55    def mark_used(self, term, comment=""):
56        BudgetAreaTerm.objects.get_or_create(
57            budget_area=self,
58            budget_term=term,
59            defaults={'comment':comment}
60        )
61
62    @classmethod
63    def get_by_path(cls, path, base=None, ):
64        if base:
65            node = base
66        else:
67            try:
68                root = BudgetArea.objects.get(name=path[0], depth=1)
69            except IndexError, e:
70                raise KeyError(e)
71            node = root
72            path = path[1:]
73        for name in path:
74            try:
75                node = node.get_children().filter(name=name)[0]
76            except IndexError, e:
77                raise KeyError(e)
78        return node
79
80    @classmethod
81    def get_by_pathstr(cls, path, base=None):
82        path = path.split('.')
83        return cls.get_by_path(path, base=base, )
84
85    def pathstr(self, skip=0):
86        if self.depth-1 > skip:
87            parent = self.get_parent()
88            prefix = parent.pathstr(skip=skip) + '.'
89        else:
90            prefix = ''
91        return prefix + self.name
92
93    def dump_to_html(self):
94        struct = self.dump_bulk()
95        return self.struct_to_html(struct, depth=0)
96
97    def struct_to_html(self, struct, depth=0):
98        def format_data(data):
99            return "<em>"+data['name']+"</em> "+unicode(data)
100        prefix = "\t"*depth
101        html = prefix+"<ul>\n"
102        html = html + "\n".join(
103            [("%(prefix)s\t<li>%(data)s\n%(children)s\t%(prefix)s</li>\n" % {
104                'prefix':prefix,
105                'data':format_data(elem['data']),
106                'children':('children' in elem and self.struct_to_html(elem['children'], depth+1) or '')
107            }) for elem in struct])
108        html = html + prefix + "</ul>\n"
109        return html
110
111    def indented_name(self, strip_levels=0):
112        return self.indent_str*(self.depth-strip_levels) + unicode(self)
113
114    def __unicode__(self,):
115        return u"%s [%s] (%s)" % (self.name, self.contact_address(), self.always, )
116
117    class Meta:
118        permissions = (
119            ('use_reporting', 'Can use basic reporting functionality',),
120        )
121        ordering = ['path']
122
123
124class BudgetTerm(models.Model):
125    name = models.CharField(max_length=20)
126    slug = models.SlugField(max_length=20)
127    start_date = models.DateField()
128    end_date = models.DateField()
129    submit_deadline = models.DateField()
130
131    def __unicode__(self,):
132        return "%s (%s to %s [due %s])" % (self.name, self.start_date, self.end_date, self.submit_deadline, )
133
134
135class BudgetAreaTerm(models.Model):
136    budget_area = models.ForeignKey(BudgetArea)
137    budget_term = models.ForeignKey(BudgetTerm)
138    comment = models.TextField(blank=True, )
139
140    def __unicode__(self,):
141        return "%s during %s" % (self.budget_area, self.budget_term, )
142
143
144class Transaction(models.Model):
145    name = models.CharField(max_length=60)
146    desc = models.TextField(blank=True)
147    incurred_time  = models.DateTimeField(default=datetime.datetime.now, help_text='Time the item or service was purchased')
148    tx_create_time = models.DateTimeField(default=datetime.datetime.now)
149
150    def __unicode__(self,):
151        return self.name
152
153def make_transfer(name, amount,
154    layer, budget_term, from_area, to_area, desc,
155    incurred_time, ):
156    tx = Transaction(
157        name=name,
158        desc=desc,
159        incurred_time=incurred_time,
160    )
161    tx.save()
162
163    from_li = LineItem(
164        label='Send: %s' % (name, ),
165        amount=-amount,
166        budget_area=from_area,
167        budget_term=budget_term,
168        layer=layer,
169        tx=tx,
170    )
171    from_li.save()
172
173    to_li = LineItem(
174        label='Receive: %s' % (name, ),
175        amount=amount,
176        budget_area=to_area,
177        budget_term=budget_term,
178        layer=layer,
179        tx=tx,
180    )
181    to_li.save()
182
183    return tx
184
185
186class LineItem(models.Model):
187    tx = models.ForeignKey(Transaction)
188    amount = models.DecimalField(max_digits=7, decimal_places=2, help_text='Do not include "$"')
189    label = models.CharField(max_length=60)
190    budget_area = models.ForeignKey(BudgetArea)
191    budget_term = models.ForeignKey(BudgetTerm)
192    layer = models.IntegerField() # this might actually be a Transaction property...
193
194    def layer_string(self,):
195        layer = self.layer
196        return layer_name(get_layer_by_num(layer))
197
198    def __unicode__(self, ):
199        return "%s: %s: $%s (%s) in %s during %s" % (
200            self.tx, self.label, self.amount, self.layer,
201            self.budget_area, self.budget_term, )
202
203
204LAYER_BUDGET      = 10
205LAYER_ALLOCATION  = 20
206LAYER_EXPENDITURE = 30
207LAYER_CLOSEOUT    = 40
208layers=(
209    # Guessing the meanings here a bit...
210    (LAYER_BUDGET,      'budget'),      # requested amount
211    (LAYER_ALLOCATION,  'allocation'),  # amount approved
212    (LAYER_EXPENDITURE, 'expenditure'), # spent amount
213    (LAYER_CLOSEOUT,    'closeout'),    # difference between spent and allocated
214
215    # Remit was originally developed for the UA, so I'm guessing the layers
216    # reflect a process where the treasurer proposes a budget, the body
217    # approves it (potentially with amendments), committees spend it, and then
218    # you balance the accounts at the end
219
220)
221def get_layer_by_name(name):
222    for layer in layers:
223        if name == layer[1]:
224            return layer
225    raise KeyError, "layer %s not found" % (name, )
226def get_layer_by_num(num):
227    for layer in layers:
228        if num == layer[0]:
229            return layer
230    raise KeyError, "layer %d not found" % (num, )
231def layer_name(layer): return layer[1]
232def layer_num(layer):  return layer[0]
233
234
235def coerce_full_email(email):
236    if '@' in email: return email
237    else: return email + '@' + settings.DEFAULT_DOMAIN
Note: See TracBrowser for help on using the repository browser.