Compare commits
5 Commits
a62aed56ae
...
297ee45590
| Author | SHA1 | Date | |
|---|---|---|---|
| 297ee45590 | |||
| b85e19b94a | |||
| 0a6ecda7a2 | |||
| 5d6235679a | |||
| 4533fd6402 |
@@ -0,0 +1,4 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
[Buildset]
|
||||||
|
BuildItems=@Variant(\x00\x00\x00\t\x00\x00\x00\x00\x01\x00\x00\x00\x0b\x00\x00\x00\x00\x01\x00\x00\x00\x10\x00S\x00G\x00T\x00M\x00e\x00n\x00s\x00a)
|
||||||
|
|
||||||
|
[Project]
|
||||||
|
VersionControlSupport=kdevgit
|
||||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,65 @@
|
|||||||
|
@startuml
|
||||||
|
|
||||||
|
' Teilnehmer – Standard‑Icons
|
||||||
|
actor Student as "Student"
|
||||||
|
participant Browser as "Browser"
|
||||||
|
participant WebsiteServer as "Website Server"
|
||||||
|
database OrderDB as "Order DB"
|
||||||
|
participant OrderService as "Order Service"
|
||||||
|
participant InventoryService as "Inventory Service"
|
||||||
|
participant KitchenDisplaySystem as "Kitchen Display"
|
||||||
|
participant NotificationService as "Notification Service"
|
||||||
|
|
||||||
|
' ---------- Login ----------
|
||||||
|
Student -> Browser : Öffnet die Webseite
|
||||||
|
Browser -> WebsiteServer : GET /login
|
||||||
|
WebsiteServer --> Browser : Liefert Login‑Seite
|
||||||
|
Student -> Browser : Gibt Benutzerdaten ein und schickt Formular
|
||||||
|
Browser -> WebsiteServer : POST /login\n{username, password}
|
||||||
|
WebsiteServer --> OrderService : Authentifiziere User
|
||||||
|
OrderService --> WebsiteServer : OK (Session‑Token)
|
||||||
|
WebsiteServer --> Browser : Setzt Session‑Cookie & leitet zur Menüseite
|
||||||
|
|
||||||
|
' ---------- Menü auswählen ----------
|
||||||
|
Student -> Browser : Wählt Speisen aus dem Menü
|
||||||
|
Browser -> WebsiteServer : GET /menu\n{session}
|
||||||
|
WebsiteServer --> OrderDB : Liefert aktuelle Menüpunkte
|
||||||
|
OrderDB --> WebsiteServer : Menüdaten
|
||||||
|
WebsiteServer --> Browser : Zeigt Menü
|
||||||
|
|
||||||
|
' ---------- Artikel zum Warenkorb hinzufügen ----------
|
||||||
|
loop Für jeden gewählten Artikel
|
||||||
|
Student -> Browser : Klickt „In den Warenkorb“
|
||||||
|
Browser -> OrderService : POST /cart/add\n{session, articleID}
|
||||||
|
OrderService -> OrderDB : Update Cart (Add Item)
|
||||||
|
OrderDB --> OrderService : OK
|
||||||
|
OrderService --> Browser : Rückmeldung "Artikel hinzugefügt"
|
||||||
|
end
|
||||||
|
|
||||||
|
' ---------- Bestellung prüfen ----------
|
||||||
|
Student -> Browser : Öffnet Warenkorb und prüft Bestellübersicht
|
||||||
|
Browser -> OrderService : GET /cart\n{session}
|
||||||
|
OrderService -> OrderDB : Liefert aktuelle Cart‑Daten
|
||||||
|
OrderDB --> OrderService : Cart‑Details
|
||||||
|
OrderService --> Browser : Zeigt Cart‑Übersicht
|
||||||
|
|
||||||
|
' ---------- Checkout (ohne Bezahlung) ----------
|
||||||
|
Student -> Browser : Klicke „Zur Kasse“
|
||||||
|
Browser -> WebsiteServer : GET /checkout\n{session}
|
||||||
|
WebsiteServer --> NotificationService : Sende “Bestellung anstehend” (Optional)
|
||||||
|
NotificationService --> Browser : Bestätigungsnachricht
|
||||||
|
Student -> Browser : Gibt Zahlungsinformationen für Bezahlung vor Ort ein und schickt Formular
|
||||||
|
Browser -> OrderService : POST /checkout/confirm\n{session, paymentMethod="VorOrt"}
|
||||||
|
OrderService --> OrderDB : Speichere Order & set Status “Pending Payment”
|
||||||
|
OrderDB --> OrderService : OK
|
||||||
|
OrderService --> KitchenDisplaySystem : Sende Order‑Details (Zubereitung)
|
||||||
|
KitchenDisplaySystem --> Browser : Zeigt „Bestellung in Bearbeitung“
|
||||||
|
OrderService -> InventoryService : Update Bestand
|
||||||
|
InventoryService --> OrderService : OK
|
||||||
|
OrderService --> NotificationService : Sende “Bestellung bestätigt” (Email/SMS)
|
||||||
|
|
||||||
|
' ---------- Fertigstellung ----------
|
||||||
|
Student -> Browser : Empfängt Benachrichtigung “Ihr Essen ist fertig”
|
||||||
|
Browser -> KitchenDisplaySystem : Optional „Abholung bestätigen“
|
||||||
|
|
||||||
|
@enduml
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Generated by Django 5.2.14 on 2026-05-15 09:28
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mensa_app', '0004_alter_gericht_time_creation_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='bestellung',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gericht',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gericht',
|
||||||
|
name='time_creation',
|
||||||
|
field=models.TimeField(default=datetime.datetime(2026, 5, 15, 9, 28, 5, 292454)),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='gericht',
|
||||||
|
name='time_last_change',
|
||||||
|
field=models.TimeField(default=datetime.datetime(2026, 5, 15, 9, 28, 5, 292454)),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='kategorie',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='menue',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='person',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='speiseplantag',
|
||||||
|
name='id',
|
||||||
|
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Bewertung',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('sterne', models.IntegerField(choices=[(1, '★☆☆☆☆'), (2, '★★☆☆☆'), (3, '★★★☆☆'), (4, '★★★★☆'), (5, '★★★★★')], default=3)),
|
||||||
|
('kommentar', models.TextField(blank=True, null=True)),
|
||||||
|
('datum', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('ist_verifiziert', models.BooleanField(default=False, help_text='Wird automatisch auf True gesetzt, wenn eine bezahlte Bestellung vorliegt.')),
|
||||||
|
('gericht', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bewertungen', to='mensa_app.gericht')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bewertungen', to='mensa_app.person')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Bewertung',
|
||||||
|
'verbose_name_plural': 'Bewertungen',
|
||||||
|
'unique_together': {('user', 'gericht')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Bestell-Zusammenfassung</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<!-- PAGER & DATUM -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h2 class="mb-3">Bestell-Check für den <span class="text-success">{{ target_date|date:"d.m.Y" }}</span></h2>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="?datum={{ prev_date }}" class="btn btn-outline-secondary {% if not prev_date %}disabled{% endif %}">« Vorheriger Tag</a>
|
||||||
|
<a href="?" class="btn btn-primary">Heute</a>
|
||||||
|
<a href="?datum={{ next_date }}" class="btn btn-outline-secondary {% if not next_date %}disabled{% endif %}">Nächster Tag »</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DASHBOARD CARDS -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-white shadow border-start border-primary border-5">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted">Gesamtanzahl Bestellungen</h6>
|
||||||
|
<h3 class="mb-0">{{ total_bestellungen }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card bg-white shadow border-start border-success border-5">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted">Erwarteter Umsatz</h6>
|
||||||
|
<h3 class="mb-0">{{ total_umsatz|floatformat:2 }} €</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- DETAILS TABELLE -->
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-dark text-white">Details pro Gericht</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Gericht</th>
|
||||||
|
<th>Kategorie</th>
|
||||||
|
<th class="text-center">Menge</th>
|
||||||
|
<th class="text-end">Umsatz</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for stat in summary_stats %}
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ stat.menue__gericht__name }}</strong></td>
|
||||||
|
<td><span class="badge bg-info text-dark">{{ stat.menue__gericht__kategorie__name }}</span></td>
|
||||||
|
<td class="text-center">{{ stat.anzahl }}</td>
|
||||||
|
<td class="text-end">{{ stat.umsatz|floatformat:2 }} €</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-4 text-muted">Keine Bestellungen für diesen Tag vorhanden.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-3 text-end">
|
||||||
|
<a href="{% url 'speisekarte' %}" class="btn btn-link text-decoration-none">← Zurück zur Speisekarte</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mensa-Speisekarte</title>
|
||||||
|
<!-- Bootstrap CSS für schnelles Styling -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class='card-header bg-primary text-white text-center py-3'>
|
||||||
|
<h1><i class="bi bi-egg-fried"></i> Unsere Speisekarte</h1>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Gericht</th>
|
||||||
|
<th>Kategorie</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for gericht in alle_gerichte %}
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ gericht.name }}</strong></td>
|
||||||
|
<td><span class="badge bg-info text-dark">{{ gericht.kategorie.name }}</span></td>
|
||||||
|
<td>
|
||||||
|
{% if gericht.ist_dauerangebot %}
|
||||||
|
<span class="badge bg-success">Dauerangebot</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">Tagesangebot</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center text-muted">Keine Gerichte im System gefunden.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mensa Speiseplan</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
|
||||||
|
<!-- PAGER & DATUM ANZEIGE -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h2 class="mb-3">Speiseplan für den <br><span class="text-primary">{{ target_date|date:"d.m.Y" }}</span></h2>
|
||||||
|
|
||||||
|
<div class="btn-group" role="group" aria-label="Datum Navigation">
|
||||||
|
<!-- Button Zurück -->
|
||||||
|
{% if prev_date %}
|
||||||
|
<a href="?datum={{ prev_date }}" class="btn btn-outline-secondary">« Vorheriger Tag</a>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-outline-light text-muted" disabled>« Vorheriger</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Button Heute (Reset) -->
|
||||||
|
<a href="?" class="btn btn-primary">Heute</a>
|
||||||
|
|
||||||
|
<!-- Button Weiter -->
|
||||||
|
{% if next_date %}
|
||||||
|
<a href="?datum={{ next_date }}" class="btn btn-outline-secondary">Nächster Tag »</a>
|
||||||
|
{% else %}
|
||||||
|
<button class="btn btn-outline-light text-muted" disabled>Nächster »</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TABELLE DER TAGESMENÜS -->
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-header bg-dark text-white">Tagesmenüs</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<table class="table table-striped mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Gericht</th>
|
||||||
|
<th>Kategorie</th>
|
||||||
|
<th>Preis</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for menue in menues_day %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ menue.gericht.name }}</td>
|
||||||
|
<td><span class="badge bg-info text-dark">{{ menue.gericht.kategorie.name }}</span></td>
|
||||||
|
<td>{{ menue.preis|floatformat:2 }} €</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center py-3 text-muted">Keine Tagesmenüs für diesen Tag geplant.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TABELLE DER DAUERANGEBOTE -->
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-success text-white">Immer verfügbar (Dauerangebote)</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{% for gericht in dauerangebote %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ gericht.name }}
|
||||||
|
<span class="badge bg-success rounded-pill">Verfügbar</span>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">Momentan keine Dauerangebote.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +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='speisekarte'),
|
||||||
|
path('speiseplan/', SpeiseplanView.as_view(), name='speiseplan'),
|
||||||
|
path('bestellungen/summary/', BestellSummaryView.as_view(), name='bestell_summary'),
|
||||||
|
]
|
||||||
@@ -1,3 +1,101 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from .models import Gericht
|
||||||
|
|
||||||
|
class GerichtListView(ListView):
|
||||||
|
model = Gericht
|
||||||
|
template_name = 'mensa_app/gericht_liste.html' # Der Pfad zum Template
|
||||||
|
context_object_name = 'alle_gerichte' # Der Name, den wir im Template nutzen
|
||||||
|
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import datetime
|
||||||
|
from .models import Menue, Gericht, SpeiseplanTag
|
||||||
|
|
||||||
|
class SpeiseplanView(TemplateView):
|
||||||
|
template_name = 'mensa_app/speiseplan.html'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# 1. Datum aus der URL holen (z.B. ?datum=2023-10-27)
|
||||||
|
# Wenn kein Datum angegeben ist, nehmen wir heute.
|
||||||
|
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.glob.now().date()
|
||||||
|
else:
|
||||||
|
target_date = timezone.now().date()
|
||||||
|
|
||||||
|
# 2. Menüs für diesen spezifischen Tag laden
|
||||||
|
# Wir suchen alle Menüs, deren Tag das target_date hat
|
||||||
|
context['target_date'] = target_date
|
||||||
|
context['menues_day'] = Menue.objects.filter(tag__datum=target_date)
|
||||||
|
|
||||||
|
# 3. Dauerangebote laden (unabhängig vom Tag)
|
||||||
|
context['dauerangebote'] = Gericht.objects.filter(ist_dauerangebot=True)
|
||||||
|
|
||||||
|
# 4. Pager-Logik: Vorherigen und nächsten Tag finden
|
||||||
|
# Wir suchen in der Tabelle SpeiseplanTag nach dem Tag davor/danach
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
-1
@@ -15,10 +15,12 @@ Including another URLconf
|
|||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('', include('mensa_app.urls')), # Schaltet die App-URLs frei
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user