source: remit/vouchers/models.py @ bef7191

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

Refactor out request approval

  • Property mode set to 100644
File size: 9.3 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
40    class Meta:
41        permissions = (
42            ('can_list', 'Can list requests',),
43            ('can_approve', 'Can approve requests',),
44            ('can_email', 'Can send mail about requests',),
45        )
46        ordering = ['id', ]
47
48    def __unicode__(self, ):
49        return "%s: %s %s (%s) (by %s) for $%s" % (
50            self.name,
51            self.check_to_first_name,
52            self.check_to_last_name,
53            self.check_to_email,
54            self.submitter,
55            self.amount,
56        )
57
58    def convert(self, signatory, signatory_email=None):
59        if signatory_email is None:
60            signatory_email = settings.SIGNATORY_EMAIL
61        voucher = Voucher()
62        voucher.group_name = settings.GROUP_NAME
63        voucher.account = self.budget_area.get_account_number()
64        voucher.signatory = signatory
65        voucher.signatory_email = signatory_email
66        voucher.first_name = self.check_to_first_name
67        voucher.last_name = self.check_to_last_name
68        voucher.email_address = self.check_to_email
69        voucher.mailing_address = self.check_to_addr
70        voucher.amount = self.amount
71        voucher.description = self.label() + ': ' + self.name
72        voucher.gl = self.expense_area.get_account_number()
73        voucher.documentation = self.documentation
74        voucher.save()
75        finance_core.models.make_transfer(
76            self.name,
77            self.amount,
78            finance_core.models.LAYER_EXPENDITURE,
79            self.budget_term,
80            self.budget_area,
81            self.expense_area,
82            self.description,
83            self.incurred_time,
84        )
85        self.approval_status = 1
86        self.approval_time = datetime.datetime.now()
87        self.voucher = voucher
88        self.save()
89
90    def approve(self, approver, signatory_name, signatory_email=None, ):
91        """Mark a request as approved.
92
93        approver:       user object of the approving user
94        signatory_name: name of signatory
95        signatory_email: email address of signatory (provide None for default)
96        """
97        voucher = self.convert(signatory_name, signatory_email,)
98        tmpl = get_template('vouchers/emails/request_approval_admin.txt')
99        ctx = Context({
100            'approver': approver,
101            'request': self,
102        })
103        body = tmpl.render(ctx)
104        mail_admins(
105            'Request approval: %s approved $%s' % (
106                approver,
107                self.amount,
108            ),
109            body,
110        )
111
112    def label(self, ):
113        return settings.GROUP_ABBR + unicode(self.pk) + 'RR'
114
115class Voucher(models.Model):
116    group_name = models.CharField(max_length=40)
117    account = models.IntegerField()
118    signatory = models.CharField(max_length=50)
119    signatory_email = models.EmailField()
120    first_name = models.CharField(max_length=20)
121    last_name = models.CharField(max_length=20)
122    email_address = models.EmailField(max_length=50)
123    mailing_address = models.TextField(blank=True, )
124    amount = models.DecimalField(max_digits=7, decimal_places=2,)
125    description = models.TextField()
126    gl = models.IntegerField()
127    processed = models.BooleanField()
128    process_time = models.DateTimeField(blank=True, null=True,)
129    documentation = models.ForeignKey('Documentation', blank=True, null=True, )
130
131    def mailing_addr_lines(self):
132        import re
133        if self.mailing_address:
134            lst = re.split(re.compile('[\n\r]*'), self.mailing_address)
135            lst = filter(lambda elem: len(elem)>0, lst)
136        else:
137            lst = []
138        lst = lst + ['']*(3-len(lst))
139        return lst
140
141    def mark_processed(self, ):
142        self.process_time = datetime.datetime.now()
143        self.processed = True
144        self.save()
145
146    def __unicode__(self, ):
147        return "%s: %s %s (%s) for $%s" % (
148            self.description,
149            self.first_name,
150            self.last_name,
151            self.email_address,
152            self.amount,
153        )
154
155    class Meta:
156        permissions = (
157            ('generate_vouchers', 'Can generate vouchers',),
158        )
159
160
161class Documentation(models.Model):
162    backing_file = models.FileField(upload_to='documentation', verbose_name='File', help_text='PDF files only', )
163    label = models.CharField(max_length=50, default="")
164    submitter = models.CharField(max_length=30, null=True, ) # Username of submitter
165    upload_time = models.DateTimeField(default=datetime.datetime.now)
166
167    def __unicode__(self, ):
168        return "%s; uploaded at %s" % (self.label, self.upload_time, )
169
170
171class StockEmail:
172    def __init__(self, name, label, recipients, template, subject_template, context, ):
173        """
174        Initialize a stock email object.
175       
176        Each argument is required.
177       
178        name:       Short name. Letters, numbers, and hyphens only.
179        label:      User-readable label. Briefly describe what the email says
180        recipients: Who receives the email. List of "recipient" (check recipient), "area" (area owner), "admins" (site admins)
181        template:   Django template filename with the actual text
182        subject_template: Django template string with the subject
183        context:    Type of context the email needs. Must be 'request' currently.
184        """
185
186        self.name       = name
187        self.label      = label
188        self.recipients = recipients
189        self.template   = template
190        self.subject_template = subject_template
191        self.context    = context
192
193    def send_email_request(self, request,):
194        """
195        Send an email that requires context "request".
196        """
197
198        assert self.context == 'request'
199
200        # Generate text
201        from django.template import Context, Template
202        from django.template.loader import get_template
203        ctx = Context({
204            'prefix': settings.EMAIL_SUBJECT_PREFIX,
205            'request': request,
206            'sender': settings.USER_EMAIL_SIGNATURE,
207        })
208        tmpl = get_template(self.template)
209        body = tmpl.render(ctx)
210        subject_tmpl = Template(self.subject_template)
211        subject = subject_tmpl.render(ctx)
212
213        # Generate recipients
214        recipients = []
215        for rt in self.recipients:
216            if rt == 'recipient':
217                recipients.append(request.check_to_email)
218            elif rt == 'area':
219                recipients.append(request.budget_area.owner_address())
220            elif rt == 'admins':
221                pass # you don't *actually* have a choice...
222        for name, addr in settings.ADMINS:
223            recipients.append(addr)
224
225        # Send mail!
226        from django.core.mail import send_mail
227        send_mail(
228            subject,
229            body,
230            settings.SERVER_EMAIL,
231            recipients,
232        )
233
234stock_emails = {
235    'nodoc': StockEmail(
236        name='nodoc',
237        label='No documentation',
238        recipients=['recipient', 'area',],
239        template='vouchers/emails/no_docs_user.txt',
240        subject_template='{{prefix}}Missing documentation for reimbursement',
241        context = 'request',
242    ),
243    'voucher-sao': StockEmail(
244        name='voucher-sao',
245        label='Voucher submitted to SAO',
246        recipients=['recipient', ],
247        template='vouchers/emails/voucher_sao_user.txt',
248        subject_template='{{prefix}}Reimbursement sent to SAO for processing',
249        context = 'request',
250    ),
251}
Note: See TracBrowser for help on using the repository browser.