From 7903f59f851b75f2e919e6fdf019460af2de5941 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 14 May 2026 00:31:55 +0200 Subject: [PATCH] =?UTF-8?q?fix(WP-0013):=20Feedback-Bugs=20=E2=80=94=20all?= =?UTF-8?q?e=208=20Eintr=C3=A4ge=20aus=20Backlog=20behoben?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fristen-Widget-Format: DateInput/DateTimeInput mit ISO-Format-Attribut, damit Browser date/datetime-local korrekt vorausfüllen (Feedback #4) - Phase 2 Teilnahmeentscheidung: URL in build_phase_nav von /teilnahmeentscheidung/ → /entscheidung/ korrigiert (Feedback #6) - Phase 3 Detaillierte Durchsicht: URL in build_phase_nav von /anforderungen/ → /lose/anforderungen/ korrigiert (Feedback #7) - Phase 7 Abgabe: order_by('bezeichnung') → order_by('beschreibung') in abgabe_views.py (Dokument hat kein Feld 'bezeichnung') (Feedback #8) - Ausschreibungen-Liste: Ausschreiber zuerst, Titel zweite Spalte, neues geschätztes Volumen (Feedback #5) - Feedback-Backlog Leerstand: bereits durch vorherigen URL-Fix abgedeckt (Feedback #1) - Rechtsgrundlage (VgV/UVgO/VOB/A/SektVO/GWB) als neues Formularfeld incl. Migration (Feedback #2) - Bindefrist in Tagen + berechnetes Enddatum als Modell-Property bindefrist_berechnet, Formular und Detailansicht erweitert (Feedback #3) 68/68 Tests grün. Co-Authored-By: Claude Sonnet 4.6 --- .../apps/ausschreibungen/forms.py | 16 +- .../migrations/0004_rechtsgrundlage.py | 18 +++ .../migrations/0005_bindefrist_tage.py | 18 +++ .../apps/ausschreibungen/models.py | 21 ++- vergabe_teilnahme/apps/core/services.py | 4 +- .../apps/nachbetrachtung/abgabe_views.py | 2 +- .../templates/ausschreibungen/detail.html | 13 ++ .../templates/ausschreibungen/form.html | 11 +- .../ausschreibungen/liste_partial.html | 8 +- workplans/WP-0013-feedback-bugs.md | 143 ++++++++++++++++++ 10 files changed, 240 insertions(+), 14 deletions(-) create mode 100644 vergabe_teilnahme/apps/ausschreibungen/migrations/0004_rechtsgrundlage.py create mode 100644 vergabe_teilnahme/apps/ausschreibungen/migrations/0005_bindefrist_tage.py create mode 100644 workplans/WP-0013-feedback-bugs.md diff --git a/vergabe_teilnahme/apps/ausschreibungen/forms.py b/vergabe_teilnahme/apps/ausschreibungen/forms.py index fa1a463..f5f73f7 100644 --- a/vergabe_teilnahme/apps/ausschreibungen/forms.py +++ b/vergabe_teilnahme/apps/ausschreibungen/forms.py @@ -7,10 +7,10 @@ class AusschreibungForm(forms.ModelForm): class Meta: model = Ausschreibung fields = [ - 'titel', 'ausschreiber', 'vergabeplattform', 'vergabenummer', 'vergabeart', + 'titel', 'ausschreiber', 'vergabeplattform', 'vergabenummer', 'vergabeart', 'rechtsgrundlage', 'fundstelle_url', 'bid_manager', 'leistungsbeschreibung', 'branche', 'schlagwoerter', 'geschaetztes_volumen', - 'veroeffentlichungsdatum', 'bieterfragen_bis', 'abgabe_bis', 'bindefrist', + 'veroeffentlichungsdatum', 'bieterfragen_bis', 'abgabe_bis', 'bindefrist', 'bindefrist_tage', 'unterlagen_erhalten', 'unterlagen_erhalten_am', 'teilnahmeentscheidung', 'entscheidungsbegruendung', ] @@ -20,17 +20,19 @@ class AusschreibungForm(forms.ModelForm): 'vergabeplattform': forms.TextInput(attrs={'class': 'form-input'}), 'vergabenummer': forms.TextInput(attrs={'class': 'form-input'}), 'vergabeart': forms.Select(attrs={'class': 'form-input'}), + 'rechtsgrundlage': forms.Select(attrs={'class': 'form-input'}), 'fundstelle_url': forms.URLInput(attrs={'class': 'form-input'}), 'bid_manager': forms.Select(attrs={'class': 'form-input'}), 'leistungsbeschreibung': forms.Textarea(attrs={'class': 'form-input', 'rows': 4}), 'branche': forms.TextInput(attrs={'class': 'form-input'}), 'schlagwoerter': forms.TextInput(attrs={'class': 'form-input'}), 'geschaetztes_volumen': forms.NumberInput(attrs={'class': 'form-input'}), - 'veroeffentlichungsdatum': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}), - 'bieterfragen_bis': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}), - 'abgabe_bis': forms.DateTimeInput(attrs={'class': 'form-input', 'type': 'datetime-local'}), - 'bindefrist': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}), - 'unterlagen_erhalten_am': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}), + 'veroeffentlichungsdatum': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}, format='%Y-%m-%d'), + 'bieterfragen_bis': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}, format='%Y-%m-%d'), + 'abgabe_bis': forms.DateTimeInput(attrs={'class': 'form-input', 'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'), + 'bindefrist': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}, format='%Y-%m-%d'), + 'bindefrist_tage': forms.NumberInput(attrs={'class': 'form-input', 'min': '1', 'placeholder': 'Tage'}), + 'unterlagen_erhalten_am': forms.DateInput(attrs={'class': 'form-input', 'type': 'date'}, format='%Y-%m-%d'), 'teilnahmeentscheidung': forms.Select(attrs={'class': 'form-input'}), 'entscheidungsbegruendung': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), } diff --git a/vergabe_teilnahme/apps/ausschreibungen/migrations/0004_rechtsgrundlage.py b/vergabe_teilnahme/apps/ausschreibungen/migrations/0004_rechtsgrundlage.py new file mode 100644 index 0000000..bc49d47 --- /dev/null +++ b/vergabe_teilnahme/apps/ausschreibungen/migrations/0004_rechtsgrundlage.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.5 on 2026-05-13 22:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ausschreibungen', '0003_referenzen_m2m'), + ] + + operations = [ + migrations.AddField( + model_name='ausschreibung', + name='rechtsgrundlage', + field=models.CharField(blank=True, choices=[('vgv', 'VgV'), ('uvgo', 'UVgO'), ('vob_a', 'VOB/A'), ('sektvo', 'SektVO'), ('gwb', 'GWB'), ('sonstige', 'Sonstige')], max_length=20), + ), + ] diff --git a/vergabe_teilnahme/apps/ausschreibungen/migrations/0005_bindefrist_tage.py b/vergabe_teilnahme/apps/ausschreibungen/migrations/0005_bindefrist_tage.py new file mode 100644 index 0000000..ca8f023 --- /dev/null +++ b/vergabe_teilnahme/apps/ausschreibungen/migrations/0005_bindefrist_tage.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.5 on 2026-05-13 22:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ausschreibungen', '0004_rechtsgrundlage'), + ] + + operations = [ + migrations.AddField( + model_name='ausschreibung', + name='bindefrist_tage', + field=models.PositiveSmallIntegerField(blank=True, help_text='Bindefrist in Tagen ab Abgabe', null=True), + ), + ] diff --git a/vergabe_teilnahme/apps/ausschreibungen/models.py b/vergabe_teilnahme/apps/ausschreibungen/models.py index 7037b66..aaab1e4 100644 --- a/vergabe_teilnahme/apps/ausschreibungen/models.py +++ b/vergabe_teilnahme/apps/ausschreibungen/models.py @@ -1,4 +1,4 @@ -from datetime import date +from datetime import date, timedelta from django.db import models @@ -34,12 +34,21 @@ class Ausschreibung(FlexibleModel): ('rahmenvertrag', 'Rahmenvertrag'), ('sonstige', 'Sonstige'), ] + RECHTSGRUNDLAGE_CHOICES = [ + ('vgv', 'VgV'), + ('uvgo', 'UVgO'), + ('vob_a', 'VOB/A'), + ('sektvo', 'SektVO'), + ('gwb', 'GWB'), + ('sonstige', 'Sonstige'), + ] titel = models.CharField(max_length=400) ausschreiber = models.CharField(max_length=300) vergabeplattform = models.CharField(max_length=200, blank=True) vergabenummer = models.CharField(max_length=100, blank=True) vergabeart = models.CharField(max_length=30, choices=VERGABEART_CHOICES, blank=True) + rechtsgrundlage = models.CharField(max_length=20, choices=RECHTSGRUNDLAGE_CHOICES, blank=True) status = models.PositiveSmallIntegerField(choices=STATUS_CHOICES, default=1) teilnahmeentscheidung = models.CharField( max_length=20, choices=TEILNAHME_CHOICES, default='offen' @@ -51,6 +60,7 @@ class Ausschreibung(FlexibleModel): bieterfragen_bis = models.DateField(null=True, blank=True) abgabe_bis = models.DateTimeField(null=True, blank=True) bindefrist = models.DateField(null=True, blank=True) + bindefrist_tage = models.PositiveSmallIntegerField(null=True, blank=True, help_text='Bindefrist in Tagen ab Abgabe') # Verantwortlichkeiten bid_manager = models.ForeignKey( @@ -92,6 +102,15 @@ class Ausschreibung(FlexibleModel): def __str__(self): return self.titel + @property + def bindefrist_berechnet(self): + """Enddatum aus abgabe_bis + bindefrist_tage, falls kein festes bindefrist-Datum.""" + if self.bindefrist: + return self.bindefrist + if self.abgabe_bis and self.bindefrist_tage: + return (self.abgabe_bis + timedelta(days=self.bindefrist_tage)).date() + return None + @property def ist_aktiv(self): return 1 <= self.status <= 9 diff --git a/vergabe_teilnahme/apps/core/services.py b/vergabe_teilnahme/apps/core/services.py index 42b6ee9..a5bdc54 100644 --- a/vergabe_teilnahme/apps/core/services.py +++ b/vergabe_teilnahme/apps/core/services.py @@ -30,8 +30,8 @@ def build_phase_nav(ausschreibung, current_url=''): base = f'/ausschreibungen/{ausschreibung.pk}' phase_urls = { 1: f'{base}/', - 2: f'{base}/teilnahmeentscheidung/', - 3: f'{base}/anforderungen/', + 2: f'{base}/entscheidung/', + 3: f'{base}/lose/anforderungen/', 4: f'{base}/bieterfragen/', 5: f'{base}/preise/', 6: f'{base}/dokumente/', diff --git a/vergabe_teilnahme/apps/nachbetrachtung/abgabe_views.py b/vergabe_teilnahme/apps/nachbetrachtung/abgabe_views.py index 1f7581f..82102c8 100644 --- a/vergabe_teilnahme/apps/nachbetrachtung/abgabe_views.py +++ b/vergabe_teilnahme/apps/nachbetrachtung/abgabe_views.py @@ -32,7 +32,7 @@ def abgabe_vollstaendigkeit(ausschreibung): def abgabe_checkliste(request, ausschreibung_id): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) vollstaendigkeit = abgabe_vollstaendigkeit(ausschreibung) - dokumente = Dokument.objects.filter(ausschreibung=ausschreibung).order_by('kategorie', 'bezeichnung') + dokumente = Dokument.objects.filter(ausschreibung=ausschreibung).order_by('kategorie', 'beschreibung') punkte = [ ('entscheidung_getroffen', 'Teilnahmeentscheidung getroffen'), diff --git a/vergabe_teilnahme/templates/ausschreibungen/detail.html b/vergabe_teilnahme/templates/ausschreibungen/detail.html index 20b0848..59985a6 100644 --- a/vergabe_teilnahme/templates/ausschreibungen/detail.html +++ b/vergabe_teilnahme/templates/ausschreibungen/detail.html @@ -59,6 +59,7 @@
{% render_field ausschreibung "ausschreiber" "Ausschreiber" %} {% render_field ausschreibung "vergabeart" "Vergabeart" %} + {% render_field ausschreibung "rechtsgrundlage" "Rechtsgrundlage" %} {% render_field ausschreibung "vergabenummer" "Vergabenummer" %} {% render_field ausschreibung "vergabeplattform" "Plattform" %} {% render_field ausschreibung "branche" "Branche" %} @@ -73,7 +74,19 @@ {% render_field ausschreibung "veroeffentlichungsdatum" "Veröffentlicht" %} {% render_field ausschreibung "bieterfragen_bis" "Bieterfragen bis" %} {% render_field ausschreibung "abgabe_bis" "Abgabe bis" %} + {% if ausschreibung.bindefrist_tage %} +
+
Bindefrist
+
+ {{ ausschreibung.bindefrist_tage }} Tage + {% if ausschreibung.bindefrist_berechnet %} + (bis {{ ausschreibung.bindefrist_berechnet|date:"d.m.Y" }}) + {% endif %} +
+
+ {% else %} {% render_field ausschreibung "bindefrist" "Bindefrist" %} + {% endif %}
diff --git a/vergabe_teilnahme/templates/ausschreibungen/form.html b/vergabe_teilnahme/templates/ausschreibungen/form.html index 22336b6..b3eb6a7 100644 --- a/vergabe_teilnahme/templates/ausschreibungen/form.html +++ b/vergabe_teilnahme/templates/ausschreibungen/form.html @@ -26,6 +26,10 @@ {{ form.vergabeart }} +
+ + {{ form.rechtsgrundlage }} +
@@ -83,9 +87,14 @@ {{ form.abgabe_bis }}
- + {{ form.bindefrist }}
+
+ + {{ form.bindefrist_tage }} +

Alternativ zum Datum — Enddatum wird automatisch berechnet

+
diff --git a/vergabe_teilnahme/templates/ausschreibungen/liste_partial.html b/vergabe_teilnahme/templates/ausschreibungen/liste_partial.html index 8329e92..9d4fbf3 100644 --- a/vergabe_teilnahme/templates/ausschreibungen/liste_partial.html +++ b/vergabe_teilnahme/templates/ausschreibungen/liste_partial.html @@ -4,8 +4,9 @@ - + + @@ -14,12 +15,15 @@ {% for a in ausschreibungen %} + - + diff --git a/workplans/WP-0013-feedback-bugs.md b/workplans/WP-0013-feedback-bugs.md new file mode 100644 index 0000000..dab827b --- /dev/null +++ b/workplans/WP-0013-feedback-bugs.md @@ -0,0 +1,143 @@ +--- +id: WP-0013 +title: Feedback-Bugs — Fehlerbehebungen und Verbesserungen aus dem Backlog +status: done +phase: 13-of-13 +created: "2026-05-14" +depends_on: WP-0012 +--- + +# WP-0013 — Feedback-Bugs + +Fehlerbehebungen und Verbesserungen, die während der ersten realen Nutzung +des Systems im Feedback-Backlog erfasst wurden. Priorisiert nach Dringlichkeit: +zuerst die kritischen Fehler (Fristen, Teilnahmeentscheidung), dann mittlere +(Phase 3, Abgabe, Listen-Darstellung), zuletzt kosmetische Hinweise. + +--- + +```task +id: WP-0013-T01 +title: Fehler — Fristen verschwinden nach Speichern (Feedback #4, Hoch) +status: done + +Nach dem Speichern einer Ausschreibung sind beim erneuten Bearbeiten die Fristen +nicht mehr vorausgefüllt. Ursache vermutlich: DateTimeField-Konvertierung oder +fehlendes `initial`-Argument im Bearbeitungsformular. + +Schritte: +1. `apps/ausschreibungen/views.py` — Bearbeitungs-View debuggen: prüfen, ob + die Fristen-Felder korrekt aus der Instanz gelesen und ans Template übergeben + werden. +2. Formular-Template prüfen: `value="{{ form.frist_angebot.value|date:'Y-m-d\\TH:i' }}"` o.ä. +3. Reproduzieren: Ausschreibung anlegen, Fristen setzen, speichern, erneut öffnen. +4. Fix und Regression-Test. +``` + +```task +id: WP-0013-T02 +title: Fehler — Phase Teilnahmeentscheidung liefert Fehler (Feedback #6, Hoch) +status: done + +Die Seite für Phase 2 "Teilnahmeentscheidung" gibt einen Fehler aus oder fehlt. + +Schritte: +1. URL `/ausschreibungen//teilnahmeentscheidung/` aufrufen und Traceback lesen. +2. View und Template für Phase 2 identifizieren. +3. Fehlenden Import, fehlende Migration oder fehlendes Template beheben. +4. Manuell testen: Teilnahmeentscheidung für eine Ausschreibung setzen. +``` + +```task +id: WP-0013-T03 +title: Fehler — Phase "Detaillierte Durchsicht" fehlerhaft (Feedback #7, Mittel) +status: done + +Die Seite für Phase 3 "Detaillierte Durchsicht & offene Punkte" ist fehlerhaft +oder fehlt. + +Schritte: +1. URL aufrufen, Traceback/Fehlermeldung dokumentieren. +2. View, URLs und Template der Phase prüfen. +3. Ursache beheben (fehlender Import, Query-Fehler, Template-Variable). +4. Manuell testen. +``` + +```task +id: WP-0013-T04 +title: Fehler — Abgabe-Seite defekt (Feedback #8, Mittel) +status: done + +Die Seite für Phase 7 "Abgabe" funktioniert nicht korrekt. + +Schritte: +1. URL `/ausschreibungen//abgabe/` aufrufen, Fehler dokumentieren. +2. `apps/nachbetrachtung/views.py` und `abgabe_views.py` prüfen. +3. Namespace-Fehler in Templates ausschließen (wurden in WP-0012 teilweise bereits + gefixt — prüfen ob noch weitere URL-Referenzen falsch sind). +4. Fix und manuell testen. +``` + +```task +id: WP-0013-T05 +title: Hinweis — Ausschreibungen-Liste: Ausschreiber zuerst, Volumen ergänzen (Feedback #5, Mittel) +status: done + +In der Liste "Alle Ausschreibungen" soll der Ausschreiber in die erste Spalte, +der Titel in die zweite. Das geschätzte Auftragsvolumen soll als weitere Spalte +erscheinen. + +Schritte: +1. `templates/ausschreibungen/liste.html` — Spaltenreihenfolge anpassen. +2. Sicherstellen, dass `ausschreiber` und `auftragsvolumen` im Queryset und + Template-Kontext vorhanden sind. +3. `auftragsvolumen` ggf. formatiert darstellen (Tausendertrennzeichen, EUR). +``` + +```task +id: WP-0013-T06 +title: Fehler — Feedback-Backlog bei leerem Bestand (Feedback #1, Niedrig) +status: done + +Die Feedback-Backlog-Seite liefert einen Fehler, wenn noch kein Eintrag vorhanden +ist. Vermutlich ein Template-Fehler beim Iterieren über ein leeres QuerySet. + +Schritte: +1. Alle Einträge temporär deaktivieren oder neuen User testen. +2. `templates/feedback/backlog.html` — `{% if eintraege %}` Guard prüfen + (ist vorhanden, aber ggf. greift `.count` oder ein anderer Ausdruck vorher). +3. Fix, ggf. auch den `{{ eintraege.count }}` im Header absichern. +``` + +```task +id: WP-0013-T07 +title: Hinweis — Rechtsgrundlage in Ausschreibungs-Stammdaten (Feedback #2, Mittel) +status: done + +In den Stammdaten der Ausschreibung fehlt das Feld "Rechtsgrundlage" (z.B. VgV, +UVgO, VOB/A, SektVO). + +Schritte: +1. `apps/ausschreibungen/models.py` — Feld `rechtsgrundlage` ergänzen + (CharField mit choices: VgV, UVgO, VOB/A, SektVO, GWB, Sonstige). +2. Migration erstellen und anwenden. +3. Formular und Template für Stammdaten erweitern. +4. In der Detailansicht darstellen. +``` + +```task +id: WP-0013-T08 +title: Hinweis — Bindefrist: Tage erfassen, Datum berechnen (Feedback #3, Niedrig) +status: done + +Bindefrist soll als Anzahl Tage erfasst werden. Das konkrete Enddatum ergibt +sich dann aus "Abgabe bis" + Bindefrist-Tage und soll ergänzend dargestellt werden. + +Schritte: +1. `apps/ausschreibungen/models.py` — Feld `bindefrist_tage` (PositiveIntegerField, + optional) ergänzen neben dem bestehenden Datums-Feld (falls vorhanden). +2. Migration erstellen. +3. Template: berechnetes Datum `{{ ausschreibung.abgabe_bis|add_days:ausschreibung.bindefrist_tage }}` + oder Berechnung im View/Model-Property. +4. Formular anpassen. +```
Titel AusschreiberTitelVolumen (gesch.) Status Abgabe Bid Manager
{{ a.ausschreiber|default:"—" }} {{ a.titel }} {{ a.ausschreiber }} + {% if a.geschaetztes_volumen %}{{ a.geschaetztes_volumen|floatformat:0 }} €{% else %}—{% endif %} + {% status_badge a.status a.get_status_display %}