diff --git a/jdav_web/contrib/media.py b/jdav_web/contrib/media.py index 4ff27c9..d5e873b 100644 --- a/jdav_web/contrib/media.py +++ b/jdav_web/contrib/media.py @@ -1,9 +1,20 @@ import os from django.conf import settings from django.http import HttpResponse +from django import template +from django.template.loader import get_template from wsgiref.util import FileWrapper +def find_template(template_name): + for engine in template.engines.all(): + for loader in engine.engine.template_loaders: + for origin in loader.get_template_sources(template_name): + if os.path.exists(origin.name): + return origin.name + raise template.TemplateDoesNotExist(f"Could not find template: {template_name}") + + def media_path(fp): return os.path.join(os.path.join(settings.MEDIA_ROOT, "memberlists"), fp) diff --git a/jdav_web/locale/de/LC_MESSAGES/django.po b/jdav_web/locale/de/LC_MESSAGES/django.po index cafea14..4f805db 100644 --- a/jdav_web/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-01 14:54+0100\n" +"POT-Creation-Date: 2025-02-02 00:45+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -260,6 +260,10 @@ msgstr "SJR Antrag erstellen" msgid "Generate seminar report" msgstr "Landesjugendplan Antrag erstellen" +#: templates/admin/members/freizeit/change_form_object_tools.html +msgid "Generate LJP V-BK form" +msgstr "Erzeuge LJP V-BK Formular" + #: templates/admin/members/freizeit/change_form_object_tools.html msgid "Generate overview" msgstr "Hinweise für Jugendleiter*innen erstellen" diff --git a/jdav_web/members/admin.py b/jdav_web/members/admin.py index 2ca8b50..548bd5f 100644 --- a/jdav_web/members/admin.py +++ b/jdav_web/members/admin.py @@ -29,7 +29,7 @@ from django.forms import Textarea, RadioSelect, TypedChoiceField, CheckboxInput from django.shortcuts import render from django.core.exceptions import PermissionDenied, ValidationError from .pdf import render_tex, fill_pdf_form, merge_pdfs, serve_pdf -from .excel import generate_group_overview +from .excel import generate_group_overview, VBK_3_1, VBK_3_2, generate_ljp_vbk from contrib.admin import CommonAdminInlineMixin, CommonAdminMixin @@ -844,7 +844,7 @@ class GroupAdmin(CommonAdminMixin, admin.ModelAdmin): class ActivityCategoryAdmin(admin.ModelAdmin): - fields = ['name', 'description'] + fields = ['name', 'ljp_category', 'description'] class FreizeitAdminForm(forms.ModelForm): @@ -1045,6 +1045,13 @@ class GenerateSeminarReportForm(forms.Form): widget=CheckboxInput(attrs={'style': 'display: inherit'}), required=False) + +class GenerateVBKForm(forms.Form): + categories = ((VBK_3_1, _('Staff training')), + (VBK_3_2, _('Educational programme'))) + category = forms.ChoiceField(choices=categories, label=_('Category')) + + class GenerateSjrForm(forms.Form): def __init__(self, *args, **kwargs): @@ -1052,7 +1059,6 @@ class GenerateSjrForm(forms.Form): super(GenerateSjrForm,self).__init__(*args,**kwargs) self.fields['invoice'] = forms.ChoiceField(choices=self.attachments, label=_('Invoice')) - class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin): @@ -1116,6 +1122,29 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin): return render_tex(memberlist.name + "_Notizen", 'members/notes_list.tex', context) notes_list.short_description = _('Generate overview') + def render_seminar_vbk_options(self, request, memberlist, form): + context = dict(self.admin_site.each_context(request), + title=_('Generate LJP V-BK form'), + opts=self.opts, + memberlist=memberlist, + form=form, + object=memberlist) + return render(request, 'admin/generate_seminar_vbk.html', context=context) + + def seminar_vbk(self, request, memberlist): + if not self.may_view_excursion(request, memberlist): + return self.not_allowed_view(request, memberlist) + if "apply" in request.POST: + form = GenerateVBKForm(request.POST) + if not form.is_valid(): + messages.error(request, _('Please select a category.')) + return self.render_seminar_vbk_options(request, memberlist, form) + category = int(form.cleaned_data['category']) + title = memberlist.ljpproposal.title if hasattr(memberlist, 'ljpproposal') else memberlist.name + fp = generate_ljp_vbk(memberlist, category) + return serve_media(fp, 'application/xlsx') + return self.render_seminar_vbk_options(request, memberlist, GenerateVBKForm()) + def render_seminar_report_options(self, request, memberlist, form): context = dict(self.admin_site.each_context(request), title=_('Generate seminar report'), @@ -1233,6 +1262,8 @@ class FreizeitAdmin(CommonAdminMixin, nested_admin.NestedModelAdmin): def action_view(self, request, object_id): if "sjr_application" in request.POST: return self.sjr_application(request, Freizeit.objects.get(pk=object_id)) + if "seminar_vbk" in request.POST: + return self.seminar_vbk(request, Freizeit.objects.get(pk=object_id)) if "seminar_report" in request.POST: return self.seminar_report(request, Freizeit.objects.get(pk=object_id)) if "notes_list" in request.POST: diff --git a/jdav_web/members/excel.py b/jdav_web/members/excel.py index 6fa8ff7..4b9e83d 100644 --- a/jdav_web/members/excel.py +++ b/jdav_web/members/excel.py @@ -1,8 +1,9 @@ from datetime import datetime import os import xlsxwriter +import openpyxl from django.conf import settings -from contrib.media import media_path +from contrib.media import media_path, find_template from .models import WEEKDAYS def generate_group_overview(all_groups, limit_to_public = True): @@ -67,3 +68,49 @@ def generate_group_overview(all_groups, limit_to_public = True): workbook.close() return filename + + +VBK_3_1, VBK_3_2 = 1, 2 +VBK_TEMPLATES = { + VBK_3_1: 'members/LJP_VBK_3-1.xlsx', + VBK_3_2: 'members/LJP_VBK_3-2.xlsx', +} + + +def generate_ljp_vbk(excursion, mode): + """ + Generate the VBK forms for LJP given an excursion. Returns the filename to the filled excel file. + """ + print(mode, VBK_TEMPLATES, mode in VBK_TEMPLATES) + if not mode in VBK_TEMPLATES: + raise ValueError(f"Invalid mode {mode}.") + template_path = VBK_TEMPLATES[mode] + path = find_template(template_path) + workbook = openpyxl.load_workbook(path) + + sheet = workbook.active + title = excursion.ljpproposal.title if hasattr(excursion, 'ljpproposal') else excursion.name + + sheet['I6'] = settings.SEKTION_IBAN + sheet['I8'] = settings.SEKTION_ACCOUNT_HOLDER + sheet['P3'] = excursion.end.year + sheet['B4'] = f"Sektion {settings.SEKTION}" + sheet['B5'] = settings.SEKTION_STREET + sheet['B6'] = settings.SEKTION_TOWN + sheet['B7'] = settings.RESPONSIBLE_MAIL + sheet['B36'] = f"{settings.SEKTION}, {datetime.today():%d.%m.%Y}" + sheet['F19'] = f"B {excursion.date:%y}-{excursion.pk}" + sheet['D19'] = settings.SEKTION + sheet['G19'] = title + sheet['I19'] = f"von {excursion.date:%d.%m.%y} bis {excursion.end:%d.%m.%y}" + sheet['J19'] = f"{excursion.duration}" + sheet['L19'] = f"{excursion.ljp_participant_count}" + sheet['H19'] = excursion.get_ljp_activity_category() + sheet['M19'] = excursion.place + + if hasattr(excursion, 'statement'): + sheet['Q19'] = f"{excursion.statement.total_theoretic}" + + filename = f"LJP_V-BK_3.{mode}_{title}.xlsx" + workbook.save(media_path(filename)) + return filename diff --git a/jdav_web/members/locale/de/LC_MESSAGES/django.po b/jdav_web/members/locale/de/LC_MESSAGES/django.po index fc719e6..cefb895 100644 --- a/jdav_web/members/locale/de/LC_MESSAGES/django.po +++ b/jdav_web/members/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-02 00:32+0100\n" +"POT-Creation-Date: 2025-02-02 00:45+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -351,6 +351,18 @@ msgstr "Modus" msgid "Prepend V32" msgstr "V32 Formblatt einfügen" +#: members/admin.py +msgid "Staff training" +msgstr "Jugendleiter*innenweiterbildung" + +#: members/admin.py +msgid "Educational programme" +msgstr "Themenorientierte Bildungsmaßnahme" + +#: members/admin.py members/models.py +msgid "Category" +msgstr "Kategorie" + #: members/admin.py msgid "Invoice" msgstr "Beleg" @@ -379,6 +391,14 @@ msgstr "Kriseninterventionsliste erstellen" msgid "Generate overview" msgstr "Hinweise für Jugendleiter erstellen" +#: members/admin.py members/templates/admin/generate_seminar_vbk.html +msgid "Generate LJP V-BK form" +msgstr "Erzeuge LJP V-BK Formular" + +#: members/admin.py +msgid "Please select a category." +msgstr "Bitte wähle eine Kategorie aus." + #: members/admin.py members/templates/admin/generate_seminar_report.html msgid "Generate seminar report" msgstr "Landesjugendplan Antrag erstellen" @@ -491,8 +511,7 @@ msgstr "LJP Spielart" #: members/models.py msgid "" "The official category for LJP applications associated with this activity." -msgstr "" -"Die offizielle Spielart für LJP Anträge mit dieser Aktivität." +msgstr "Die offizielle Spielart für LJP Anträge mit dieser Aktivität." #: members/models.py msgid "Description" @@ -1046,10 +1065,6 @@ msgstr "Fortbildungstyp" msgid "Training categories" msgstr "Fortbildungstypen" -#: members/models.py -msgid "Category" -msgstr "Kategorien" - #: members/models.py msgid "Comments" msgstr "Kommentar" @@ -1073,6 +1088,7 @@ msgstr "Fortbildungen" #: members/templates/admin/demote_to_waiter.html #: members/templates/admin/freizeit_finance_overview.html #: members/templates/admin/generate_seminar_report.html +#: members/templates/admin/generate_seminar_vbk.html #: members/templates/admin/generate_sjr_application.html #: members/templates/admin/invite_as_user.html #: members/templates/admin/invite_for_group.html @@ -1098,6 +1114,7 @@ msgstr "Zurück auf die Warteliste setzen" #: members/templates/admin/demote_to_waiter.html #: members/templates/admin/freizeit_finance_overview.html #: members/templates/admin/generate_seminar_report.html +#: members/templates/admin/generate_seminar_vbk.html #: members/templates/admin/generate_sjr_application.html #: members/templates/admin/invite_as_user.html #: members/templates/admin/invite_for_group.html @@ -1389,10 +1406,36 @@ msgstr "" "Felder im Formblatt selbst aus und unterschreibe das PDF." #: members/templates/admin/generate_seminar_report.html +#: members/templates/admin/generate_seminar_vbk.html #: members/templates/admin/generate_sjr_application.html msgid "Generate" msgstr "Erstellen" +#: members/templates/admin/generate_seminar_vbk.html +msgid "" +"Every LJP application needs a V-BK form containing the most important facts " +"about the seminar.\n" +"Here you can automatically generate such a form in Excel format." +msgstr "" +"Jeder LJP Antrag benötigt ein V-BK Formular, das die wichtigsten Randdaten " +"des Seminars enthält. Hier kannst du automatisch ein solches Formular im " +"Excel Format erstellen." + +#: members/templates/admin/generate_seminar_vbk.html +msgid "" +"Your excursion currently has no cost-plan attached, hence the total costs " +"can't be automatically\n" +"calculated and added to the form." +msgstr "" +"Deine Ausfahrt hat zur Zeit keinen Kostenplan. Daher können die Gesamtkosten " +"nicht automatisch berechnet und dem Formular hinzugefügt werden." + +#: members/templates/admin/generate_seminar_vbk.html +msgid "" +"Depending on the type of seminar, please select one of the two options below." +msgstr "" +"Bitte wähle aus, um welche Art von Seminar es sich handelt." + #: members/templates/admin/generate_sjr_application.html members/tests.py msgid "Here you can generate an allowance application for the SJR." msgstr "Hier kannst du einen SJR-Zuschussantrag erstellen." diff --git a/jdav_web/members/pdf.py b/jdav_web/members/pdf.py index ff43d94..9ffb88a 100644 --- a/jdav_web/members/pdf.py +++ b/jdav_web/members/pdf.py @@ -11,19 +11,10 @@ from django.template.loader import get_template from django.conf import settings from django.http import HttpResponse, HttpResponseRedirect from wsgiref.util import FileWrapper -from contrib.media import media_path, media_dir, serve_media, ensure_media_dir +from contrib.media import media_path, media_dir, serve_media, ensure_media_dir, find_template from PIL import Image -def find_template(template_name): - for engine in template.engines.all(): - for loader in engine.engine.template_loaders: - for origin in loader.get_template_sources(template_name): - if os.path.exists(origin.name): - return origin.name - raise template.TemplateDoesNotExist(f"Could not find template: {template_name}") - - def serve_pdf(filename_pdf): return serve_media(filename_pdf, 'application/pdf') diff --git a/jdav_web/members/templates/admin/generate_seminar_vbk.html b/jdav_web/members/templates/admin/generate_seminar_vbk.html new file mode 100644 index 0000000..f028989 --- /dev/null +++ b/jdav_web/members/templates/admin/generate_seminar_vbk.html @@ -0,0 +1,56 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls static %} + +{% block extrahead %} + {{ block.super }} + {{ media }} + + + +{% endblock %} + +{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} invite-waiter +{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} + +

