Files
vergabe-teilnahme/workplans/WP-0010-partner-bibliothek.md
tegwick 278cc1014c feat(partner,bibliothek): Subunternehmer-Katalog, Dienstleistertypen und Bibliothek (WP-0010)
Implementiert Subunternehmer-Katalog mit Suche/Filter, Zuordnung zu Losen via HTMX-Modal,
Dienstleistertyp-CRUD und Präferenz-Badges. Bibliothek: Nachweis-Katalog mit Ablaufwarnung
und Versionierung, Referenz-Katalog mit Ausschreibungszuordnung, Leistungsblatt-CRUD,
Entscheidungsregel-CRUD mit Aktiv-Toggle. Migration für referenzen M2M auf Ausschreibung.
56 Tests grün. Tests-Discovery auf tests.py-Dateien ausgedehnt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:27:53 +02:00

7.8 KiB

id, title, status, phase, created, depends_on
id title status phase created depends_on
WP-0010 Subunternehmer, Partner und Bibliothek done 10-of-12 2026-05-08 WP-0009

WP-0010 — Subunternehmer, Partner und Bibliothek

Subunternehmer-Katalog, Dienstleistertypen, Nachweis-/Referenz-/Leistungsblatt-/ Entscheidungsregel-Verwaltung. Referenz: UC-SU-01 bis UC-SU-04, UC-BIB-01 bis UC-BIB-05.


id: WP-0010-T01
title: Subunternehmer-Katalog: Liste, Suche, Anlegen (UC-SU-01, UC-SU-03)
status: done

`partner/views.py` — subunternehmer_liste, subunternehmer_neu:

Liste: Filter nach Dienstleistertyp, Präferenz (bevorzugt/zugelassen/gesperrt), Freitext-Suche.
`Subunternehmer.objects.filter(name__icontains=q)` für Freitext.
Präferenz 'gesperrt': Rot-Badge, wird in Suchergebnissen mit Warnsymbol angezeigt.

`SubunternehmerForm(ModelForm)`: alle Felder, Präferenz als Radio-Buttons.

subunternehmer_detail: Stammdaten + verknüpfte Ausschreibungen (über SubunternehmerZuordnung):
```python
zuordnungen = SubunternehmerZuordnung.objects.filter(
    subunternehmer=obj
).select_related('ausschreibung', 'los').order_by('-ausschreibung__erstellt_am')

Zeigt: Ausschreibung, Los, Leistung, Zusage/Nachweis/Preis-Status. CustomAttribute-Panel.


```task
id: WP-0010-T02
title: Subunternehmer einer Ausschreibung/Los zuordnen (UC-SU-02)
status: done

`partner/views.py` — subunternehmer_zuordnen:

HTMX-Modal auf Los-Detail-Seite:
```python
def subunternehmer_suche_modal(request, ausschreibung_id, los_pk):
    q = request.GET.get('q', '')
    subunternehmer = Subunternehmer.objects.filter(name__icontains=q)
    return render(request, 'partner/partials/subunternehmer_suche.html',
                  {'subunternehmer': subunternehmer, 'los_pk': los_pk,
                   'ausschreibung_id': ausschreibung_id})

def subunternehmer_zuordnen(request, ausschreibung_id, los_pk):
    if request.method == 'POST':
        sub_id = request.POST['subunternehmer_id']
        sub = get_object_or_404(Subunternehmer, pk=sub_id)
        if sub.praeferenz == 'gesperrt':
            # Warnung anzeigen aber nicht blockieren
            pass
        los = get_object_or_404(Los, pk=los_pk)
        zuordnung, created = SubunternehmerZuordnung.objects.get_or_create(
            subunternehmer=sub,
            ausschreibung_id=ausschreibung_id,
            los=los,
            defaults={'konkrete_leistung': request.POST.get('konkrete_leistung', '')}
        )
        return render(request, 'partner/partials/zuordnung_zeile.html',
                      {'zuordnung': zuordnung})

Auf der Los-Detail-Seite zeigt jede Zuordnung: Name, Dienstleistertyp, Leistung, drei Checkboxen (Zusage, Nachweis, Preis) — HTMX-togglebar.


```task
id: WP-0010-T03
title: Dienstleistertyp-Katalog und Subunternehmer als gesperrt markieren (UC-SU-04)
status: done

`partner/views.py` — dienstleistertypen_liste, dienstleistertyp_neu/_bearbeiten:
Einfache CRUD-Views für Dienstleistertypen (Katalog-Daten).

`subunternehmer_praeferenz (POST)`:
```python
def subunternehmer_praeferenz(request, pk):
    sub = get_object_or_404(Subunternehmer, pk=pk)
    if request.method == 'POST':
        sub.praeferenz = request.POST['praeferenz']
        if sub.praeferenz == 'gesperrt':
            sub.bewertung = request.POST.get('begruendung', sub.bewertung)
        sub.save(update_fields=['praeferenz', 'bewertung'])
    return render(request, 'partner/partials/praeferenz_badge.html', {'sub': sub})

Bei Präferenz 'gesperrt': Roter Warnhinweis wenn dieser Subunternehmer bei Zuordnung gewählt wird (im Suchmodal).

partner/urls.py:

app_name = 'partner'
urlpatterns = [
    path('subunternehmer/', views.subunternehmer_liste, name='su_liste'),
    path('subunternehmer/neu/', views.subunternehmer_neu, name='su_neu'),
    path('subunternehmer/<int:pk>/', views.subunternehmer_detail, name='su_detail'),
    path('subunternehmer/<int:pk>/bearbeiten/', views.subunternehmer_bearbeiten, name='su_bearbeiten'),
    path('subunternehmer/<int:pk>/praeferenz/', views.subunternehmer_praeferenz, name='su_praeferenz'),
    path('dienstleistertypen/', views.dienstleistertypen_liste, name='dt_liste'),
    path('dienstleistertypen/neu/', views.dienstleistertyp_neu, name='dt_neu'),
]

```task
id: WP-0010-T04
title: Bibliothek: Nachweis-Katalog mit Ablaufwarnung (UC-BIB-01, UC-BIB-02)
status: done

