Established workplans
This commit is contained in:
parent
026f7c0db1
commit
315143a6fc
16 changed files with 5492 additions and 0 deletions
211
workplans/WP-0005-lose-anforderungen.md
Normal file
211
workplans/WP-0005-lose-anforderungen.md
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
---
|
||||
id: WP-0005
|
||||
title: Lose und Anforderungen
|
||||
status: todo
|
||||
phase: 5-of-12
|
||||
created: "2026-05-08"
|
||||
depends_on: WP-0004
|
||||
---
|
||||
|
||||
# WP-0005 — Lose und Anforderungen
|
||||
|
||||
Implementiert alle Views, Forms und Templates für Lose (UC-LA-01) und Anforderungen
|
||||
(UC-LA-02 bis UC-LA-05) inklusive Nachweis-Verknüpfung und Ausschlusskriterium-Eskalation.
|
||||
|
||||
**URL-Präfix:** `/ausschreibungen/<ausschreibung_id>/lose/` und `.../anforderungen/`
|
||||
|
||||
---
|
||||
|
||||
```task
|
||||
id: WP-0005-T01
|
||||
title: Lose-Liste und Lose anlegen (UC-LA-01)
|
||||
status: todo
|
||||
|
||||
`lose/views.py` — lose_liste und los_neu:
|
||||
|
||||
lose_liste: Zeigt alle Lose einer Ausschreibung geordnet nach Losnummer.
|
||||
Template: `lose/liste.html` — Tabelle mit Losnummer, Lostitel, Zuständiger, Teilnahme (Badge), Status.
|
||||
"+ Los hinzufügen"-Button öffnet Inline-Formular via HTMX.
|
||||
|
||||
`LosForm(ModelForm)`: Felder losnummer, lostitel, beschreibung, abgrenzung, zustaendiger, teilnahme.
|
||||
Alle Inputs mit `form-input`, Textarea mit 3 rows.
|
||||
|
||||
los_neu (POST): Erstellt Los, gibt bei HTMX-Request nur neuen Tabellen-Row zurück.
|
||||
los_bearbeiten (GET/POST): Edit in eigenem Template oder Inline.
|
||||
los_loeschen (POST): Löscht Los nach Bestätigung.
|
||||
|
||||
URLs in `lose/urls.py`:
|
||||
```python
|
||||
path('', views.lose_liste, name='liste'),
|
||||
path('neu/', views.los_neu, name='neu'),
|
||||
path('<int:los_pk>/', views.los_detail, name='detail'),
|
||||
path('<int:los_pk>/bearbeiten/', views.los_bearbeiten, name='bearbeiten'),
|
||||
```
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T02
|
||||
title: Los-Detail-Seite mit eingebetteten Anforderungen
|
||||
status: todo
|
||||
|
||||
`lose/views.py` — los_detail:
|
||||
```python
|
||||
def los_detail(request, ausschreibung_id, los_pk):
|
||||
los = get_object_or_404(Los, pk=los_pk, ausschreibung_id=ausschreibung_id)
|
||||
ctx = {
|
||||
'los': los,
|
||||
'ausschreibung': los.ausschreibung,
|
||||
'anforderungen': los.anforderungen.all().order_by('verbindlichkeit', 'titel'),
|
||||
'subunternehmer': SubunternehmerZuordnung.objects.filter(los=los),
|
||||
...
|
||||
}
|
||||
return render(request, 'lose/detail.html', ctx)
|
||||
```
|
||||
|
||||
`lose/detail.html`:
|
||||
- Los-Stammdaten (render_field Tags)
|
||||
- Abschnitt "Anforderungen" — Tabelle mit Link zur Anforderungsdetailseite
|
||||
- Abschnitt "Subunternehmer" — Liste zugeordneter Subunternehmer
|
||||
- CustomAttribute-Panel (HTMX lazy, wie in WP-0012)
|
||||
- Abschnitt "Teilnahme": Toggle Ja/Nein/Offen (HTMX POST)
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T03
|
||||
title: Anforderungsliste nach Los gruppiert (UC-LA-02)
|
||||
status: todo
|
||||
|
||||
`lose/views.py` — anforderungen_liste:
|
||||
Lädt alle Anforderungen der Ausschreibung, gruppiert nach Los.
|
||||
Anforderungen ohne Los-Zuordnung erscheinen in "Allgemein".
|
||||
|
||||
Template `lose/anforderungen_liste.html`:
|
||||
- Filter-Leiste: Verbindlichkeit (Muss/Soll/Kann), Erfüllungsstatus, Los, Zuständiger
|
||||
- Alle Filteränderungen via HTMX ohne Reload
|
||||
- Pro Gruppe: aufklappbarer Akkordeon-Abschnitt (Alpine.js x-show)
|
||||
- Jede Zeile: Titel, Verbindlichkeit-Badge, Erfüllungsstatus-Badge, Zuständiger
|
||||
- Rote Hervorhebung bei Ausschlusskriterium + nicht_erfuellbar
|
||||
- "+ Anforderung" Button oben rechts
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T04
|
||||
title: Anforderung anlegen und Detailseite (UC-LA-02, UC-LA-03)
|
||||
status: todo
|
||||
|
||||
`AnforderungForm(ModelForm)`: alle Felder aus Modell.
|
||||
Besonderer Widget für verbindlichkeit: Radio-Buttons statt Dropdown.
|
||||
|
||||
anforderung_neu: Liest ausschreibung_id aus URL. Los-Dropdown zeigt nur Lose der aktuellen Ausschreibung.
|
||||
|
||||
anforderung_detail: Zeigt alle Felder (via render_field), Kommentarverlauf als Timeline
|
||||
(für v1: einfache Textarea + gespeicherte Kommentare als JSONField auf Anforderung).
|
||||
Verknüpfte Dokumente, Nachweise und Aufgaben als Listen.
|
||||
|
||||
Erfüllungsstatus inline ändern (UC-LA-03):
|
||||
HTMX-Endpunkt `anforderung_status`:
|
||||
```python
|
||||
def anforderung_status(request, ausschreibung_id, pk):
|
||||
a = get_object_or_404(Anforderung, pk=pk, ausschreibung_id=ausschreibung_id)
|
||||
if request.method == 'POST':
|
||||
a.erfuellungsstatus = request.POST['erfuellungsstatus']
|
||||
a.save(update_fields=['erfuellungsstatus'])
|
||||
return render(request, 'lose/partials/erfuellungsstatus_widget.html', {'anforderung': a})
|
||||
```
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T05
|
||||
title: Nachweis-Verknüpfung mit Bibliothek (UC-LA-04)
|
||||
status: todo
|
||||
|
||||
`lose/views.py` — nachweis_suche_modal und nachweis_zuordnen:
|
||||
|
||||
Auf der Anforderungsdetailseite: Button "Nachweis zuordnen".
|
||||
```html
|
||||
<button hx-get="{% url 'lose:nachweis_suche' ausschreibung.pk anforderung.pk %}"
|
||||
hx-target="#nachweis-modal"
|
||||
class="btn-secondary">Nachweis zuordnen</button>
|
||||
<div id="nachweis-modal"></div>
|
||||
```
|
||||
|
||||
nachweis_suche_modal (GET): Gibt Such-Modal zurück mit Textfeld + Ergebnisliste (HTMX-Suche im Bibliothek-Bestand).
|
||||
Jeder Treffer zeigt: Titel, Ablaufdatum, Freigabestatus. Ablaufende/abgelaufene Nachweise in Orange/Rot.
|
||||
|
||||
nachweis_zuordnen (POST): Fügt Nachweis via M2M hinzu.
|
||||
nachweis_entfernen (DELETE/POST): Entfernt M2M-Verknüpfung.
|
||||
|
||||
Zeige zugeordnete Nachweise auf Anforderungsdetail als Liste mit Ablaufstatus-Badge.
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T06
|
||||
title: Ausschlusskriterium-Eskalation auf Phase-2-Seite (UC-LA-05)
|
||||
status: todo
|
||||
|
||||
Ergänze `ausschreibungen/views.py` — ausschreibung_entscheidung:
|
||||
|
||||
Vor dem Laden der Seite: Prüfe ob es Anforderungen mit
|
||||
`ausschlusskriterium=True AND erfuellungsstatus='nicht_erfuellbar'` gibt.
|
||||
|
||||
Falls ja: Zeige oben auf der Entscheidungsseite einen roten Alert-Banner:
|
||||
```html
|
||||
{% if ausschlusskriterien_nicht_erfuellbar %}
|
||||
<div class="bg-red-50 border border-red-300 rounded-lg p-4 mb-6">
|
||||
<p class="font-semibold text-red-700">⚠ Nicht erfüllbare Ausschlusskriterien</p>
|
||||
<ul class="mt-2 text-sm text-red-600 list-disc ml-4">
|
||||
{% for a in ausschlusskriterien_nicht_erfuellbar %}
|
||||
<li>{{ a.titel }} (Los: {{ a.los|default:"Allgemein" }})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="text-sm text-red-500 mt-2">Empfehlung: Nichtteilnahme</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
Prüfe: Seed-Daten mit einer nicht erfüllbaren Muss-Anforderung + Ausschlusskriterium anlegen,
|
||||
dann Phase-2-Seite öffnen → Banner erscheint.
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T07
|
||||
title: Aufgabe aus Anforderung ableiten (UC-AU-02)
|
||||
status: todo
|
||||
|
||||
Auf der Anforderungsdetailseite: Button "Aufgabe erstellen".
|
||||
```python
|
||||
def anforderung_aufgabe_erstellen(request, ausschreibung_id, pk):
|
||||
anforderung = get_object_or_404(Anforderung, pk=pk)
|
||||
if request.method == 'POST':
|
||||
from vergabe_teilnahme.apps.aufgaben.models import Aufgabe
|
||||
Aufgabe.objects.create(
|
||||
ausschreibung_id=ausschreibung_id,
|
||||
los=anforderung.los,
|
||||
anforderung=anforderung,
|
||||
titel=f"Klärung: {anforderung.titel[:200]}",
|
||||
typ='fachlich',
|
||||
verantwortlicher=anforderung.zustaendiger,
|
||||
)
|
||||
return redirect('ausschreibungen:lose:anforderung_detail',
|
||||
ausschreibung_id=ausschreibung_id, pk=pk)
|
||||
return render(request, 'lose/aufgabe_erstellen_confirm.html', {'anforderung': anforderung})
|
||||
```
|
||||
|
||||
Nach Erstellen: Anforderungsdetail zeigt die neue Aufgabe im Abschnitt "Verbundene Aufgaben".
|
||||
```
|
||||
|
||||
```task
|
||||
id: WP-0005-T08
|
||||
title: Tests für Lose und Anforderungen
|
||||
status: todo
|
||||
|
||||
`lose/tests/test_views.py`:
|
||||
- Test: Lose-Liste gibt 200 zurück
|
||||
- Test: Los anlegen mit POST → Redirect, Los existiert in DB
|
||||
- Test: Anforderung anlegen mit Muss-Verbindlichkeit
|
||||
- Test: Erfüllungsstatus via HTMX-POST auf 'nicht_erfuellbar' setzen
|
||||
- Test: Ausschlusskriterium + nicht_erfuellbar → Entscheidungsseite zeigt Alert-Banner
|
||||
- Test: Nachweis-Verknüpfung über M2M
|
||||
|
||||
Nutze AusschreibungFactory und erstelle Factories für Los und Anforderung.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue