import logging from datetime import date, datetime from django.conf import settings from django.contrib import admin, messages from django.http import HttpResponseRedirect from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext from segno import helpers from posts.models import FetMeeting from .forms import ( BankDataAdminForm, BillAdminForm, BillInlineForm, ResolutionAdminForm, WirefAdminForm, ) from .models import BankData, Bill, Resolution, Wiref from .utils import generate_pdf logger = logging.getLogger(__name__) class BillPeriodeFilter(admin.SimpleListFilter): title = "Periode" parameter_name = "period" __lst = () def choices(self, changelist): # Current period. yield { "selected": self.value() is None, "query_string": changelist.get_query_string(remove=[self.parameter_name]), "display": self.__lst[0][1], } # Add all other periods. for lookup, title in self.__lst[1:]: yield { "selected": self.value() == str(lookup), "query_string": changelist.get_query_string({self.parameter_name: lookup}), "display": title, } # 'All' choice. yield { "selected": self.value() == "All", "query_string": changelist.get_query_string({self.parameter_name: "All"}), "display": _("All"), } def lookups(self, request, model_admin): qs = model_admin.get_queryset(request).order_by("-date") if not qs.exists(): return None count = 0 total_count = qs.count() # Get first period. start_year = qs.first().date.year # Check if date of first bill is in first half of year. If yes, start of period is the # year before. if qs.first().date < date(start_year, 7, 1): start_year -= 1 start_date = date(start_year, 7, 1) end_date = date(start_year + 1, 6, 30) entry_qs = qs.filter(date__range=(start_date, end_date)) # Go through all bills to get the periods. while count != total_count: # Set period if a bill exists. if entry_qs.exists(): self.__lst += ( ( f"{start_year}-{start_year + 1}", f"Periode {start_year}-{start_year + 1}", ), ) count += entry_qs.count() # Get bills from next period. start_year -= 1 start_date = date(start_year, 7, 1) end_date = date(start_year + 1, 6, 30) entry_qs = qs.filter(date__range=(start_date, end_date)) # If you are searching bills from 90's, something went wrong. if start_year < 2000: logger.info( "Something went wrong while counting. Count: %s. Totalcounter: %s", count, total_count, ) break return self.__lst def queryset(self, request, queryset): qs = queryset if self.value(): try: period = datetime.strptime(self.value()[:4], "%Y") # noqa: DTZ007 except Exception: # If choice is 'All', return all bills. qs = queryset else: # Return bills from specific old period. start_year = period.year start_date = date(start_year, 7, 1) end_date = date(start_year + 1, 6, 30) qs = queryset.filter(date__range=(start_date, end_date)) # Return bills from current period. else: tmp_qs = queryset.order_by("-date") # Get first period. start_year = tmp_qs.first().date.year # Check if date of first bill is in first half of year. If yes, start of period is the # year before. if tmp_qs.first().date < date(start_year, 7, 1): start_year -= 1 start_date = date(start_year, 7, 1) end_date = date(start_year + 1, 6, 30) qs = queryset.filter(date__range=(start_date, end_date)) return qs class BillInline(admin.TabularInline): form = BillInlineForm model = Bill can_delete = False extra = 0 max_num = 0 readonly_fields = ("purpose", "amount", "file_field", "affiliation") show_change_link = True @admin.register(BankData) class BankDataAdmin(admin.ModelAdmin): form = BankDataAdminForm model = BankData inlines = (BillInline,) list_display = [ "name", "iban", "bic", "address", "is_disabled", ] ordering = ["name"] def add_view(self, request, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().add_view( request, form_url, extra_context=extra_context, ) def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().change_view( request, object_id, form_url, extra_context=extra_context, ) @admin.register(Bill) class BillAdmin(admin.ModelAdmin): form = BillAdminForm model = Bill list_display = [ "id", "only_digital", "amount", "purpose", "resolution", "status_colored", "bill_creator", "affiliation", ] actions = ["make_cleared", "make_finished"] autocomplete_fields = ["resolution"] list_filter = ["status", "affiliation", "payer", BillPeriodeFilter] search_fields = [ "purpose", "bankdata__name", "bill_creator__firstname", "bill_creator__surname", ] show_facets = admin.ShowFacets.ALWAYS ordering = ["-id"] readonly_fields = [ "get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic", "get_qrcode", ] fieldsets = ( ( None, { "fields": ( "bill_creator", "resolution", ), }, ), ( "Bankdaten", { "fields": ( "payer", "bankdata", ("get_bankdata_name", "get_bankdata_iban", "get_bankdata_bic"), "get_qrcode", ), }, ), ( "Rechnung", { "fields": ( "affiliation", "date", "invoice", "purpose", "amount", "only_digital", "file_field", ), }, ), ( "Sonstiges", { "fields": ( "comment", "wiref", "status", ), }, ), ) def add_view(self, request, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().add_view( request, form_url, extra_context=extra_context, ) def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().change_view( request, object_id, form_url, extra_context=extra_context, ) def save_model(self, request, obj, form, change): obj.author = request.user super().save_model(request, obj, form, change) @admin.display(description="Kontoinhaber:in") def get_bankdata_name(self, obj): try: tmp = BankData.objects.get(id=obj.bankdata.id) except Exception: return "-" return f"{tmp.name}" @admin.display(description="IBAN") def get_bankdata_iban(self, obj): try: tmp = BankData.objects.get(id=obj.bankdata.id) except Exception: return "-" return f"{tmp.iban}" @admin.display(description="BIC") def get_bankdata_bic(self, obj): try: tmp = BankData.objects.get(id=obj.bankdata.id) except Exception: return "-" return f"{tmp.bic}" @admin.display(description="QR Code") def get_qrcode(self, obj): if obj.status != Bill.Status.CLEARED: return "-" try: tmp = BankData.objects.get(id=obj.bankdata.id) except Exception: return "-" try: qrcode = helpers.make_epc_qr( name=tmp.name, iban=tmp.iban, amount=obj.amount, text=obj.purpose, bic=tmp.bic, encoding="utf-8", ) except Exception: return "Daten für QR Code ungültig" uri = qrcode.png_data_uri(scale=3.0) return format_html('', uri) @admin.display(description="Status") def status_colored(self, obj): # TODO: if there is a status without color, set nothing. colors = { Bill.Status.SUBMITTED: "red", Bill.Status.CLEARED: "darkorange", Bill.Status.FINISHED: "green", Bill.Status.INCOMPLETED: "blue", } return format_html( '{status}', color=colors[obj.status], status=obj.get_status_display(), ) @admin.action(description="Als 'Für Überweisung freigegeben' markieren.") def make_cleared(self, request, queryset): updated = queryset.update(status=Bill.Status.CLEARED) self.message_user( request, ngettext( "%d Rechnung wurde als 'Für Überweisung freigegeben' markiert.", "%d Rechnungen wurden als 'Für Überweisung freigegeben' markiert.", updated, ) % updated, messages.SUCCESS, ) @admin.action(description="Als 'Abgeschlossen / Überwiesen' markieren.") def make_finished(self, request, queryset): updated = queryset.update(status=Bill.Status.FINISHED) self.message_user( request, ngettext( "%d Rechnung wurde als 'Abgeschlossen / Überwiesen' markiert.", "%d Rechnungen wurden als 'Abgeschlossen / Überwiesen' markiert.", updated, ) % updated, messages.SUCCESS, ) @admin.register(Resolution) class ResolutionAdmin(admin.ModelAdmin): form = ResolutionAdminForm model = Resolution inlines = (BillInline,) list_display = [ "name", "id", "is_visible", "total", ] ordering = ["-id"] search_fields = ["name", "id"] readonly_fields = [ "fetmeeting", ] fieldsets = ( ( None, { "fields": ( "name", "id", "date", "option", "is_visible", ), }, ), ( "Budget", { "fields": ( "budget", "total", "budget_remaining", ), }, ), ( "Abstimmung", { "classes": ["collapse"], "fields": ( "voting", "voting_text", "fetmeeting", ), }, ), ) def add_view(self, request, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().add_view( request, form_url, extra_context=extra_context, ) def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().change_view( request, object_id, form_url, extra_context=extra_context, ) @admin.display(description="Gesamtsumme (EUR)") def total(self, obj): total = 0 bills = Bill.objects.filter(resolution=obj) for elem in bills: total += elem.amount return f"{total}" @admin.display(description="Fachschaftssitzung") def fetmeeting(self, obj): fetmeeting = FetMeeting.objects.get_queryset().filter(date=obj.date).first() if fetmeeting is not None: link = f"https://{settings.HOST_NAME}/posts/{fetmeeting.slug}/" return format_html('Link zur Fachschaftssitzung', link) return format_html("-") @admin.register(Wiref) class WirefAdmin(admin.ModelAdmin): form = WirefAdminForm model = Wiref inlines = (BillInline,) list_display = [ "wiref_id", "status", "file_field", "total", ] actions = ["make_cleared", "make_finished"] list_filter = ["status"] ordering = ["-wiref_id"] def add_view(self, request, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." return super().add_view( request, form_url, extra_context=extra_context, ) def change_view(self, request, object_id, form_url="", extra_context=None): extra_context = extra_context or {} extra_context["help_text"] = "Fette Schriften sind Pflichtfelder." extra_context["generate_pdf"] = True return super().change_view( request, object_id, form_url, extra_context=extra_context, ) def response_change(self, request, obj): if "_generate_pdf" in request.POST: if generate_pdf(obj) is True: self.message_user( request, "Neues Wiref Formular wurde generiert.", messages.SUCCESS, ) else: self.message_user( request, "Das PDF-Dokument konnte nicht generiert werden, da der Status nicht auf " "'Offen' gesetzt ist.", messages.WARNING, ) return HttpResponseRedirect(".") return super().response_change(request, obj) def save_model(self, request, obj, form, change): # set status to submitted, if a file exists and status is opened. if ( change is True and obj.file_field != "" and obj.status == Wiref.Status.OPENED and "_generate_pdf" not in request.POST ): obj.status = Wiref.Status.SUBMITTED super().save_model(request, obj, form, change) @admin.display(description="Gesamtsumme (EUR)") def total(self, obj): total = 0 bills = Bill.objects.filter(wiref=obj) for elem in bills: total += elem.amount return f"{total}" @admin.action(description="Als 'Überwiesen' markieren.") def make_finished(self, request, queryset): updated = queryset.update(status=Wiref.Status.TRANSFERRED) self.message_user( request, ngettext( "%d Wiref Formular wurde als 'Überwiesen' markiert.", "%d Wiref Formulare wurden als 'Überwiesen' markiert.", updated, ) % updated, messages.SUCCESS, )