source: remit/vouchers/models.py @ 42b287c

client
Last change on this file since 42b287c was 42b287c, checked in by Alex Dehnert <adehnert@…>, 14 years ago

Include review link in emails

  • Property mode set to 100644
File size: 13.1 KB
Line 
1from django.db import models
2import settings
3import finance_core
4from finance_core.models import BudgetArea, BudgetTerm
5
6from django.core.mail import send_mail, mail_admins
7from django.core.urlresolvers import reverse
8from django.template import Context, Template
9from django.template.loader import get_template
10
11import datetime
12
13APPROVAL_STATE_PENDING = 0
14APPROVAL_STATE_APPROVED = 1
15APPROVAL_STATE_REJECTED = -1
16APPROVAL_STATES = (
17    (APPROVAL_STATE_PENDING,  'Pending'),
18    (APPROVAL_STATE_APPROVED, 'Approved'),
19    (APPROVAL_STATE_REJECTED, 'Rejected'),
20)
21
22class ReimbursementRequest(models.Model):
23    submitter = models.CharField(max_length=30) # Username of submitter
24    check_to_first_name = models.CharField(max_length=50, verbose_name="check recipient's first name")
25    check_to_last_name = models.CharField(max_length=50, verbose_name="check recipient's last name")
26    check_to_email = models.EmailField(verbose_name="email address for check pickup")
27    check_to_addr = models.TextField(blank=True, verbose_name="address for check mailing", help_text="For most requests, this should be blank for pickup in SAO (W20-549)")
28    amount = models.DecimalField(max_digits=7, decimal_places=2, help_text='Do not include "$"')
29    budget_area = models.ForeignKey(BudgetArea, related_name='as_budget_area')
30    budget_term = models.ForeignKey(BudgetTerm)
31    expense_area = models.ForeignKey(BudgetArea, related_name='as_expense_area') # ~GL
32    incurred_time = models.DateTimeField(default=datetime.datetime.now, help_text='Time the item or service was purchased')
33    request_time = models.DateTimeField(default=datetime.datetime.now)
34    approval_time = models.DateTimeField(blank=True, null=True,)
35    approval_status = models.IntegerField(default=0, choices=APPROVAL_STATES)
36    name = models.CharField(max_length=50, verbose_name='short description', )
37    description = models.TextField(blank=True, verbose_name='long description', )
38    documentation = models.ForeignKey('Documentation', null=True, blank=True, )
39    voucher       = models.ForeignKey('Voucher',       null=True, )
40    rfp           = models.ForeignKey('RFP',           null=True, blank=True, )
41
42    class Meta:
43        permissions = (
44            ('can_list', 'Can list requests',),
45            ('can_approve', 'Can approve requests',),
46            ('can_email', 'Can send mail about requests',),
47        )
48        ordering = ['id', ]
49
50    def __unicode__(self, ):
51        return "%s: %s %s (%s) (by %s) for $%s" % (
52            self.name,
53            self.check_to_first_name,
54            self.check_to_last_name,
55            self.check_to_email,
56            self.submitter,
57            self.amount,
58        )
59
60    def create_transfers(self, ):
61        finance_core.models.make_transfer(
62            self.name,
63            self.amount,
64            finance_core.models.LAYER_EXPENDITURE,
65            self.budget_term,
66            self.budget_area,
67            self.expense_area,
68            self.description,
69            self.incurred_time,
70        )
71
72    def convert_to_voucher(self, signatory, signatory_email=None):
73        if signatory_email is None:
74            signatory_email = settings.SIGNATORY_EMAIL
75        voucher = Voucher()
76        voucher.group_name = settings.GROUP_NAME
77        voucher.account = self.budget_area.get_account_number()
78        voucher.signatory = signatory
79        voucher.signatory_email = signatory_email
80        voucher.first_name = self.check_to_first_name
81        voucher.last_name = self.check_to_last_name
82        voucher.email_address = self.check_to_email
83        voucher.mailing_address = self.check_to_addr
84        voucher.amount = self.amount
85        voucher.description = self.label() + ': ' + self.name
86        voucher.gl = self.expense_area.get_account_number()
87        voucher.documentation = self.documentation
88        voucher.save()
89        self.create_transfers()
90        self.approval_status = 1
91        self.approval_time = datetime.datetime.now()
92        self.voucher = voucher
93        self.save()
94
95    def convert_to_rfp(self, ):
96        rfp = RFP()
97        rfp.save()
98        self.create_transfers()
99        self.approval_status = APPROVAL_STATE_APPROVED
100        self.approval_time = datetime.datetime.now()
101        self.rfp = rfp
102        self.save()
103
104    def approve(self, approver, signatory_name, signatory_email=None, ):
105        """Mark a request as approved.
106
107        approver:       user object of the approving user
108        signatory_name: name of signatory
109        signatory_email: email address of signatory (provide None for default)
110        """
111        self.convert_to_voucher(signatory_name, signatory_email,)
112        tmpl = get_template('vouchers/emails/request_approval_admin.txt')
113        ctx = Context({
114            'approver': approver,
115            'request': self,
116            'approval_type': 'voucher',
117        })
118        body = tmpl.render(ctx)
119        mail_admins(
120            'Request approval: %s approved $%s [voucher]' % (
121                approver,
122                self.amount,
123            ),
124            body,
125        )
126
127    def approve_as_rfp(self, approver, ):
128        self.convert_to_rfp()
129        tmpl = get_template('vouchers/emails/request_approval_admin.txt')
130        ctx = Context({
131            'approver': approver,
132            'request': self,
133            'approval_type': 'RFP',
134        })
135        body = tmpl.render(ctx)
136        mail_admins(
137            'Request approval: %s approved $%s [RFP]' % (
138                approver,
139                self.amount,
140            ),
141            body,
142        )
143
144
145    def review_link(self, ):
146        path = settings.SITE_URL_BASE + reverse('review_request', kwargs=dict(object_id=self.id,))
147        return path
148
149    def label(self, ):
150        return settings.GROUP_ABBR + unicode(self.pk) + 'RR'
151
152class Voucher(models.Model):
153    group_name = models.CharField(max_length=40)
154    account = models.IntegerField()
155    signatory = models.CharField(max_length=50)
156    signatory_email = models.EmailField()
157    first_name = models.CharField(max_length=20)
158    last_name = models.CharField(max_length=20)
159    email_address = models.EmailField(max_length=50)
160    mailing_address = models.TextField(blank=True, )
161    amount = models.DecimalField(max_digits=7, decimal_places=2,)
162    description = models.TextField()
163    gl = models.IntegerField()
164    processed = models.BooleanField()
165    process_time = models.DateTimeField(blank=True, null=True,)
166    documentation = models.ForeignKey('Documentation', blank=True, null=True, )
167
168    def mailing_addr_lines(self):
169        import re
170        if self.mailing_address:
171            lst = re.split(re.compile('[\n\r]*'), self.mailing_address)
172            lst = filter(lambda elem: len(elem)>0, lst)
173        else:
174            lst = []
175        lst = lst + ['']*(3-len(lst))
176        return lst
177
178    def mark_processed(self, ):
179        self.process_time = datetime.datetime.now()
180        self.processed = True
181        self.save()
182
183    def __unicode__(self, ):
184        return "%s: %s %s (%s) for $%s" % (
185            self.description,
186            self.first_name,
187            self.last_name,
188            self.email_address,
189            self.amount,
190        )
191
192    class Meta:
193        permissions = (
194            ('generate_vouchers', 'Can generate vouchers',),
195        )
196
197class RFP(models.Model):
198    create_time = models.DateTimeField(default=datetime.datetime.now)
199
200    def __unicode__(self, ):
201        return "RFP: %s" % (self.create_time.strftime(settings.SHORT_DATETIME_FORMAT_F), )
202
203class Documentation(models.Model):
204    backing_file = models.FileField(upload_to='documentation', verbose_name='File', help_text='PDF files only', )
205    label = models.CharField(max_length=50, default="")
206    submitter = models.CharField(max_length=30, null=True, ) # Username of submitter
207    upload_time = models.DateTimeField(default=datetime.datetime.now)
208
209    def __unicode__(self, ):
210        return "%s; uploaded at %s" % (self.label, self.upload_time, )
211
212
213class StockEmail:
214    def __init__(self, name, label, recipients, template, subject_template, context, ):
215        """
216        Initialize a stock email object.
217       
218        Each argument is required.
219       
220        name:       Short name. Letters, numbers, and hyphens only.
221        label:      User-readable label. Briefly describe what the email says
222        recipients: Who receives the email. List of "recipient" (check recipient), "area" (area owner), "admins" (site admins)
223        template:   Django template filename with the actual text
224        subject_template: Django template string with the subject
225        context:    Type of context the email needs. Must be 'request' currently.
226        """
227
228        self.name       = name
229        self.label      = label
230        self.recipients = recipients
231        self.template   = template
232        self.subject_template = subject_template
233        self.context    = context
234
235    def send_email_request(self, request,):
236        """
237        Send an email that requires context "request".
238        """
239
240        assert self.context == 'request'
241
242        # Generate text
243        from django.template import Context, Template
244        from django.template.loader import get_template
245        ctx = Context({
246            'prefix': settings.EMAIL_SUBJECT_PREFIX,
247            'request': request,
248            'sender': settings.USER_EMAIL_SIGNATURE,
249        })
250        tmpl = get_template(self.template)
251        body = tmpl.render(ctx)
252        subject_tmpl = Template(self.subject_template)
253        subject = subject_tmpl.render(ctx)
254
255        # Generate recipients
256        recipients = []
257        for rt in self.recipients:
258            if rt == 'recipient':
259                recipients.append(request.check_to_email)
260            elif rt == 'area':
261                recipients.append(request.budget_area.owner_address())
262            elif rt == 'admins':
263                pass # you don't *actually* have a choice...
264        for name, addr in settings.ADMINS:
265            recipients.append(addr)
266
267        # Send mail!
268        from django.core.mail import send_mail
269        send_mail(
270            subject,
271            body,
272            settings.SERVER_EMAIL,
273            recipients,
274        )
275
276stock_emails = {
277    'nodoc': StockEmail(
278        name='nodoc',
279        label='No documentation',
280        recipients=['recipient', 'area',],
281        template='vouchers/emails/no_docs_user.txt',
282        subject_template='{{prefix}}Missing documentation for reimbursement',
283        context = 'request',
284    ),
285    'voucher-sao': StockEmail(
286        name='voucher-sao',
287        label='Voucher submitted to SAO',
288        recipients=['recipient', ],
289        template='vouchers/emails/voucher_sao_user.txt',
290        subject_template='{{prefix}}Reimbursement sent to SAO for processing',
291        context = 'request',
292    ),
293}
294
295class BulkRequestAction:
296    def __init__(self, name, label, action, perm_predicate=None, ):
297        self.name = name
298        self.label = label
299        self.action = action
300        if perm_predicate is None:
301            perm_predicate = lambda user: True
302        elif perm_predicate == True:
303            perm_predicate = lambda user: True
304        self.perm_predicate = perm_predicate
305    def can(self, user):
306        return self.perm_predicate(user)
307    def do(self, http_request, rr, ):
308        if self.can(http_request.user):
309            return self.action(http_request, rr, )
310        else:
311            return False, "permission denied"
312    def __str__(self):
313        return self.label
314    @classmethod
315    def filter_can_only(cls, actions, user):
316        return [ action for action in actions if action.can(user) ]
317
318def bulk_action_approve(http_request, rr):
319    approver = http_request.user
320    signatory_name = http_request.user.get_full_name()
321    if rr.voucher:
322        return False, "already approved"
323    else:
324        rr.approve(approver, signatory_name)
325        return True, "request approved"
326
327def bulk_action_approve_as_rfp(http_request, rr):
328    approver = http_request.user
329    if rr.rfp:
330        return False, "already marked as RFPized"
331    else:
332        rr.approve_as_rfp(approver, )
333        return True, "request marked as RFPized"
334
335def bulk_action_email_factory(stock_email_obj):
336    assert stock_email_obj.context == 'request'
337    def inner(http_request, rr):
338        stock_email_obj.send_email_request(rr)
339        return True, "mail sent"
340    return inner
341def perm_checker(perm):
342    def predicate(user):
343        return user.has_perm(perm)
344    return predicate
345
346bulk_request_actions = []
347if settings.SIGNATORY_EMAIL:
348    bulk_request_actions.append(BulkRequestAction(
349        name='approve',
350        label='Approve Requests',
351        action=bulk_action_approve,
352        perm_predicate=perm_checker('vouchers.can_approve'),
353    ))
354bulk_request_actions.append(BulkRequestAction(
355    name='approve_as_rfp',
356    label='Mark Requests as RFPized',
357    action=bulk_action_approve_as_rfp,
358    perm_predicate=perm_checker('vouchers.can_approve'),
359))
360for name, stockemail in stock_emails.items():
361    if stockemail.context == 'request':
362        bulk_request_actions.append(BulkRequestAction(
363            name='email/%s' % name,
364            label='Stock Email: %s' % stockemail.label,
365            action=bulk_action_email_factory(stockemail),
366            perm_predicate=perm_checker('vouchers.can_email'),
367        ))
Note: See TracBrowser for help on using the repository browser.