Aktualisierung nach merge, damit Datenmodell vollständig verfügbar ist. #6

Merged
mputzlocher merged 5 commits from main into gui-dev 2026-06-12 09:19:06 +00:00
41 changed files with 485 additions and 1 deletions
Showing only changes of commit 297ee45590 - Show all commits
+4
View File
@@ -0,0 +1,4 @@
__pycache__/
*.pyc
*.sqlite3
+5
View File
@@ -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
View File
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.
+65
View File
@@ -0,0 +1,65 @@
@startuml
' Teilnehmer StandardIcons
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 LoginSeite
Student -> Browser : Gibt Benutzerdaten ein und schickt Formular
Browser -> WebsiteServer : POST /login\n{username, password}
WebsiteServer --> OrderService : Authentifiziere User
OrderService --> WebsiteServer : OK (SessionToken)
WebsiteServer --> Browser : Setzt SessionCookie & 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 CartDaten
OrderDB --> OrderService : CartDetails
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 OrderDetails (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')},
},
),
]
@@ -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 %}">&laquo; 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 &raquo;</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">&larr; 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">&laquo; Vorheriger Tag</a>
{% else %}
<button class="btn btn-outline-light text-muted" disabled>&laquo; 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 &raquo;</a>
{% else %}
<button class="btn btn-outline-light text-muted" disabled>Nächster &raquo;</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>
+10
View File
@@ -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'),
]
+98
View File
@@ -1,3 +1,101 @@
from django.shortcuts import render
# 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
View File
@@ -15,10 +15,12 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('mensa_app.urls')), # Schaltet die App-URLs frei
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)