`bibliothek/views.py` — nachweise_liste, nachweis_neu/_bearbeiten:

Liste mit Ablauffilter:
```python
from datetime import date, timedelta
heute = date.today()
in_60_tagen = heute + timedelta(days=60)

Tabs: "Alle", "Bald ablaufend" (gueltig_bis__lte=in_60_tagen), "Abgelaufen" (gueltig_bis__lt=heute). Abgelaufene Nachweise: Roter Badge. Bald ablaufende: Oranger Badge.

NachweisForm(ModelForm): datei als FileInput. gueltig_ab/gueltig_bis als DateInput type="date". Checkboxen für fuer_oeffentliche/fuer_privatwirtschaftliche.

nachweis_neue_version: Analog zu Dokument-Versionierung (WP-0007-T03). Alten Nachweis auf status='ersetzt', neuen anlegen mit höherer Version.

bibliothek/urls.py (Auszug):

path('nachweise/', views.nachweise_liste, name='nachweise_liste'),
path('nachweise/neu/', views.nachweis_neu, name='nachweis_neu'),
path('nachweise/<int:pk>/', views.nachweis_detail, name='nachweis_detail'),
path('nachweise/<int:pk>/version/', views.nachweis_neue_version, name='nachweis_version'),

```task
id: WP-0010-T05
title: Bibliothek: Referenz anlegen und zuordnen (UC-BIB-03, UC-BIB-04)
status: done

`bibliothek/views.py` — referenzen_liste, referenz_neu/_bearbeiten:

`ReferenzForm(ModelForm)`:
`whitepaper` als FileInput. `projektzeitraum` als CharField (freies Datum-Format: "2024-2025").
`leistungsblaetter` als CheckboxSelectMultiple.

referenz_detail: Zeigt alle Felder, Whitepaper-Download-Link, verknüpfte Leistungsblätter.

`referenz_zuordnen (POST)`:
Wird von Ausschreibungs-Abgabe-Seite aufgerufen.
Erstellt eine M2M-Verknüpfung zwischen Referenz und Ausschreibung.
(Ergänze `referenzen` M2M-Feld auf Ausschreibung-Modell + Migration)

HTMX-Suchmodal: Suche nach Branche, Leistungsbeschreibung, Titel.
Zeigt Freigabestatus und Nutzungseinschränkungen als Warnung.
id: WP-0010-T06
title: Bibliothek: Leistungsblatt und Entscheidungsregel (UC-BIB-05)
status: done

`bibliothek/views.py` — leistungsblaetter_liste, leistungsblatt_neu/_bearbeiten:
Einfache CRUD-Views. `LeistungsblattForm(ModelForm)` mit allen Textfeldern.

`bibliothek/views.py` — entscheidungsregeln_liste, entscheidungsregel_neu/_bearbeiten:

`EntscheidungsregelForm(ModelForm)`:
`kategorie` als Select mit Choices:
[(ausschlusskriterium, 'Ausschlusskriterium'), (frist, 'Fristlage'),
 (referenz, 'Referenzanforderung'), (wirtschaftlichkeit, 'Wirtschaftlichkeit'),
 (ressourcen, 'Ressourcenverfügbarkeit'), (sonstiges, 'Sonstiges')]

`empfehlung` als Radio-Buttons (teilnehmen/nicht_teilnehmen/pruefen).
`aktiv`-Toggle in der Liste (HTMX POST).

Auf der Entscheidungsseite (Phase 2) werden nur `aktiv=True` Regeln angezeigt.
id: WP-0010-T07
title: Bibliothek URL-Verkabelung und Tests
status: done

`bibliothek/urls.py` vollständig:
```python
app_name = 'bibliothek'
urlpatterns = [
    path('nachweise/', ...),
    path('referenzen/', ...),
    path('leistungsblaetter/', ...),
    path('entscheidungsregeln/', ...),
    # Detail/Neu/Bearbeiten für jede Entität
]

Global in Haupt-URLs: path('bibliothek/', include('vergabe_teilnahme.apps.bibliothek.urls')) path('partner/', include('vergabe_teilnahme.apps.partner.urls'))

Tests:

  • Test: Nachweis mit abgelaufenem gueltig_bis → ist_abgelaufen property True
  • Test: Nachweis-Liste mit Filter "Abgelaufen" → nur abgelaufene sichtbar
  • Test: Subunternehmer-Zuordnung zu Los → SubunternehmerZuordnung-Objekt in DB
  • Test: Gesperrter Subunternehmer → Warnung im Modal sichtbar (Template enthält 'gesperrt')
  • Test: Entscheidungsregel mit aktiv=False → erscheint nicht in Phase-2-Auswertung