source: remit/vouchers/models.py @ 680b2bd

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

Allow marking as RFP'd (WIP)

  • Property mode set to 100644
File size: 11.8 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, signatory, signatory_email=None):
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        voucher = 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        })
116        body = tmpl.render(ctx)
117        mail_admins(
118            'Request approval: %s approved $%s' % (
119                approver,
120                self.amount,
121            ),
122            body,
123        )
124
125    def approve_with_rfp(self, approver, ):
126       
127
128    def label(self, ):
129        return settings.GROUP_ABBR + unicode(self.pk) + 'RR'
130
131class Voucher(models.Model):
132    group_name = models.CharField(max_length=40)
133    account = models.IntegerField()
134    signatory = models.CharField(max_length=50)
135    signatory_email = models.EmailField()
136    first_name = models.CharField(max_length=20)
137    last_name = models.CharField(max_length=20)
138    email_address = models.EmailField(max_length=50)
139    mailing_address = models.TextField(blank=True, )
140    amount = models.DecimalField(max_digits=7, decimal_places=2,)
141    description = models.TextField()
142    gl = models.IntegerField()
143    processed = models.BooleanField()
144    process_time = models.DateTimeField(blank=True, null=True,)
145    documentation = models.ForeignKey('Documentation', blank=True, null=True, )
146
147    def mailing_addr_lines(self):
148        import re
149        if self.mailing_address:
150            lst = re.split(re.compile('[\n\r]*'), self.mailing_address)
151            lst = filter(lambda elem: len(elem)>0, lst)
152        else:
153            lst = []
154        lst = lst + ['']*(3-len(lst))
155        return lst
156
157    def mark_processed(self, ):
158        self.process_time = datetime.datetime.now()
159        self.processed = True
160        self.save()
161
162    def __unicode__(self, ):
163        return "%s: %s %s (%s) for $%s" % (
164            self.description,
165            self.first_name,
166            self.last_name,
167            self.email_address,
168            self.amount,
169        )
170
171    class Meta:
172        permissions = (
173            ('generate_vouchers', 'Can generate vouchers',),
174        )
175
176
177class Documentation(models.Model):
178    backing_file = models.FileField(upload_to='documentation', verbose_name='File', help_text='PDF files only', )
179    label = models.CharField(max_length=50, default="")
180    submitter = models.CharField(max_length=30, null=True, ) # Username of submitter
181    upload_time = models.DateTimeField(default=datetime.datetime.now)
182
183    def __unicode__(self, ):
184        return "%s; uploaded at %s" % (self.label, self.upload_time, )
185
186
187class StockEmail:
188    def __init__(self, name, label, recipients, template, subject_template, context, ):
189        """
190        Initialize a stock email object.
191       
192        Each argument is required.
193       
194        name:       Short name. Letters, numbers, and hyphens only.
195        label:      User-readable label. Briefly describe what the email says
196        recipients: Who receives the email. List of "recipient" (check recipient), "area" (area owner), "admins" (site admins)
197        template:   Django template filename with the actual text
198        subject_template: Django template string with the subject
199        context:    Type of context the email needs. Must be 'request' currently.
200        """
201
202        self.name       = name
203        self.label      = label
204        self.recipients = recipients
205        self.template   = template
206        self.subject_template = subject_template
207        self.context    = context
208
209    def send_email_request(self, request,):
210        """
211        Send an email that requires context "request".
212        """
213
214        assert self.context == 'request'
215
216        # Generate text
217        from django.template import Context, Template
218        from django.template.loader import get_template
219        ctx = Context({
220            'prefix': settings.EMAIL_SUBJECT_PREFIX,
221            'request': request,
222            'sender': settings.USER_EMAIL_SIGNATURE,
223        })
224        tmpl = get_template(self.template)
225        body = tmpl.render(ctx)
226        subject_tmpl = Template(self.subject_template)
227        subject = subject_tmpl.render(ctx)
228
229        # Generate recipients
230        recipients = []
231        for rt in self.recipients:
232            if rt == 'recipient':
233                recipients.append(request.check_to_email)
234            elif rt == 'area':
235                recipients.append(request.budget_area.owner_address())
236            elif rt == 'admins':
237                pass # you don't *actually* have a choice...
238        for name, addr in settings.ADMINS:
239            recipients.append(addr)
240
241        # Send mail!
242        from django.core.mail import send_mail
243        send_mail(
244            subject,
245            body,
246            settings.SERVER_EMAIL,
247            recipients,
248        )
249
250stock_emails = {
251    'nodoc': StockEmail(
252        name='nodoc',
253        label='No documentation',
254        recipients=['recipient', 'area',],
255        template='vouchers/emails/no_docs_user.txt',
256        subject_template='{{prefix}}Missing documentation for reimbursement',
257        context = 'request',
258    ),
259    'voucher-sao': StockEmail(
260        name='voucher-sao',
261        label='Voucher submitted to SAO',
262        recipients=['recipient', ],
263        template='vouchers/emails/voucher_sao_user.txt',
264        subject_template='{{prefix}}Reimbursement sent to SAO for processing',
265        context = 'request',
266    ),
267}
268
269class BulkRequestAction:
270    def __init__(self, name, label, action, perm_predicate=None, ):
271        self.name = name
272        self.label = label
273        self.action = action
274        if perm_predicate is None:
275            perm_predicate = lambda user: True
276        elif perm_predicate == True:
277            perm_predicate = lambda user: True
278        self.perm_predicate = perm_predicate
279    def can(self, user):
280        return self.perm_predicate(user)
281    def do(self, http_request, rr, ):
282        if self.can(http_request.user):
283            return self.action(http_request, rr, )
284        else:
285            return False, "permission denied"
286    def __str__(self):
287        return self.label
288    @classmethod
289    def filter_can_only(cls, actions, user):
290        return [ action for action in actions if action.can(user) ]
291def bulk_action_approve(http_request, rr):
292    approver = http_request.user
293    signatory_name = http_request.user.get_full_name()
294    if rr.voucher:
295        return False, "already approved"
296    else:
297        rr.approve(approver, signatory_name)
298        return True, "request approved"
299
300def bulk_action_email_factory(stock_email_obj):
301    assert stock_email_obj.context == 'request'
302    def inner(http_request, rr):
303        stock_email_obj.send_email_request(rr)
304        return True, "mail sent"
305    return inner
306def perm_checker(perm):
307    def predicate(user):
308        return user.has_perm(perm)
309    return predicate
310
311bulk_request_actions = []
312if settings.SIGNATORY_EMAIL:
313    bulk_request_actions.append(BulkRequestAction(
314        name='approve',
315        label='Approve Requests',
316        action=bulk_action_approve,
317        perm_predicate=perm_checker('vouchers.can_approve'),
318    ))
319for name, stockemail in stock_emails.items():
320    if stockemail.context == 'request':
321        bulk_request_actions.append(BulkRequestAction(
322            name='email/%s' % name,
323            label='Stock Email: %s' % stockemail.label,
324            action=bulk_action_email_factory(stockemail),
325            perm_predicate=perm_checker('vouchers.can_email'),
326        ))
Note: See TracBrowser for help on using the repository browser.