You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kompass/jdav_web/finance/admin.py

268 lines
12 KiB
Python

from django.contrib import admin, messages
from django.forms import Textarea
from django.http import HttpResponse, HttpResponseRedirect
from django.db.models import TextField
from django.urls import path, reverse
from functools import update_wrapper
from django.utils.translation import gettext_lazy as _
from django.shortcuts import render
from .models import Ledger, Statement, Receipt, Transaction, Bill, StatementSubmitted, StatementConfirmed,\
StatementUnSubmitted
@admin.register(Ledger)
class LedgerAdmin(admin.ModelAdmin):
search_fields = ('name', )
class BillOnStatementInline(admin.TabularInline):
model = Bill
extra = 0
sortable_options = []
fields = ['short_description', 'explanation', 'amount', 'paid_by', 'proof']
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 1, 'cols': 40})}
}
def get_readonly_fields(self, request, obj=None):
if obj is not None and obj.submitted:
return self.fields
return super(BillOnStatementInline, self).get_readonly_fields(request, obj)
@admin.register(StatementUnSubmitted)
class StatementUnSubmitteddAdmin(admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'submitted']
inlines = [BillOnStatementInline]
def get_readonly_fields(self, request, obj=None):
readonly_fields = ['submitted']
if obj is not None and obj.submitted:
return readonly_fields + self.fields
else:
return readonly_fields
def get_urls(self):
urls = super().get_urls()
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
custom_urls = [
path(
"<path:object_id>/submit/",
wrap(self.submit_view),
name="%s_%s_submit" % (self.opts.app_label, self.opts.model_name),
),
]
return custom_urls + urls
def submit_view(self, request, object_id):
statement = Statement.objects.get(pk=object_id)
if statement.submitted:
messages.error(request,
_("%(name)s is already submitted.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
if "apply" in request.POST:
statement.submit(get_member(request))
messages.success(request,
_("Successfully submited %(name)s. The finance department will notify the requestors as soon as possible.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
context = dict(self.admin_site.each_context(request),
title=_('Submit statement'),
opts=self.opts,
statement=statement)
return render(request, 'admin/submit_statement.html', context=context)
class TransactionOnSubmittedStatementInline(admin.TabularInline):
model = Transaction
fields = ['amount', 'member', 'reference', 'ledger']
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 1, 'cols': 40})}
}
extra = 0
class BillOnSubmittedStatementInline(BillOnStatementInline):
model = Bill
extra = 0
sortable_options = []
fields = ['short_description', 'explanation', 'amount', 'paid_by', 'proof', 'costs_covered']
formfield_overrides = {
TextField: {'widget': Textarea(attrs={'rows': 1, 'cols': 40})}
}
def get_readonly_fields(self, request, obj=None):
return ['short_description', 'explanation', 'amount', 'paid_by', 'proof']
@admin.register(StatementSubmitted)
class StatementSubmittedAdmin(admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'submitted']
list_display = ['__str__', 'is_valid', 'submitted_date', 'submitted_by']
ordering = ('-submitted_date',)
inlines = [BillOnSubmittedStatementInline, TransactionOnSubmittedStatementInline]
def has_add_permission(self, request, obj=None):
return False
def get_readonly_fields(self, request, obj=None):
readonly_fields = ['submitted']
if obj is not None and obj.submitted:
return readonly_fields + self.fields
else:
return readonly_fields
def get_urls(self):
urls = super().get_urls()
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
custom_urls = [
path(
"<path:object_id>/overview/",
wrap(self.overview_view),
name="%s_%s_overview" % (self.opts.app_label, self.opts.model_name),
),
path(
"<path:object_id>/reduce_transactions/",
wrap(self.reduce_transactions_view),
name="%s_%s_reduce_transactions" % (self.opts.app_label, self.opts.model_name),
),
]
return custom_urls + urls
def overview_view(self, request, object_id):
statement = StatementSubmitted.objects.get(pk=object_id)
if not statement.submitted:
messages.error(request,
_("%(name)s is not yet submitted.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
if "transaction_execution_confirm" in request.POST:
res = statement.confirm(confirmer=get_member(request))
if not res:
# this should NOT happen!
messages.error(request,
_("An error occured while trying to confirm %(name)s. Please try again.") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_overview' % (self.opts.app_label, self.opts.model_name)))
messages.success(request,
_("Successfully confirmed %(name)s. I hope you executed the associated transactions, I wont remind you again.")
% {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "confirm" in request.POST:
res = statement.validity
if res == Statement.VALID:
context = dict(self.admin_site.each_context(request),
title=_('Statement confirmed'),
opts=self.opts,
statement=statement)
return render(request, 'admin/confirmed_statement.html', context=context)
elif res == Statement.NON_MATCHING_TRANSACTIONS:
messages.error(request,
_("Transactions do not match the covered expenses. Please correct the mistakes listed below.")
% {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_overview' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
elif res == Statement.MISSING_LEDGER:
messages.error(request,
_("Some transactions have no ledger configured. Please fill in the gaps.")
% {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_overview' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "reject" in request.POST:
statement.submitted = False
statement.save()
messages.success(request,
_("Successfully rejected %(name)s. The requestor can reapply, when needed.")
% {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' % (self.opts.app_label, self.opts.model_name)))
if "generate_transactions" in request.POST:
if statement.transaction_set.count() > 0:
messages.error(request,
_("%(name)s already has transactions. Please delete them first, if you want to generate new ones") % {'name': str(statement)})
else:
statement.generate_transactions()
messages.success(request,
_("Successfully generated transactions for %(name)s") % {'name': str(statement)})
return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
context = dict(self.admin_site.each_context(request),
title=_('View submitted statement'),
opts=self.opts,
statement=statement,
transaction_issues=statement.transaction_issues,
nights=statement.excursion.night_count,
price_per_night=statement.real_night_cost,
duration=statement.excursion.duration,
staff_count=statement.real_staff_count,
kilometers_traveled=statement.excursion.kilometers_traveled,
means_of_transport=statement.excursion.get_tour_approach(),
euro_per_km=statement.euro_per_km,
allowance_per_day=statement.ALLOWANCE_PER_DAY,
total_bills=statement.total_bills,
nights_per_yl=statement.nights_per_yl,
allowance_per_yl=statement.allowance_per_yl,
transportation_per_yl=statement.transportation_per_yl,
total_per_yl=statement.total_per_yl,
total_staff=statement.total_staff,
total=statement.total)
return render(request, 'admin/overview_submitted_statement.html', context=context)
def reduce_transactions_view(self, request, object_id):
statement = StatementSubmitted.objects.get(pk=object_id)
statement.reduce_transactions()
messages.success(request,
_("Successfully reduced transactions for %(name)s.") % {'name': str(statement)})
return HttpResponseRedirect(request.GET['redirectTo'])
#return HttpResponseRedirect(reverse('admin:%s_%s_change' % (self.opts.app_label, self.opts.model_name), args=(statement.pk,)))
@admin.register(StatementConfirmed)
class StatementConfirmedAdmin(admin.ModelAdmin):
fields = ['short_description', 'explanation', 'excursion', 'confirmed']
#readonly_fields = fields
list_display = ['__str__', 'total_pretty', 'confirmed_date', 'confirmed_by']
ordering = ('-confirmed_date',)
def has_add_permission(self, request, obj=None):
return False
@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
list_display = ['member', 'ledger', 'amount', 'reference', 'statement', 'confirmed',
'confirmed_date', 'confirmed_by']
list_filter = ('ledger', 'member', 'statement', 'confirmed')
search_fields = ('reference', )
fields = ['reference', 'amount', 'member', 'ledger', 'statement']
readonly_fields = fields
@admin.register(Bill)
class BillAdmin(admin.ModelAdmin):
list_display = ['__str__', 'statement', 'short_description', 'pretty_amount', 'paid_by', 'refunded']
list_filter = ('statement', 'paid_by', 'refunded')
search_fields = ('reference', 'statement')
def get_member(request):
if not hasattr(request.user, 'member'):
return None
else:
return request.user.member