+{% blocktrans %}Every LJP application needs a V-BK form containing the most important facts about the seminar. +Here you can automatically generate such a form in Excel format.{% endblocktrans %} +

+{% if not memberlist.statement %} +

+{% blocktrans %}Your excursion currently has no cost-plan attached, hence the total costs can't be automatically +calculated and added to the form.{% endblocktrans %} +

+{% endif %} +

+{% blocktrans %}Depending on the type of seminar, please select one of the two options below.{% endblocktrans %} +

+ +
+ {% csrf_token %} +

+ + {{ form }} +
+

+
+ + + + {% translate "Cancel" %} +
+ +{% endblock %} diff --git a/jdav_web/members/templates/members/LJP_VBK_3-1.xlsx b/jdav_web/members/templates/members/LJP_VBK_3-1.xlsx new file mode 100644 index 0000000..a02224a Binary files /dev/null and b/jdav_web/members/templates/members/LJP_VBK_3-1.xlsx differ diff --git a/jdav_web/members/templates/members/LJP_VBK_3-2.xlsx b/jdav_web/members/templates/members/LJP_VBK_3-2.xlsx new file mode 100644 index 0000000..1b21d3d Binary files /dev/null and b/jdav_web/members/templates/members/LJP_VBK_3-2.xlsx differ diff --git a/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html b/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html index f27115c..c52ac75 100644 --- a/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html +++ b/jdav_web/templates/admin/members/freizeit/change_form_object_tools.html @@ -24,6 +24,13 @@ +
  • +
    + {% csrf_token %} + +
    +
  • +
  • {% csrf_token %}