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 @@
Alternativ zum Datum — Enddatum wird automatisch berechnet
+| Titel | Ausschreiber | +Titel | +Volumen (gesch.) | Status | Abgabe | Bid Manager | @@ -14,12 +15,15 @@
|---|---|---|---|---|---|---|
| {{ 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 %} | 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/