source: remit/vouchers/views.py @ d601b0b

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

Make depth of the committee hierarchy configurable

The UA and ESP have a two-level hierarchy:

UA:

  • General
    • Senate, Exec, Operating
  • Committees
    • All the committees

ESP:

  • Programs
    • All the programs
  • Officers
    • All the officers

However, many groups are likely to have a natural single-level hierarchy,
and Remit should not force them to contort their accounting to fit
Remit's needs.

  • Property mode set to 100644
File size: 12.6 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
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.template import Context, Template
17from django.template.loader import get_template
18
19import settings
20
21class RequestForm(ModelForm):
22    class Meta:
23        model = ReimbursementRequest
24        fields = (
25            'name',
26            'description',
27            'incurred_time',
28            'amount',
29            'budget_area',
30            'expense_area',
31            'check_to_first_name',
32            'check_to_last_name',
33            'check_to_email',
34            'check_to_addr',
35        )
36
37
38class CommitteesField(ModelChoiceField):
39    def __init__(self, *args, **kargs):
40        base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH)
41        self.strip_levels = base_area.depth
42        areas = (base_area.get_descendants()
43            .filter(depth__lte=base_area.depth+settings.COMMITTEE_HIERARCHY_LEVELS)
44            .exclude(name='Holding')
45        )
46        ModelChoiceField.__init__(self, queryset=areas,
47            help_text='Select the appropriate committe or other budget area',
48            *args, **kargs)
49
50    def label_from_instance(self, obj,):
51        return obj.indented_name(strip_levels=self.strip_levels)
52
53class SelectRequestBasicsForm(Form):
54    area = CommitteesField()
55    term = ModelChoiceField(queryset = BudgetTerm.objects.all())
56
57class DocUploadForm(ModelForm):
58    def clean_backing_file(self, ):
59        f = self.cleaned_data['backing_file']
60        ext = f.name.rsplit('.')[-1]
61        contenttype = f.content_type
62        if ext != 'pdf':
63            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a .%s file" % (ext, ))
64        elif contenttype != 'application/pdf':
65            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a %s file" % (contenttype, ))
66        else:
67            return f
68
69    class Meta:
70        model = Documentation
71        fields = (
72            'label',
73            'backing_file',
74        )
75
76
77@user_passes_test(lambda u: u.is_authenticated())
78def select_request_basics(http_request, ):
79    if http_request.method == 'POST': # If the form has been submitted...
80        form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data
81        if form.is_valid(): # All validation rules pass
82            term = form.cleaned_data['term'].slug
83            area = form.cleaned_data['area'].id
84            return HttpResponseRedirect(reverse(submit_request, args=[term, area],)) # Redirect after POST
85    else:
86        form = SelectRequestBasicsForm() # An unbound form
87
88    context = {
89        'form':form,
90        'pagename':'request_reimbursement',
91    }
92    return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), )
93
94class CommitteeBudgetAreasField(ModelChoiceField):
95    def __init__(self, base_area, *args, **kargs):
96        self.strip_levels = base_area.depth
97        areas = base_area.get_descendants()
98        ModelChoiceField.__init__(self, queryset=areas,
99            help_text='In general, this should be a fully indented budget area, not one with children',
100            *args, **kargs)
101
102    def label_from_instance(self, obj,):
103        return obj.indented_name(strip_levels=self.strip_levels)
104
105class ExpenseAreasField(ModelChoiceField):
106    def __init__(self, *args, **kargs):
107        base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses'])
108        self.strip_levels = base_area.depth
109        areas = base_area.get_descendants()
110        ModelChoiceField.__init__(self, queryset=areas,
111            help_text='In general, this should be a fully indented budget area, not one with children',
112            *args, **kargs)
113
114    def label_from_instance(self, obj,):
115        return obj.indented_name(strip_levels=self.strip_levels)
116
117@user_passes_test(lambda u: u.is_authenticated())
118def submit_request(http_request, term, committee):
119    term_obj = get_object_or_404(BudgetTerm, slug=term)
120    comm_obj = get_object_or_404(BudgetArea, pk=committee)
121
122    new_request = ReimbursementRequest()
123    new_request.submitter = http_request.user.username
124    new_request.budget_term = term_obj
125
126    # Prefill from user information (itself prefilled from LDAP now)
127    initial = {}
128    initial['check_to_first_name'] = http_request.user.first_name
129    initial['check_to_last_name']  = http_request.user.last_name
130    initial['check_to_email']      = http_request.user.email
131
132    if http_request.method == 'POST': # If the form has been submitted...
133        form = RequestForm(http_request.POST, instance=new_request) # A form bound to the POST data
134        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
135        form.fields['expense_area'] = ExpenseAreasField()
136
137        if form.is_valid(): # All validation rules pass
138            request_obj = form.save()
139
140            # Send email
141            tmpl = get_template('vouchers/emails/request_submit_admin.txt')
142            ctx = Context({
143                'submitter': http_request.user,
144                'request': request_obj,
145            })
146            body = tmpl.render(ctx)
147            recipients = []
148            for name, addr in settings.ADMINS:
149                recipients.append(addr)
150            recipients.append(request_obj.budget_area.owner_address())
151            send_mail(
152                '%sRequest submittal: %s requested $%s' % (
153                    settings.EMAIL_SUBJECT_PREFIX,
154                    http_request.user,
155                    request_obj.amount,
156                ),
157                body,
158                settings.SERVER_EMAIL,
159                recipients,
160            )
161
162            return HttpResponseRedirect(reverse(review_request, args=[new_request.pk],) + '?new=true') # Redirect after POST
163    else:
164        form = RequestForm(instance=new_request, initial=initial, ) # An unbound form
165        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
166        form.fields['expense_area'] = ExpenseAreasField()
167
168    context = {
169        'term':term_obj,
170        'comm':comm_obj,
171        'form':form,
172        'pagename':'request_reimbursement',
173    }
174    return render_to_response('vouchers/submit.html', context, context_instance=RequestContext(http_request), )
175
176class VoucherizeForm(Form):
177    name = django.forms.CharField(max_length=100, help_text='Signatory name for voucher',)
178    email = django.forms.EmailField(max_length=100, help_text='Signatory email for voucher')
179
180
181@user_passes_test(lambda u: u.is_authenticated())
182def review_request(http_request, object_id):
183    request_obj = get_object_or_404(ReimbursementRequest, pk=object_id)
184    user = http_request.user
185    pagename = 'request_reimbursement'
186    new = False
187    if 'new' in http_request.REQUEST:
188        if http_request.REQUEST['new'].upper() == 'TRUE':
189            new = True
190        else:
191            new = False
192
193    if (user.has_perm('vouchers.view_requests') or
194        user.username == request_obj.submitter or
195        user.email.upper() == request_obj.check_to_email.upper()
196        ):
197        pass
198    else:
199        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, )
200
201    # DOCUMENTATION #
202    if request_obj.documentation:
203        doc_upload_form = None
204    else:
205        new_docs = Documentation()
206        new_docs.submitter = http_request.user.username
207        if http_request.method == 'POST' and 'upload_documentation' in http_request.REQUEST: # If the form has been submitted...
208            doc_upload_form = DocUploadForm(http_request.POST, http_request.FILES, instance=new_docs) # A form bound to the POST data
209
210            if doc_upload_form.is_valid(): # All validation rules pass
211                new_docs = doc_upload_form.save()
212                request_obj.documentation = new_docs
213                request_obj.save()
214
215                return HttpResponseRedirect(reverse(review_request, args=[object_id],)) # Redirect after POST
216        else:
217            doc_upload_form = DocUploadForm(instance=new_docs, ) # An unbound form
218
219    # SEND EMAILS
220    show_email = http_request.user.has_perm('vouchers.can_email')
221    if show_email:
222        email_message = ''
223        if http_request.method == 'POST' and 'send_email' in http_request.REQUEST:
224            mail = vouchers.models.stock_emails[http_request.REQUEST['email_name']]
225            assert mail.context == 'request'
226            mail.send_email_request(request_obj)
227            email_message = 'Sent email "%s".' % (mail.label, )
228        email_options = []
229        for mail in vouchers.models.stock_emails.values():
230            if mail.context == 'request':
231                email_options.append({
232                    'label': mail.label,
233                    'name' : mail.name,
234                })
235
236    # APPROVE VOUCHERS
237    show_approve = (http_request.user.has_perm('vouchers.can_approve')
238        and request_obj.approval_status == vouchers.models.APPROVAL_STATE_PENDING)
239    if show_approve:
240        # Voucherize form
241        # Prefill from certs / config
242        initial = {}
243        initial['name'] = '%s %s' % (http_request.user.first_name, http_request.user.last_name, )
244        if settings.SIGNATORY_EMAIL:
245            initial['email'] = settings.SIGNATORY_EMAIL
246        else:
247            initial['email'] = http_request.user.email
248
249        approve_message = ''
250        if http_request.method == 'POST' and 'approve' in http_request.REQUEST:
251            approve_form = VoucherizeForm(http_request.POST)
252            if approve_form.is_valid():
253                voucher = request_obj.convert(
254                    approve_form.cleaned_data['name'],
255                    signatory_email=approve_form.cleaned_data['email'],)
256                tmpl = get_template('vouchers/emails/request_approval_admin.txt')
257                ctx = Context({
258                    'approver': http_request.user,
259                    'request': request_obj,
260                })
261                body = tmpl.render(ctx)
262                mail_admins(
263                    'Request approval: %s approved $%s' % (
264                        http_request.user,
265                        request_obj.amount,
266                    ),
267                    body,
268                )
269                approve_message = 'Created new voucher from request'
270        else:
271            approve_form = VoucherizeForm(initial=initial)
272
273    context = {
274        'rr':request_obj,
275        'pagename':pagename,
276        'new': new,
277        'doc_form': doc_upload_form,
278    }
279    if show_approve:
280        context['approve_form'] = approve_form
281        context['approve_message'] = approve_message
282    if show_email:
283        context['email_options'] = email_options
284        context['email_message'] = email_message
285    return render_to_response('vouchers/ReimbursementRequest_review.html', context, context_instance=RequestContext(http_request), )
286
287@user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers'))
288def generate_vouchers(http_request, *args):
289    unprocessed = True
290    if 'unprocessed' in http_request.REQUEST:
291        if http_request.REQUEST['unprocessed'].upper() == 'TRUE':
292            unprocessed = True
293        else:
294            unprocessed = False
295    mark = True
296    if 'mark' in http_request.REQUEST:
297        if http_request.REQUEST['mark'].upper() == 'TRUE':
298            mark = True
299        else:
300            mark = False
301
302    lst = vouchers.models.Voucher.objects.all()
303    if unprocessed:
304        lst = lst.filter(processed=False)
305
306    context = {
307        'vouchers': lst,
308        'MEDIA_ROOT': settings.MEDIA_ROOT,
309    }
310    response = render_to_response('vouchers/vouchers.tex', context, context_instance=RequestContext(http_request), )
311
312    # Send mail
313    tmpl = get_template('vouchers/emails/vouchers_tex.txt')
314    ctx = Context({
315        'converter': http_request.user,
316        'vouchers': lst,
317        'mark': mark,
318        'unprocessed': unprocessed,
319    })
320    body = tmpl.render(ctx)
321    mail_admins(
322        'Voucher rendering: %d by %s' % (
323            len(lst),
324            http_request.user,
325        ),
326        body,
327    )
328
329    if mark:
330        for voucher in lst:
331            voucher.mark_processed()
332
333    return response
Note: See TracBrowser for help on using the repository browser.