From 0a6ecda7a229fe89f7b2d82b8dc9ef6734e6d259 Mon Sep 17 00:00:00 2001 From: Martin Putzlocher Date: Wed, 10 Jun 2026 10:43:53 +0200 Subject: [PATCH] =?UTF-8?q?Neue=20Bestell=C3=BCbersicht=20erstellt.=20/bes?= =?UTF-8?q?tellungen/summary?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db.sqlite3 | Bin 212992 -> 225280 bytes .../templates/mensa_app/bestell_summary.html | 81 +++++++++++++++ mensa_app/templates/mensa_app/speiseplan.html | 92 ++++++++++++++++++ mensa_app/urls.py | 4 +- mensa_app/views.py | 51 ++++++++++ 5 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 mensa_app/templates/mensa_app/bestell_summary.html create mode 100644 mensa_app/templates/mensa_app/speiseplan.html diff --git a/db.sqlite3 b/db.sqlite3 index e42d6e9edc9e29fcef7c1d82a707fe8efa10ea17..ce6b7425fff2156feb3c10b7efdf69e2e5aff451 100644 GIT binary patch delta 4281 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 delta 1602 zcmZuxYfKbZ6uys{ea}6MvaCXb_@WxzU3OtrNDJ0Bl2|EP(-=d-%C0M@VHbHM_E#D0 zkJ?(xGT==RrHu)#HI{C+W;NAHYqU*mm6ATwfCLIwrA1AUAKK76AXbyk$-S9-zVo{0 zd^1ghN>h{4oRP8%A(SuHQn9dD!PtsCyeqz~9h5?GbUdSoJ z2sFH28&_42w0V zCq|1hG4&g}G~=4xaW&tV=qjl&QNJ~=88-?=u{9Ad-z?cMtVOU^*`rHb^?|lF8cp@P zy#B1*f-u*$n~^-Aqhv{+bABAA3B&iPJ~7kVH!D zNk}BSOS7n-eXSXLMWWXj9}f^Y@!ddyx*T^Qs9_%{KifXB{%ZAFj#{cspPSw?UNg2B z9vDgu2I(W(Ov`Xr(!CAu#dLpmAs-Fle7u~$&_?F*D$c-`Y$z@;I5)fa zp=vyjXLJw?-=L9s(z1Py)%>3rS%IIOar-_-6kgUsE?V}LuQEDwa`2lqJgSk!ID5w6 zq(-*!Q=DY;ni$FD!B!HI^9;_dc~68MYbBY|A~naySsU^37LCm3TiZxw^5G6_DsU^1 zK+q3Ah(TO{voI{0bO{ph0488uu=4EFB08l4Pjur=!m$UzL($cH@E80k90%Yx_z|wb zw<16fv_37u@euHy3;3l>g^eKLG9V1kK_|Qod!QU%1P`PGW+QR+B1?{3#S5@ovED*( z0$znVtdG6Ne9DCKsZy>`Tes~M>n-!p(-K$DSd+JY$BR-$t|ah_Q}_itfgzn{L-5&* z2DEBK*LB}aTrd|h=8sEA1)IFCvX#qyH5yJKk$AJv1Sqr%`k6?hN{B>pUz(y zZs@na3uABt?hC`8V5vmSSk^a5->6J6CWKP6S=i4$O9cJKA+}L&X_k#JhTuMo3DqEE znTU)q#wMAV@bsHZiT)8GJ3=rBgO45Wm?SYAQQV_IJ>q;G&d;9R3OR*r_#iP{HWbRO z(rsHzNh%ScNV4_RV7+agoGk_+aw@J?V7f2!WnR`n3US^HulkRWlxe>5KRU<(o3R3! zW_(IF@TntYgOtBNxRD1B6F1ID#txGNrfPOE=RsV8JsckvlD{<` zK%(m6P5fLNInFb8;C_BEj9VnIqV+L#9aW1;dGQhQg(T{nh^cp5nk6O!LxR>HLOqlK zi>nVAXXE6aO`jiu38a%;bnPZM^-;-0qS#FTA0*Qt(ZT6?Ov2=BzgzTq+}Anqc(6(N p4w>=@8ujTk>J#j^47@K&d`Zbp$H49*3SDUS<@#2-a>YN(`!8Y- + + + + Bestell-Zusammenfassung + + + + +
+ +
+
+

Bestell-Check für den {{ target_date|date:"d.m.Y" }}

+ +
+
+ + +
+
+
+
+
Gesamtanzahl Bestellungen
+

{{ total_bestellungen }}

+
+
+
+
+
+
+
Erwarteter Umsatz
+

{{ total_umsatz|floatformat:2 }} €

+
+
+
+
+ + +
+
Details pro Gericht
+
+ + + + + + + + + + + {% for stat in summary_stats %} + + + + + + + {% empty %} + + + + {% endfor %} + +
GerichtKategorieMengeUmsatz
{{ stat.menue__gericht__name }}{{ stat.menue__gericht__kategorie__name }}{{ stat.anzahl }}{{ stat.umsatz|floatformat:2 }} €
Keine Bestellungen für diesen Tag vorhanden.
+
+
+ + +
+ + + diff --git a/mensa_app/templates/mensa_app/speiseplan.html b/mensa_app/templates/mensa_app/speiseplan.html new file mode 100644 index 0000000..e5918a1 --- /dev/null +++ b/mensa_app/templates/mensa_app/speiseplan.html @@ -0,0 +1,92 @@ + + + + + + Mensa Speiseplan + + + + +
+
+
+ + +
+
+

Speiseplan für den
{{ target_date|date:"d.m.Y" }}

+ +
+ + {% if prev_date %} + « Vorheriger Tag + {% else %} + + {% endif %} + + + Heute + + + {% if next_date %} + Nächster Tag » + {% else %} + + {% endif %} +
+
+
+ + +
+
Tagesmenüs
+
+ + + + + + + + + + {% for menue in menues_day %} + + + + + + {% empty %} + + + + {% endfor %} + +
GerichtKategoriePreis
{{ menue.gericht.name }}{{ menue.gericht.kategorie.name }}{{ menue.preis|floatformat:2 }} €
Keine Tagesmenüs für diesen Tag geplant.
+
+
+ + +
+
Immer verfügbar (Dauerangebote)
+
+
    + {% for gericht in dauerangebote %} +
  • + {{ gericht.name }} + Verfügbar +
  • + {% empty %} +
  • Momentan keine Dauerangebote.
  • + {% endfor %} +
+
+
+ +
+
+
+ + + diff --git a/mensa_app/urls.py b/mensa_app/urls.py index 60835bc..c59a541 100644 --- a/mensa_app/urls.py +++ b/mensa_app/urls.py @@ -1,8 +1,10 @@ from django.urls import path from .views import GerichtListView from .views import SpeiseplanView # Achte auf den neuen Klassennamen! +from .views import BestellSummaryView urlpatterns = [ - path('speisekarte/', GerichtListView.as_view(), name='gericht_list'), + path('speisekarte/', GerichtListView.as_view(), name='speisekarte'), path('speiseplan/', SpeiseplanView.as_view(), name='speiseplan'), + path('bestellungen/summary/', BestellSummaryView.as_view(), name='bestell_summary'), ] diff --git a/mensa_app/views.py b/mensa_app/views.py index 5cb7a01..662af6f 100644 --- a/mensa_app/views.py +++ b/mensa_app/views.py @@ -48,3 +48,54 @@ class SpeiseplanView(TemplateView): context['next_date'] = next_tag.datum.strftime('%Y-%m-%d') if next_tag else None return context + + +from django.views.generic import TemplateView +from django.utils import timezone +from django.db.models import Count, Sum +from datetime import datetime +from .models import Bestellung, SpeiseplanTag, Gericht, Menue + +class BestellSummaryView(TemplateView): + template_name = 'mensa_app/bestell_summary.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # 1. Datum aus der URL holen (wie beim Speiseplan) + date_str = self.request.GET. get('datum') + if date_str: + try: + target_date = datetime.strptime(date_str, '%Y-%m-%d').date() + except ValueError: + target_date = timezone.now().date() + else: + target_date = timezone.now().date() + + context['target_date'] = target_date + + # 2. Aggregation: Bestellungen nach Gericht gruppieren + # Wir suchen alle Bestellungen, deren Menü am target_date stattfindet + summary_stats = ( + Bestellung.objects.filter(menue__tag__datum=target_date) + .values('menue__gericht__name', 'menue__gericht__kategorie__name') # Gruppierung nach Name & Kategorie + .annotate( + anzahl=Count('id'), # Wie viele wurden bestellt? + umsatz=Sum('menue__preis') # Was macht das für einen Umsatz? + ) + .order_by('menue__gericht__name') + ) + context['summary_stats'] = summary_stats + + # 3. Pager-Logik (identisch mit dem Speiseplan-View) + prev_tag = SpeiseplanTag.objects.filter(datum__lt=target_date).order_by('-datum').first() + next_tag = SpeiseplanTag.objects.filter(datum__gt=target_date).order_by('datum').first() + + context['prev_date'] = prev_tag.datum.strftime('%Y-%m-%d') if prev_tag else None + context['next_date'] = next_tag.datum.strftime('%Y-%m-%d') if next_tag else None + + # 4. Gesamtzahlen für die Übersicht + context['total_bestellungen'] = Bestellung.objects.filter(menue__tag__datum=target_date).count() + context['total_umsatz'] = Bestellung.objects.filter(menue__tag__datum=target_date).aggregate(Sum('menue__preis'))['menue__preis__sum'] or 0 + + return context