source: remit/vouchers/models.py @ 0e4f185

client
Last change on this file since 0e4f185 was d50ec5b, checked in by Alex Dehnert <adehnert@…>, 15 years ago

Allow marking things as RFPized

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