source: remit/vouchers/views.py @ 71ca9e6

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

Display total amount on summary sheet (Trac: #31)

  • Property mode set to 100644
File size: 15.5 KB
Line 
1import vouchers.models
2from vouchers.models import ReimbursementRequest, Documentation
3from finance_core.models import BudgetTerm, BudgetArea
4from util.shortcuts import get_403_response
5
6from django.contrib.auth.decorators import user_passes_test, login_required
7from django.shortcuts import render_to_response, get_object_or_404
8from django.template import RequestContext
9from django.http import Http404, HttpResponseRedirect
10import django.forms
11from django.forms import Form
12from django.forms import ModelForm
13from django.forms import ModelChoiceField
14from django.core.urlresolvers import reverse
15from django.core.mail import send_mail, mail_admins
16from django.db.models import Q
17from django.template import Context, Template
18from django.template.loader import get_template
19from django.views.generic import list_detail
20
21import decimal
22
23import settings
24
25class RequestForm(ModelForm):
26    class Meta:
27        model = ReimbursementRequest
28        fields = (
29            'name',
30            'description',
31            'incurred_time',
32            'amount',
33            'budget_area',
34            'expense_area',
35            'check_to_first_name',
36            'check_to_last_name',
37            'check_to_email',
38            'check_to_addr',
39        )
40
41
42class CommitteesField(ModelChoiceField):
43    def __init__(self, *args, **kargs):
44        base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH)
45        self.strip_levels = base_area.depth
46        areas = (base_area.get_descendants()
47            .filter(depth__lte=base_area.depth+settings.COMMITTEE_HIERARCHY_LEVELS)
48            .exclude(name='Holding')
49        )
50        ModelChoiceField.__init__(self, queryset=areas,
51            help_text='Select the appropriate committe or other budget area',
52            *args, **kargs)
53
54    def label_from_instance(self, obj,):
55        return obj.indented_name(strip_levels=self.strip_levels)
56
57class SelectRequestBasicsForm(Form):
58    area = CommitteesField()
59    term = ModelChoiceField(queryset = BudgetTerm.objects.all())
60
61class DocUploadForm(ModelForm):
62    def clean_backing_file(self, ):
63        f = self.cleaned_data['backing_file']
64        ext = f.name.rsplit('.')[-1]
65        contenttype = f.content_type
66        if ext != 'pdf':
67            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a .%s file" % (ext, ))
68        elif contenttype != 'application/pdf':
69            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a %s file" % (contenttype, ))
70        else:
71            return f
72
73    class Meta:
74        model = Documentation
75        fields = (
76            'label',
77            'backing_file',
78        )
79
80
81@user_passes_test(lambda u: u.is_authenticated())
82def select_request_basics(http_request, ):
83    if http_request.method == 'POST': # If the form has been submitted...
84        form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data
85        if form.is_valid(): # All validation rules pass
86            term = form.cleaned_data['term'].slug
87            area = form.cleaned_data['area'].id
88            return HttpResponseRedirect(reverse(submit_request, args=[term, area],)) # Redirect after POST
89    else:
90        form = SelectRequestBasicsForm() # An unbound form
91
92    context = {
93        'form':form,
94        'pagename':'request_reimbursement',
95    }
96    return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), )
97
98class CommitteeBudgetAreasField(ModelChoiceField):
99    def __init__(self, base_area, *args, **kargs):
100        self.strip_levels = base_area.depth
101        areas = base_area.get_descendants()
102        ModelChoiceField.__init__(self, queryset=areas,
103            help_text='In general, this should be a fully indented budget area, not one with children',
104            *args, **kargs)
105
106    def label_from_instance(self, obj,):
107        return obj.indented_name(strip_levels=self.strip_levels)
108
109class ExpenseAreasField(ModelChoiceField):
110    def __init__(self, *args, **kargs):
111        base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses'])
112        self.strip_levels = base_area.depth
113        areas = base_area.get_descendants()
114        ModelChoiceField.__init__(self, queryset=areas,
115            help_text='In general, this should be a fully indented budget area, not one with children',
116            *args, **kargs)
117
118    def label_from_instance(self, obj,):
119        return obj.indented_name(strip_levels=self.strip_levels)
120
121@user_passes_test(lambda u: u.is_authenticated())
122def submit_request(http_request, term, committee):
123    term_obj = get_object_or_404(BudgetTerm, slug=term)
124    comm_obj = get_object_or_404(BudgetArea, pk=committee)
125
126    new_request = ReimbursementRequest()
127    new_request.submitter = http_request.user.username
128    new_request.budget_term = term_obj
129
130    # Prefill from user information (itself prefilled from LDAP now)
131    initial = {}
132    initial['check_to_first_name'] = http_request.user.first_name
133    initial['check_to_last_name']  = http_request.user.last_name
134    initial['check_to_email']      = http_request.user.email
135
136    if http_request.method == 'POST': # If the form has been submitted...
137        form = RequestForm(http_request.POST, instance=new_request) # A form bound to the POST data
138        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
139        form.fields['expense_area'] = ExpenseAreasField()
140
141        if form.is_valid(): # All validation rules pass
142            request_obj = form.save()
143
144            # Send email
145            tmpl = get_template('vouchers/emails/request_submit_admin.txt')
146            ctx = Context({
147                'submitter': http_request.user,
148                'request': request_obj,
149            })
150            body = tmpl.render(ctx)
151            recipients = []
152            for name, addr in settings.ADMINS:
153                recipients.append(addr)
154            recipients.append(request_obj.budget_area.owner_address())
155            if settings.CC_SUBMITTER:
156                recipients.append(http_request.user.email)
157            send_mail(
158                '%sRequest submittal: %s requested $%s' % (
159                    settings.EMAIL_SUBJECT_PREFIX,
160                    http_request.user,
161                    request_obj.amount,
162                ),
163                body,
164                settings.SERVER_EMAIL,
165                recipients,
166            )
167
168            return HttpResponseRedirect(reverse(review_request, args=[new_request.pk],) + '?new=true') # Redirect after POST
169    else:
170        form = RequestForm(instance=new_request, initial=initial, ) # An unbound form
171        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
172        form.fields['expense_area'] = ExpenseAreasField()
173
174    context = {
175        'term':term_obj,
176        'comm':comm_obj,
177        'form':form,
178        'pagename':'request_reimbursement',
179    }
180    return render_to_response('vouchers/submit.html', context, context_instance=RequestContext(http_request), )
181
182class VoucherizeForm(Form):
183    name = django.forms.CharField(max_length=100, help_text='Signatory name for voucher',)
184    email = django.forms.EmailField(max_length=100, help_text='Signatory email for voucher')
185
186
187@user_passes_test(lambda u: u.is_authenticated())
188def review_request(http_request, object_id):
189    request_obj = get_object_or_404(ReimbursementRequest, pk=object_id)
190    user = http_request.user
191    pagename = 'request_reimbursement'
192    new = False
193    if 'new' in http_request.REQUEST:
194        if http_request.REQUEST['new'].upper() == 'TRUE':
195            new = True
196        else:
197            new = False
198
199    if (user.has_perm('vouchers.view_requests') or
200        user.username == request_obj.submitter or
201        user.email.upper() == request_obj.check_to_email.upper()
202        ):
203        pass
204    else:
205        return get_403_response(http_request, errmsg="You do not have permission to access this reimbursement request. You can only view requests you submitted or are the recipient for, unless you have general viewing permissions.", pagename=pagename, )
206
207    # DOCUMENTATION #
208    if request_obj.documentation:
209        doc_upload_form = None
210    else:
211        new_docs = Documentation()
212        new_docs.submitter = http_request.user.username
213        if http_request.method == 'POST' and 'upload_documentation' in http_request.REQUEST: # If the form has been submitted...
214            doc_upload_form = DocUploadForm(http_request.POST, http_request.FILES, instance=new_docs) # A form bound to the POST data
215
216            if doc_upload_form.is_valid(): # All validation rules pass
217                new_docs = doc_upload_form.save()
218                request_obj.documentation = new_docs
219                request_obj.save()
220
221                return HttpResponseRedirect(reverse(review_request, args=[object_id],)) # Redirect after POST
222        else:
223            doc_upload_form = DocUploadForm(instance=new_docs, ) # An unbound form
224
225    # SEND EMAILS
226    show_email = http_request.user.has_perm('vouchers.can_email')
227    if show_email:
228        email_message = ''
229        if http_request.method == 'POST' and 'send_email' in http_request.REQUEST:
230            mail = vouchers.models.stock_emails[http_request.REQUEST['email_name']]
231            assert mail.context == 'request'
232            mail.send_email_request(request_obj)
233            email_message = 'Sent email "%s".' % (mail.label, )
234        email_options = []
235        for mail in vouchers.models.stock_emails.values():
236            if mail.context == 'request':
237                email_options.append({
238                    'label': mail.label,
239                    'name' : mail.name,
240                })
241
242    # APPROVE VOUCHERS
243    show_approve = (http_request.user.has_perm('vouchers.can_approve')
244        and request_obj.approval_status == vouchers.models.APPROVAL_STATE_PENDING)
245    if show_approve:
246        # Voucherize form
247        # Prefill from certs / config
248        initial = {}
249        initial['name'] = '%s %s' % (http_request.user.first_name, http_request.user.last_name, )
250        if settings.SIGNATORY_EMAIL:
251            initial['email'] = settings.SIGNATORY_EMAIL
252        else:
253            initial['email'] = http_request.user.email
254
255        approve_message = ''
256        if http_request.method == 'POST' and 'approve' in http_request.REQUEST:
257            approve_form = VoucherizeForm(http_request.POST)
258            if approve_form.is_valid():
259                voucher = request_obj.convert(
260                    approve_form.cleaned_data['name'],
261                    signatory_email=approve_form.cleaned_data['email'],)
262                tmpl = get_template('vouchers/emails/request_approval_admin.txt')
263                ctx = Context({
264                    'approver': http_request.user,
265                    'request': request_obj,
266                })
267                body = tmpl.render(ctx)
268                mail_admins(
269                    'Request approval: %s approved $%s' % (
270                        http_request.user,
271                        request_obj.amount,
272                    ),
273                    body,
274                )
275                approve_message = 'Created new voucher from request'
276        else:
277            approve_form = VoucherizeForm(initial=initial)
278
279    context = {
280        'rr':request_obj,
281        'pagename':pagename,
282        'new': new,
283        'doc_form': doc_upload_form,
284    }
285    if show_approve:
286        context['approve_form'] = approve_form
287        context['approve_message'] = approve_message
288    if show_email:
289        context['email_options'] = email_options
290        context['email_message'] = email_message
291    return render_to_response('vouchers/ReimbursementRequest_review.html', context, context_instance=RequestContext(http_request), )
292
293@user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers'))
294def generate_vouchers(http_request, *args):
295    unprocessed = True
296    if 'unprocessed' in http_request.REQUEST:
297        if http_request.REQUEST['unprocessed'].upper() == 'TRUE':
298            unprocessed = True
299        else:
300            unprocessed = False
301    mark = True
302    if 'mark' in http_request.REQUEST:
303        if http_request.REQUEST['mark'].upper() == 'TRUE':
304            mark = True
305        else:
306            mark = False
307
308    lst = vouchers.models.Voucher.objects.all()
309    if unprocessed:
310        lst = lst.filter(processed=False)
311
312    total = decimal.Decimal('0.00')
313    for voucher in lst:
314        total = total + voucher.amount
315
316    context = {
317        'vouchers': lst,
318        'total': total,
319        'MEDIA_ROOT': settings.MEDIA_ROOT,
320    }
321    response = render_to_response(
322        'vouchers/vouchers.tex',
323        context, context_instance=RequestContext(http_request),
324        mimetype=settings.LATEX_MIMETYPE,
325    )
326
327    # Send mail
328    tmpl = get_template('vouchers/emails/vouchers_tex.txt')
329    ctx = Context({
330        'converter': http_request.user,
331        'vouchers': lst,
332        'mark': mark,
333        'unprocessed': unprocessed,
334    })
335    body = tmpl.render(ctx)
336    mail_admins(
337        'Voucher rendering: %d by %s' % (
338            len(lst),
339            http_request.user,
340        ),
341        body,
342    )
343
344    if mark:
345        for voucher in lst:
346            voucher.mark_processed()
347
348    return response
349
350def get_related_requests_qobj(user, ):
351    return Q(submitter=user.username) | Q(check_to_email=user.email)
352
353request_list_orders = (
354#   Name            Label               Columns
355    ('default',     'Default',          ()),
356    ('id',          'ID',               ('id', )),
357    ('state',       'Approval Status',  ('approval_status', )),
358    ('stateamount', 'Approval Status, then amount',  ('approval_status', 'amount', )),
359    ('stateto',     'Approval Status, then recipient',  ('approval_status', 'check_to_first_name', 'check_to_last_name', )),
360    ('statesubmit', 'Approval Status, then submitter',  ('approval_status', 'submitter', )),
361    ('name',        'Request Name',     ('name', )),
362    ('amount',      'Amount',           ('amount', )),
363    ('check_to',    'Check Recipient',  ('check_to_first_name', 'check_to_last_name', )),
364    ('submitter',   'Submitter',        ('submitter', )),
365)
366
367@login_required
368def show_requests(request, ):
369    if request.user.has_perm('vouchers.can_list'):
370        qs = ReimbursementRequest.objects.all()
371        useronly = False
372    else:
373        qs = ReimbursementRequest.objects.filter(get_related_requests_qobj(request.user))
374        useronly = True
375
376    if 'order' in request.REQUEST:
377        order_row = [row for row in request_list_orders if row[0] == request.REQUEST['order']]
378        if order_row:
379            order, label, cols = order_row[0]
380            qs = qs.order_by(*cols)
381        else:
382            raise Http404('Order by constraint not known')
383    else:
384        order = 'default'
385
386    if 'approval_status' in request.REQUEST:
387        approval_status = request.REQUEST['approval_status']
388    else:
389        approval_status = vouchers.models.APPROVAL_STATE_PENDING
390    if approval_status == 'all':
391        pass
392    else:
393        try:
394            approval_status = int(approval_status)
395        except ValueError:
396            raise Http404('approval_status poorly formatted')
397        state_row = [row for row in vouchers.models.APPROVAL_STATES if row[0] == approval_status]
398        if state_row:
399            qs = qs.filter(approval_status=approval_status)
400        else:
401            raise Http404('approval_status not known')
402
403    return list_detail.object_list(
404        request,
405        queryset=qs,
406        extra_context={
407            'useronly': useronly,
408            'order'   : order,
409            'orders'  : request_list_orders,
410            'approval_status' : approval_status,
411            'approval_states':  vouchers.models.APPROVAL_STATES,
412            'pagename': 'list_requests',
413        },
414    )
Note: See TracBrowser for help on using the repository browser.