def banktransactions_match(request, transid): authenticate_backend_group(request, 'Invoice managers') trans = get_object_or_404(PendingBankTransaction, pk=transid) invoices = Invoice.objects.filter(finalized=True, paidat__isnull=True, deleted=False).order_by('invoicedate') def _match_invoice(i): matchinfos = [] if i.total_amount == trans.amount: matchinfos.append('Amount matches exact') if i.payment_reference in trans.transtext.replace(' ', ''): matchinfos.append('Payment reference found') return { 'matchinfo': ",\n".join(matchinfos), 'matchlabel': len(matchinfos) > 0 and 'success' or '', 'i': i, } im = map(_match_invoice, invoices) pm = trans.method.get_implementation() matchers = PendingBankMatcher.objects.filter(foraccount__num=pm.config('bankaccount'), amount=trans.amount) return render(request, 'invoices/banktransactions_match.html', { 'transaction': trans, 'invoice_matchinfo': im, 'matchers': matchers, 'topadmin': 'Invoices', 'breadcrumbs': [('/admin/invoices/banktransactions/', 'Pending bank transactions'), ], 'helplink': 'payment', })
def new(request, year): authenticate_backend_group(request, 'Accounting managers') year = int(year) # Default the date to the same date as the last entry for this year, # provided one exists. Otherwise, just the start of the year. try: lastentry = JournalEntry.objects.filter(year=year).order_by('-date')[0] d = lastentry.date except IndexError: d = date(year, 1, 1) year = get_object_or_404(Year, year=year) highseq = JournalEntry.objects.filter(year=year).aggregate( Max('seq'))['seq__max'] if highseq is None: highseq = 0 entry = JournalEntry(year=year, seq=highseq + 1, date=d, closed=False) entry.save() # Disable any search query to make sure we can actually see # the record we've just created. _setup_search(request, '') return HttpResponseRedirect('/accounting/e/%s/' % entry.pk)
def emailinvoice(request, invoicenum): authenticate_backend_group(request, 'Invoice managers') if request.method != 'POST': raise HttpResponse('Must be POST', status=401) if 'reason' not in request.POST: return HttpResponse('Reason is missing!', status=401) if request.POST['reason'] not in ('initial', 'reminder'): return HttpResponse('Invalid reason given!', status=401) invoice = get_object_or_404(Invoice, pk=invoicenum) if not invoice.finalized: return HttpResponse("Not finalized!", status=401) # Ok, it seems we're good to go... wrapper = InvoiceWrapper(invoice) if request.POST['reason'] == 'initial': wrapper.email_invoice() elif request.POST['reason'] == 'reminder': wrapper.email_reminder() else: raise Exception("Cannot happen") return HttpResponse("OK")
def search(request): authenticate_backend_group(request, 'Invoice managers') term = request.GET['term'] upstream = request.GET.get('upstream', False) users = User.objects.filter( Q(username__icontains=term) | Q(first_name__icontains=term) | Q(last_name__icontains=term) | Q(email__icontains=term) ) if users: return HttpResponse(json.dumps([{'ui': u.id, 'u': u.username, 'n': u.first_name + ' ' + u.last_name, 'e': u.email} for u in users]), content_type='application/json') if not upstream: return HttpResponse('[]', content_type='application/json') # Perform upstream search users = user_search(term) # All users need a negative id so we can differentiate them for n in range(0, len(users)): users[n]['i'] = -1 - n return HttpResponse(json.dumps([{'ui': u['i'], 'u': u['u'], 'n': u['f'] + ' ' + u['l'], 'e': u['e'], } for u in users]), content_type='application/json')
def _homeview(request, invoice_objects, unpaid=False, pending=False, deleted=False, paid=False, searchterm=None): # Utility function for all main invoice views, so make the shared permissions # check here. authenticate_backend_group(request, 'Invoice managers') # Add info about refunds to all invoices invoice_objects = invoice_objects.extra(select={ 'has_refund': 'EXISTS (SELECT 1 FROM invoices_invoicerefund r WHERE r.invoice_id=invoices_invoice.id)', }) # Render a list of all invoices (invoices, paginator, page_range) = simple_pagination(request, invoice_objects, 50) has_pending = Invoice.objects.filter(finalized=False).exists() has_unpaid = Invoice.objects.filter(finalized=True, paidat__isnull=False).exists() return render(request, 'invoices/home.html', { 'invoices': invoices, 'paid': paid, 'unpaid': unpaid, 'pending': pending, 'deleted': deleted, 'has_pending': has_pending, 'has_unpaid': has_unpaid, 'searchterm': searchterm, 'page_range': page_range, 'breadcrumbs': [('/invoiceadmin/', 'Invoices'), ], 'helplink': 'payment', })
def bankfile_transaction_methodchoice(request): authenticate_backend_group(request, 'Invoice managers') methods = InvoicePaymentMethod.objects.filter( config__has_key='file_upload_interval').order_by('internaldescription') if not methods: # Should never happen since the button is only visible if they exist messages.error(request, "No managed bank providers configured!") return HttpResponseRedirect("/admin/") if len(methods) == 1: # Only one, so no need for prompt return HttpResponseRedirect("{}/".format(methods[0].id)) if request.method == 'POST': form = BankfilePaymentMethodChoiceForm(methods=methods, data=request.POST) if form.is_valid(): return HttpResponseRedirect("{}/".format( form.cleaned_data['paymentmethod'].id)) else: form = BankfilePaymentMethodChoiceForm(methods=methods) return render( request, 'confreg/admin_backend_form.html', { 'basetemplate': 'adm/admin_base.html', 'form': form, 'whatverb': 'View', 'what': 'bank transactions', 'savebutton': 'View transactions', 'cancelurl': '/admin/', 'cancelname': 'Back', 'topadmin': 'Invoices', 'helplink': 'payment', })
def meeting_log(request, meetingid): authenticate_backend_group(request, 'Membership administrators') meeting = get_object_or_404(Meeting, pk=meetingid) if meeting.meetingtype != MeetingType.WEB: messages.warning(request, "Meeting log is only available for web meetings") return HttpResponseRedirect("../") log = MeetingMessageLog.objects.select_related('sender').only( 't', 'message', 'sender__fullname').filter(meeting=meeting) if request.method == 'POST': with transaction.atomic(): curs = connection.cursor() curs.execute( """DELETE FROM membership_meetingmessagelog l WHERE meeting_id=%(meetingid)s AND ( t < (SELECT min(t) FROM membership_meetingmessagelog l2 WHERE l2.meeting_id=%(meetingid)s AND l2.message='This meeting is now open.') OR t > (SELECT max(t) FROM membership_meetingmessagelog l3 WHERE l3.meeting_id=%(meetingid)s AND l3.message='This meeting is now finished.') )""", {'meetingid': meetingid}) messages.info( request, 'Removed {} entries from meeting {}.'.format( curs.rowcount, meetingid)) return HttpResponseRedirect(".") if request.GET.get('format', None) == 'csv': response = HttpResponse(content_type='text/csv; charset=utf8') response[ 'Content-Disposition'] = 'attachment;filename={} log.csv'.format( meeting.name) c = csv.writer(response, delimiter=';') c.writerow(['Time', 'Sender', 'Text']) for l in log: c.writerow([l.t, l.sender.fullname if l.sender else '', l.message]) return response else: log = list( log.extra( select={ 'inmeeting': "CASE WHEN t < (SELECT min(t) FROM membership_meetingmessagelog l2 WHERE l2.meeting_id=membership_meetingmessagelog.meeting_id AND message='This meeting is now open.') OR t > (SELECT max(t) FROM membership_meetingmessagelog l3 WHERE l3.meeting_id=membership_meetingmessagelog.meeting_id AND message='This meeting is now finished.') THEN false ELSE true END", })) return render( request, 'membership/meeting_log.html', { 'meeting': meeting, 'log': log, 'numextra': sum(0 if l.inmeeting else 1 for l in log), 'topadmin': 'Membership', 'breadcrumbs': ( ('/admin/membership/meetings/', 'Meetings'), ('/admin/membership/meetings/{}/'.format( meeting.pk), meeting.name), ), })
def sendmail(request): authenticate_backend_group(request, 'Membership administrators') if request.method == 'POST': idlist = list(map(int, request.POST['idlist'].split(','))) else: idlist = list(map(int, request.GET['idlist'].split(','))) cfg = get_config() recipients = Member.objects.filter(pk__in=idlist) initial = { '_from': '{0} <{1}>'.format(settings.ORG_NAME, cfg.sender_email), 'recipients': escape(", ".join([ '{0} <{1}>'.format(x.fullname, x.user.email) for x in recipients ])), 'idlist': ",".join(map(str, idlist)), } if request.method == 'POST': p = request.POST.copy() p['recipients'] = initial['recipients'] form = BackendMemberSendEmailForm(data=p, initial=initial) if form.is_valid(): with transaction.atomic(): for r in recipients: msgtxt = "{0}\n\n-- \nThis message was sent to members of {1}\n".format( form.cleaned_data['message'], settings.ORG_NAME) send_simple_mail( cfg.sender_email, r.user.email, form.cleaned_data['subject'], msgtxt, sendername=settings.ORG_NAME, receivername=r.fullname, ) messages.info(request, "Email sent to %s attendees" % len(recipients)) return HttpResponseRedirect("../") else: form = BackendMemberSendEmailForm(initial=initial) return render( request, 'confreg/admin_backend_form.html', { 'basetemplate': 'adm/admin_base.html', 'form': form, 'what': 'new email', 'savebutton': 'Send email', 'cancelurl': '../', 'breadcrumbs': [ ('../', 'Members'), ], })
def viewreceipt(request, invoiceid): invoice = get_object_or_404(Invoice, pk=invoiceid) if invoice.recipient_user != request.user: # End users can only view their own invoices, but invoice managers can view all authenticate_backend_group(request, 'Invoice managers') r = HttpResponse(content_type='application/pdf') r.write(base64.b64decode(invoice.pdf_receipt)) return r
def viewinvoice(request, invoiceid): invoice = get_object_or_404(Invoice, pk=invoiceid, deleted=False, finalized=True) if invoice.recipient_user != request.user: # End users can only view their own invoices, but invoice managers can view all authenticate_backend_group(request, 'Invoice managers') return render(request, 'invoices/userinvoice.html', { 'invoice': InvoicePresentationWrapper(invoice, "%s/invoices/%s/" % (settings.SITEBASE, invoice.pk)), })
def invoicepayment(request, methodid, invoiceid): invoice = get_object_or_404(Invoice, pk=invoiceid, deleted=False, finalized=True) if invoice.recipient_user != request.user: authenticate_backend_group(request, 'Invoice managers') return _invoice_payment(request, methodid, invoice)
def refundinvoice(request, invoicenum): authenticate_backend_group(request, 'Invoice managers') invoice = get_object_or_404(Invoice, pk=invoicenum) if request.method == 'POST': form = RefundForm(data=request.POST, invoice=invoice) if form.is_valid(): # Do some sanity checking if form.cleaned_data['vatrate']: vatamount = (Decimal(form.cleaned_data['amount']) * form.cleaned_data['vatrate'].vatpercent / Decimal(100)).quantize(Decimal('0.01')) if vatamount > invoice.total_refunds['remaining']['vatamount']: messages.error(request, "Unable to refund, VAT amount mismatch!") return HttpResponseRedirect('.') else: vatamount = 0 mgr = InvoiceManager() r = mgr.refund_invoice( invoice, form.cleaned_data['reason'], Decimal(form.cleaned_data['amount']), vatamount, form.cleaned_data['vatrate'], ) if invoice.can_autorefund: messages.info(request, "Refund initiated.") else: messages.info(request, "Refund flagged.") return HttpResponseRedirect(".") else: form = RefundForm(invoice=invoice) # Check if all invoicerows have the same VAT rate (NULL or specified) vinfo = invoice.invoicerow_set.all().aggregate(n=Count('vatrate', distinct=True), v=Max('vatrate')) return render( request, 'invoices/refundform.html', { 'form': form, 'invoice': invoice, 'breadcrumbs': [ ('/invoiceadmin/', 'Invoices'), ('/invoiceadmin/{0}/'.format( invoice.pk), 'Invoice #{0}'.format(invoice.pk)), ], 'helplink': 'payment', })
def previewinvoice(request, invoicenum): authenticate_backend_group(request, 'Invoice managers') invoice = get_object_or_404(Invoice, pk=invoicenum) # We assume there is no PDF yet wrapper = InvoiceWrapper(invoice) r = HttpResponse(content_type='application/pdf') r.write(wrapper.render_pdf_invoice(True)) return r
def importuser(request): authenticate_backend_group(request, 'Invoice managers') uid = request.POST['uid'] try: user_import(uid) except Exception as e: return HttpResponse('%s' % e, content_type='text/plain') return HttpResponse('OK', content_type='text/plain')
def banktransactions_match_multiple(request, transid): authenticate_backend_group(request, 'Invoice managers') trans = get_object_or_404(PendingBankTransaction, pk=transid) pm = trans.method.get_implementation() invoices = [ get_object_or_404(Invoice, pk=invoiceid) for invoiceid in request.GET.getlist('invoiceid') ] if not invoices: invoices = [ get_object_or_404(Invoice, pk=invoiceid) for invoiceid in request.POST.get('invoiceidlist').split(',') ] if len(invoices) == 0: raise Http404("No invoices") if request.method == 'POST': r = _flag_invoices(request, trans, invoices, pm, None) if r: return HttpResponseRedirect("/admin/invoices/banktransactions/") else: return HttpResponseRedirect(".") total_amount = sum([i.total_amount for i in invoices]) return render( request, 'invoices/banktransactions_match_invoice.html', { 'transaction': trans, 'invoices': invoices, 'topadmin': 'Invoices', 'match': { 'amountdiff': total_amount - trans.amount, 'absdiff': abs(total_amount - trans.amount), 'percentdiff': (abs(total_amount - trans.amount) / total_amount) * 100, }, 'cantmatch': (total_amount != trans.amount), 'breadcrumbs': [ ('/admin/invoices/banktransactions/', 'Pending bank transactions'), ('/admin/invoices/banktransactions/{0}/'.format( trans.id), 'Transaction'), ], 'helplink': 'payment', })
def banktransactions_match_invoice(request, transid, invoiceid): authenticate_backend_group(request, 'Invoice managers') trans = get_object_or_404(PendingBankTransaction, pk=transid) invoice = get_object_or_404(Invoice, pk=invoiceid) pm = trans.method.get_implementation() if request.method == 'POST': if pm.config('feeaccount'): fee_account = Account.objects.get(num=pm.config('feeaccount')) else: fee_account = get_object_or_404(Account, num=request.POST['account']) r = _flag_invoices(request, trans, [invoice, ], pm, fee_account) if r: return HttpResponseRedirect("/admin/invoices/banktransactions/") else: return HttpResponseRedirect(".") # Generate the form if pm.config('feeaccount'): fee_account = Account.objects.get(num=pm.config('feeaccount')) accounts = [] else: fee_account = None accounts = get_account_choices() return render(request, 'invoices/banktransactions_match_invoice.html', { 'transaction': trans, 'invoices': [invoice, ], 'topadmin': 'Invoices', 'fee_account': fee_account, 'accounts': accounts, 'match': { 'amountdiff': invoice.total_amount - trans.amount, 'absdiff': abs(invoice.total_amount - trans.amount), 'percentdiff': (abs(invoice.total_amount - trans.amount) / invoice.total_amount) * 100, 'found_ref': invoice.payment_reference in trans.transtext, 'found_id': str(invoice.id) in trans.transtext, 'highlight_ref': re.sub('({0})'.format(invoice.payment_reference), r'<strong>\1</strong>', escape(trans.transtext)), 'highlight_id': re.sub('({0})'.format(invoice.id), r'<strong>\1</strong>', escape(trans.transtext)), }, 'breadcrumbs': [ ('/admin/invoices/banktransactions/', 'Pending bank transactions'), ('/admin/invoices/banktransactions/{0}/'.format(trans.id), 'Transaction'), ], 'helplink': 'payment', })
def bankfile_transactions(request, methodid): authenticate_backend_group(request, 'Invoice managers') method = get_object_or_404(InvoicePaymentMethod, pk=methodid) # Needed for backlinks methodcount = InvoicePaymentMethod.objects.filter( config__has_key='file_upload_interval').count() backbutton = "../" breadlabel = "Bank transactions" if methodcount == 1: # If there is only one method, we have to return all the way back to the index page, or we'll # just get redirected back to ourselves. backbutton = "/admin/" q = Q(method=method) if 'file' in request.GET: q = q & Q(fromfile=get_int_or_error(request.GET, 'file')) backbutton = "../../" breadlabel = "Bankfiles" allrows = BankStatementRow.objects.filter(q).order_by('-date', 'id') (rows, paginator, page_range) = simple_pagination(request, allrows, 50) extrakeys = set() hasvaluefor = { 'uniqueid': False, 'balance': False, } for r in rows: extrakeys.update(r.other.keys()) for k in hasvaluefor.keys(): if getattr(r, k, None): hasvaluefor[k] = True params = request.GET.copy() if 'page' in params: del params['page'] return render( request, 'invoices/bankfile_transactions.html', { 'rows': rows, 'extrakeys': extrakeys, 'hasvaluefor': hasvaluefor, 'page_range': page_range, 'topadmin': 'Invoices', 'helplink': 'payment', 'requestparams': params.urlencode(), 'breadcrumbs': [ (backbutton, breadlabel), ], 'backbutton': backbutton, })
def banktransactions_match_matcher(request, transid, matcherid): authenticate_backend_group(request, 'Invoice managers') trans = get_object_or_404(PendingBankTransaction, pk=transid) matcher = get_object_or_404(PendingBankMatcher, pk=matcherid) pm = trans.method.get_implementation() if request.method == 'POST': if trans.amount != matcher.amount: # Should not happen, but let's make sure messages.error(request, "Amount mismatch") return HttpResponseRedirect(".") if matcher.journalentry.closed: messages.error(request, "Accounting entry already closed") return HttpResponseRedirect(".") # The whole point of what we do here is to ignore the text of the match, # so once the amount is correct, we just complete it. matcher.journalentry.closed = True matcher.journalentry.save() InvoiceLog( message= "Manually matched bank transaction of {0}{1} with text {2} to journal entry {3}." .format( trans.amount, settings.CURRENCY_ABBREV, trans.transtext, matcher.journalentry, )).save() # Remove both the pending transaction *and* the pending matcher trans.delete() matcher.delete() return HttpResponseRedirect("../../") return render( request, 'invoices/banktransactions_match_matcher.html', { 'transaction': trans, 'matcher': matcher, 'topadmin': 'Invoices', 'breadcrumbs': [ ('/admin/invoices/banktransactions/', 'Pending bank transactions'), ('/admin/invoices/banktransactions/{0}/'.format( trans.id), 'Transaction'), ], 'helplink': 'payment', })
def edit_meeting(request, rest): authenticate_backend_group(request, 'Membership administrators') return backend_list_editor(request, None, BackendMeetingForm, rest, bypass_conference_filter=True, topadmin='Membership', return_url='/admin/', )
def edit_vatvalidationcache(request, rest): authenticate_backend_group(request, 'Invoice managers') return backend_list_editor(request, None, BackendVatValidationCacheForm, rest, bypass_conference_filter=True, topadmin='Invoices', return_url='/admin/', )
def bankfiles(request): authenticate_backend_group(request, 'Invoice managers') if request.method == 'POST': # Uploading a file! method = get_object_or_404(InvoicePaymentMethod, active=True, config__has_key='file_upload_interval', id=get_int_or_error(request.POST, 'id')) if 'f' not in request.FILES: messages.error(request, "No file included in upload") elif request.FILES['f'].size < 1: messages.error(request, "Uploaded file is empty") else: f = request.FILES['f'] impl = method.get_implementation() try: (contents, numrows, numtrans, numpending) = impl.parse_uploaded_file(f) BankFileUpload( method=method, uploadby=request.user.username, name=f.name, textcontents=contents, parsedrows=numrows, newtrans=numtrans, newpending=numpending, ).save() messages.info( request, "File uploaded. {} rows parsed, {} transactions stored, resulting in {} pending transactions." .format(numrows, numtrans, numpending)) return HttpResponseRedirect('.') except Exception as e: messages.error(request, "Error uploading file: {}".format(e)) methods = InvoicePaymentMethod.objects.filter( active=True, config__has_key='file_upload_interval').annotate( latest_file=Max('bankfileupload__created')) file_objects = BankFileUpload.objects.select_related( 'method').all().order_by('-created')[:1000] (files, paginator, page_range) = simple_pagination(request, file_objects, 50) return render( request, 'invoices/bankfiles.html', { 'files': files, 'page_range': page_range, 'methods': methods, 'topadmin': 'Invoices', 'helplink': 'payment', })
def edit_object(request, rest): authenticate_backend_group(request, 'Accounting managers') return backend_list_editor( request, None, BackendObjectForm, rest, bypass_conference_filter=True, topadmin='Accounting', return_url='/admin/', )
def edit_election(request, rest): authenticate_backend_group(request, 'Election administrators') return backend_list_editor( request, None, BackendElectionForm, rest, bypass_conference_filter=True, topadmin='Elections', return_url='/admin/', )
def edit_news(request, rest): authenticate_backend_group(request, 'News administrators') return backend_list_editor( request, None, BackendNewsForm, rest, bypass_conference_filter=True, topadmin='News', return_url='/admin/', )
def member_email_list(request): authenticate_backend_group(request, 'Membership administrators') return render( request, 'membership/admin_email_list.html', { 'mails': MemberMail.objects.only('sentat', 'subject').order_by('-sentat'), 'helplink': 'membership', 'breadcrumbs': [ ('../../', 'Membership'), ], })
def edit_postqueue(request, rest): authenticate_backend_group(request, 'News administrators') return backend_list_editor( request, None, BackendPostQueueForm, rest, bypass_conference_filter=True, object_queryset=ConferenceTweetQueue.objects.filter( conference__isnull=True), topadmin='News', return_url='/admin/', )
def refunds(request): authenticate_backend_group(request, 'Invoice managers') refund_objects = InvoiceRefund.objects.\ select_related('invoice', 'invoice__paidusing').\ only('id', 'invoice_id', 'completed', 'issued', 'registered', 'reason', 'invoice__paidusing__internaldescription').\ order_by(F('completed').desc(nulls_first=True), F('issued').desc(nulls_first=True), F('registered').desc()) (refunds, paginator, page_range) = simple_pagination(request, refund_objects, 20) return render(request, 'invoices/refunds.html', { 'refunds': refunds, 'page_range': page_range, 'breadcrumbs': [('/admin/invoices/refunds/', 'Refunds'), ], 'helplink': 'payment', })
def extend_cancel(request, invoicenum): authenticate_backend_group(request, 'Invoice managers') invoice = get_object_or_404(Invoice, pk=invoicenum) try: days = int(request.GET.get('days', 5)) except Exception as e: days = 5 invoice.canceltime += timedelta(days=days) invoice.save() InvoiceHistory(invoice=invoice, txt='Extended autocancel by {0} days to {1}'.format(days, invoice.canceltime)).save() return HttpResponseRedirect("/invoiceadmin/%s/" % invoice.id)
def cancelinvoice(request, invoicenum): authenticate_backend_group(request, 'Invoice managers') invoice = get_object_or_404(Invoice, pk=invoicenum) reason = request.POST.get('reason', '') if not reason: return HttpResponseForbidden("Can't cancel an invoice without a reason!") manager = InvoiceManager() try: manager.cancel_invoice(invoice, reason, request.user.username) except Exception as ex: messages.warning(request, "Failed to cancel: %s" % ex) return HttpResponseRedirect("/invoiceadmin/%s/" % invoice.id)
def accountstructure(request): authenticate_backend_group(request, 'Accounting managers') accounts = exec_to_dict( """SELECT ac.id AS classid, ac.name AS classname, ac.inbalance, ag.id AS groupid, ag.name AS groupname, a.id AS accountid, a.num AS accountnum, a.name AS accountname FROM accounting_accountclass ac INNER JOIN accounting_accountgroup ag ON ag.accountclass_id=ac.id INNER JOIN accounting_account a ON a.group_id=ag.id ORDER BY a.num""") return render(request, 'accounting/structure.html', { 'accounts': accounts, 'topadmin': 'Accounting', 'helplink': 'accounting', })