diff --git a/vergabe_teilnahme/apps/marktbegleiter/passagen_urls.py b/vergabe_teilnahme/apps/marktbegleiter/passagen_urls.py index e39cb2c..6b8c2d0 100644 --- a/vergabe_teilnahme/apps/marktbegleiter/passagen_urls.py +++ b/vergabe_teilnahme/apps/marktbegleiter/passagen_urls.py @@ -1,3 +1,10 @@ from django.urls import path -urlpatterns = [] +from . import passagen_views + +urlpatterns = [ + path('', passagen_views.passagen_liste, name='liste'), + path('neu/', passagen_views.passage_neu, name='neu'), + path('/', passagen_views.passage_detail, name='detail'), + path('/bearbeiten/', passagen_views.passage_bearbeiten, name='bearbeiten'), +] diff --git a/vergabe_teilnahme/apps/marktbegleiter/passagen_views.py b/vergabe_teilnahme/apps/marktbegleiter/passagen_views.py new file mode 100644 index 0000000..ef8e090 --- /dev/null +++ b/vergabe_teilnahme/apps/marktbegleiter/passagen_views.py @@ -0,0 +1,110 @@ +from django import forms +from django.shortcuts import get_object_or_404, redirect, render + +from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung +from vergabe_teilnahme.apps.dokumente.models import Dokument + +from .models import Ausschreibungspassage, Marktbegleiter + + +KATEGORIE_CHOICES = [ + ('formulierung', 'Formulierung'), + ('leistungsmerkmal', 'Leistungsmerkmal'), + ('zertifizierung', 'Zertifizierung'), + ('referenz', 'Referenz'), + ('sonstiges', 'Sonstiges'), +] + + +class AusschreibungspassageForm(forms.ModelForm): + class Meta: + model = Ausschreibungspassage + fields = [ + 'passage', 'marktbegleiter', 'dokument', 'fundstelle', + 'kategorie', 'verlaesslichkeitsscore', + 'begruendung_zuordnung', 'auswirkung_entscheidung', + 'auswirkung_preisstrategie', 'auswirkung_loesungskonzept', + ] + widgets = { + 'passage': forms.Textarea(attrs={'class': 'form-input', 'rows': 6}), + 'marktbegleiter': forms.Select(attrs={'class': 'form-select'}), + 'dokument': forms.Select(attrs={'class': 'form-select'}), + 'fundstelle': forms.TextInput(attrs={'class': 'form-input'}), + 'kategorie': forms.Select(attrs={'class': 'form-select'}, choices=[('', '---------')] + KATEGORIE_CHOICES), + 'verlaesslichkeitsscore': forms.NumberInput(attrs={ + 'class': 'form-input', 'type': 'range', 'min': 1, 'max': 10, + 'x-model': 'score', + }), + 'begruendung_zuordnung': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + 'auswirkung_entscheidung': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + 'auswirkung_preisstrategie': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + 'auswirkung_loesungskonzept': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + } + + def __init__(self, *args, ausschreibung=None, **kwargs): + super().__init__(*args, **kwargs) + self.fields['marktbegleiter'].queryset = Marktbegleiter.objects.all() + if ausschreibung: + self.fields['dokument'].queryset = Dokument.objects.filter(ausschreibung=ausschreibung) + else: + self.fields['dokument'].queryset = Dokument.objects.none() + self.fields['dokument'].required = False + self.fields['fundstelle'].required = False + self.fields['kategorie'].required = False + self.fields['begruendung_zuordnung'].required = False + self.fields['auswirkung_entscheidung'].required = False + self.fields['auswirkung_preisstrategie'].required = False + self.fields['auswirkung_loesungskonzept'].required = False + + +def passagen_liste(request, ausschreibung_id): + ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) + passagen = Ausschreibungspassage.objects.filter( + ausschreibung=ausschreibung + ).select_related('marktbegleiter', 'dokument').order_by('-verlaesslichkeitsscore') + return render(request, 'marktbegleiter/passagen_liste.html', { + 'ausschreibung': ausschreibung, + 'passagen': passagen, + }) + + +def passage_neu(request, ausschreibung_id): + ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) + form = AusschreibungspassageForm( + request.POST or None, ausschreibung=ausschreibung + ) + if form.is_valid(): + passage = form.save(commit=False) + passage.ausschreibung = ausschreibung + passage.save() + return redirect('marktbegleiter:passagen:liste', ausschreibung_id=ausschreibung_id) + return render(request, 'marktbegleiter/passage_form.html', { + 'form': form, + 'ausschreibung': ausschreibung, + 'neu': True, + }) + + +def passage_detail(request, ausschreibung_id, pk): + ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) + passage = get_object_or_404(Ausschreibungspassage, pk=pk, ausschreibung=ausschreibung) + return render(request, 'marktbegleiter/passage_detail.html', { + 'ausschreibung': ausschreibung, + 'passage': passage, + }) + + +def passage_bearbeiten(request, ausschreibung_id, pk): + ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) + passage = get_object_or_404(Ausschreibungspassage, pk=pk, ausschreibung=ausschreibung) + form = AusschreibungspassageForm( + request.POST or None, instance=passage, ausschreibung=ausschreibung + ) + if form.is_valid(): + form.save() + return redirect('marktbegleiter:passagen:detail', ausschreibung_id=ausschreibung_id, pk=pk) + return render(request, 'marktbegleiter/passage_form.html', { + 'form': form, + 'ausschreibung': ausschreibung, + 'passage': passage, + }) diff --git a/vergabe_teilnahme/apps/marktbegleiter/tests.py b/vergabe_teilnahme/apps/marktbegleiter/tests.py index 7ce503c..065935e 100644 --- a/vergabe_teilnahme/apps/marktbegleiter/tests.py +++ b/vergabe_teilnahme/apps/marktbegleiter/tests.py @@ -1,3 +1,70 @@ -from django.test import TestCase +import pytest +from django.core.exceptions import ValidationError +from django.urls import reverse -# Create your tests here. +from vergabe_teilnahme.apps.ausschreibungen.tests import AusschreibungFactory + +from .models import Ausschreibungspassage, Marktbegleiter + + +def make_mb(name='TestBegleiter', **kwargs): + return Marktbegleiter.objects.create(name=name, **kwargs) + + +@pytest.mark.django_db +def test_passage_anlegen_mit_score_10(client): + a = AusschreibungFactory() + mb = make_mb() + url = reverse('marktbegleiter:passagen:neu', kwargs={'ausschreibung_id': a.pk}) + response = client.post(url, { + 'passage': 'Musterpassage aus dem Dokument', + 'marktbegleiter': mb.pk, + 'verlaesslichkeitsscore': 10, + }) + assert response.status_code == 302 + assert Ausschreibungspassage.objects.filter(marktbegleiter=mb, verlaesslichkeitsscore=10).exists() + + +@pytest.mark.django_db +def test_passage_score_zu_hoch_validierungsfehler(): + a = AusschreibungFactory() + mb = make_mb() + p = Ausschreibungspassage( + ausschreibung=a, + marktbegleiter=mb, + passage='Test', + verlaesslichkeitsscore=11, + ) + with pytest.raises(ValidationError): + p.full_clean() + + +@pytest.mark.django_db +def test_auswertung_score_durchschnitt(client): + a = AusschreibungFactory() + mb = make_mb() + Ausschreibungspassage.objects.create( + ausschreibung=a, marktbegleiter=mb, passage='P1', verlaesslichkeitsscore=8 + ) + Ausschreibungspassage.objects.create( + ausschreibung=a, marktbegleiter=mb, passage='P2', verlaesslichkeitsscore=6 + ) + url = reverse('marktbegleiter:auswertung', kwargs={'pk': mb.pk}) + response = client.get(url) + assert response.status_code == 200 + content = response.content.decode() + assert '7' in content + + +@pytest.mark.django_db +def test_marktbegleiter_detail_zeigt_passagen(client): + a = AusschreibungFactory() + mb = make_mb(name='DetailBegleiter') + Ausschreibungspassage.objects.create( + ausschreibung=a, marktbegleiter=mb, + passage='Sichtbare Passage', verlaesslichkeitsscore=5 + ) + url = reverse('marktbegleiter:detail', kwargs={'pk': mb.pk}) + response = client.get(url) + assert response.status_code == 200 + assert b'Sichtbare Passage' in response.content diff --git a/vergabe_teilnahme/apps/marktbegleiter/urls.py b/vergabe_teilnahme/apps/marktbegleiter/urls.py index eb89e3b..518bb3c 100644 --- a/vergabe_teilnahme/apps/marktbegleiter/urls.py +++ b/vergabe_teilnahme/apps/marktbegleiter/urls.py @@ -1,2 +1,16 @@ -from django.urls import path -urlpatterns = [] +from django.urls import include, path + +from . import views + +app_name = 'marktbegleiter' + +urlpatterns = [ + path('', views.marktbegleiter_liste, name='liste'), + path('neu/', views.marktbegleiter_neu, name='neu'), + path('/', views.marktbegleiter_detail, name='detail'), + path('/auswertung/', views.marktbegleiter_auswertung, name='auswertung'), + path('/bearbeiten/', views.marktbegleiter_bearbeiten, name='bearbeiten'), + path('ausschreibungen//passagen/', include( + ('vergabe_teilnahme.apps.marktbegleiter.passagen_urls', 'passagen') + )), +] diff --git a/vergabe_teilnahme/apps/marktbegleiter/views.py b/vergabe_teilnahme/apps/marktbegleiter/views.py index 91ea44a..b3ca3f0 100644 --- a/vergabe_teilnahme/apps/marktbegleiter/views.py +++ b/vergabe_teilnahme/apps/marktbegleiter/views.py @@ -1,3 +1,113 @@ -from django.shortcuts import render +from django.db.models import Avg, Count +from django import forms +from django.shortcuts import get_object_or_404, redirect, render -# Create your views here. +from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung + +from .models import Ausschreibungspassage, Marktbegleiter + + +# ── Forms ───────────────────────────────────────────────────────────────────── + +class MarktbegleiterForm(forms.ModelForm): + class Meta: + model = Marktbegleiter + fields = [ + 'name', 'kurzbeschreibung', 'produkt_leistungsportfolio', + 'relevante_branchen', 'bekannte_staerken', 'bekannte_schwaechen', + 'typische_formulierungen', 'typische_leistungsmerkmale', + 'bekannte_zertifizierungen', 'bekannte_referenzen', + 'quellen_links', 'letzte_aktualisierung', 'interne_notizen', + 'vertraulichkeit', + ] + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-input'}), + 'kurzbeschreibung': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), + 'produkt_leistungsportfolio': forms.Textarea(attrs={'class': 'form-input', 'rows': 4}), + 'relevante_branchen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), + 'bekannte_staerken': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), + 'bekannte_schwaechen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), + 'typische_formulierungen': forms.Textarea(attrs={ + 'class': 'form-input', 'rows': 5, + 'placeholder': 'Eine Formulierung pro Zeile', + }), + 'typische_leistungsmerkmale': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), + 'bekannte_zertifizierungen': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + 'bekannte_referenzen': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + 'quellen_links': forms.Textarea(attrs={'class': 'form-input', 'rows': 2}), + 'letzte_aktualisierung': forms.DateInput(attrs={'type': 'date', 'class': 'form-input'}), + 'interne_notizen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}), + 'vertraulichkeit': forms.Select(attrs={'class': 'form-select'}), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['letzte_aktualisierung'].required = False + + +# ── Views ───────────────────────────────────────────────────────────────────── + +def marktbegleiter_liste(request): + qs = Marktbegleiter.objects.all() + q = request.GET.get('q', '') + branche = request.GET.get('branche', '') + if q: + qs = qs.filter(name__icontains=q) + if branche: + qs = qs.filter(relevante_branchen__icontains=branche) + return render(request, 'marktbegleiter/marktbegleiter_liste.html', { + 'marktbegleiter_liste': qs, + 'q': q, + 'branche': branche, + }) + + +def marktbegleiter_neu(request): + form = MarktbegleiterForm(request.POST or None) + if form.is_valid(): + obj = form.save() + return redirect('marktbegleiter:detail', pk=obj.pk) + return render(request, 'marktbegleiter/marktbegleiter_form.html', {'form': form, 'neu': True}) + + +def marktbegleiter_bearbeiten(request, pk): + obj = get_object_or_404(Marktbegleiter, pk=pk) + form = MarktbegleiterForm(request.POST or None, instance=obj) + if form.is_valid(): + form.save() + return redirect('marktbegleiter:detail', pk=obj.pk) + return render(request, 'marktbegleiter/marktbegleiter_form.html', {'form': form, 'obj': obj}) + + +def marktbegleiter_detail(request, pk): + obj = get_object_or_404(Marktbegleiter, pk=pk) + passagen = Ausschreibungspassage.objects.filter(marktbegleiter=obj).select_related( + 'ausschreibung', 'dokument' + ).order_by('-verlaesslichkeitsscore') + anzahl_ausschreibungen = passagen.values('ausschreibung').distinct().count() + score_durchschnitt = passagen.aggregate(Avg('verlaesslichkeitsscore'))['verlaesslichkeitsscore__avg'] + return render(request, 'marktbegleiter/marktbegleiter_detail.html', { + 'obj': obj, + 'passagen': passagen, + 'anzahl_ausschreibungen': anzahl_ausschreibungen, + 'score_durchschnitt': score_durchschnitt, + }) + + +def marktbegleiter_auswertung(request, pk): + mb = get_object_or_404(Marktbegleiter, pk=pk) + passagen = Ausschreibungspassage.objects.filter(marktbegleiter=mb).select_related( + 'ausschreibung', 'dokument' + ) + ausschreiber_haeufigkeit = passagen.values( + 'ausschreibung__ausschreiber' + ).annotate(count=Count('id')).order_by('-count')[:10] + score_durchschnitt = passagen.aggregate(Avg('verlaesslichkeitsscore'))['verlaesslichkeitsscore__avg'] + anzahl_ausschreibungen = passagen.values('ausschreibung').distinct().count() + return render(request, 'marktbegleiter/auswertung.html', { + 'marktbegleiter': mb, + 'passagen': passagen, + 'ausschreiber_haeufigkeit': ausschreiber_haeufigkeit, + 'score_durchschnitt': score_durchschnitt, + 'anzahl_ausschreibungen': anzahl_ausschreibungen, + }) diff --git a/vergabe_teilnahme/templates/marktbegleiter/auswertung.html b/vergabe_teilnahme/templates/marktbegleiter/auswertung.html new file mode 100644 index 0000000..025a25b --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/auswertung.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% block title %}Auswertung — {{ marktbegleiter.name }}{% endblock %} +{% block content %} +
+

Auswertung: {{ marktbegleiter.name }}

+ Zurück +
+ +
+
+

{{ passagen.count }}

+

Passagen gesamt

+
+
+

{{ anzahl_ausschreibungen }}

+

Ausschreibungen

+
+
+

+ {% if score_durchschnitt %}{{ score_durchschnitt|floatformat:1 }}{% else %}—{% endif %} +

+

Ø Verlässlichkeit

+
+
+ +{% if ausschreiber_haeufigkeit %} +
+

Häufigste Ausschreiber

+ + + + + + + + + {% for row in ausschreiber_haeufigkeit %} + + + + + {% endfor %} + +
AusschreiberPassagen
{{ row.ausschreibung__ausschreiber|default:"(unbekannt)" }}{{ row.count }}
+
+{% endif %} + +
+

Alle Passagen

+ {% if passagen %} + + + + + + + + + + + + {% for p in passagen %} + + + + + + + + {% endfor %} + +
AusschreibungTextauszugScoreKategorieDatum
{{ p.ausschreibung.titel|truncatechars:40 }}{{ p.passage|truncatechars:150 }} + {{ p.verlaesslichkeitsscore }} + {{ p.kategorie|default:"—" }}{{ p.erfassungsdatum }}
+ {% else %} +

Keine Passagen vorhanden.

+ {% endif %} +
+{% endblock %} diff --git a/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_detail.html b/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_detail.html new file mode 100644 index 0000000..b8bc46e --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_detail.html @@ -0,0 +1,118 @@ +{% extends "base.html" %} +{% block title %}{{ obj.name }}{% endblock %} +{% block content %} +
+

{{ obj.name }}

+ +
+ +
+
+ {% if obj.kurzbeschreibung %} +
+

Beschreibung

+

{{ obj.kurzbeschreibung }}

+
+ {% endif %} + {% if obj.produkt_leistungsportfolio %} +
+

Portfolio

+

{{ obj.produkt_leistungsportfolio }}

+
+ {% endif %} + {% if obj.bekannte_staerken %} +
+

Stärken

+

{{ obj.bekannte_staerken }}

+
+ {% endif %} + {% if obj.bekannte_schwaechen %} +
+

Schwächen

+

{{ obj.bekannte_schwaechen }}

+
+ {% endif %} + {% if obj.typische_formulierungen %} +
+

Typische Formulierungen

+
+ {% for zeile in obj.typische_formulierungen.splitlines %} + {% if zeile.strip %} + {{ zeile.strip }} + {% endif %} + {% endfor %} +
+
+ {% endif %} +
+ +
+
+

Kennzahlen

+
+
+
Ausschreibungen
+
{{ anzahl_ausschreibungen }}
+
+
+
Passagen gesamt
+
{{ passagen.count }}
+
+ {% if score_durchschnitt %} +
+
Ø Verlässlichkeit
+
{{ score_durchschnitt|floatformat:1 }}
+
+ {% endif %} +
+
Vertraulichkeit
+
{{ obj.get_vertraulichkeit_display }}
+
+ {% if obj.letzte_aktualisierung %} +
+
Aktualisiert
+
{{ obj.letzte_aktualisierung }}
+
+ {% endif %} +
+
+
+
+ +
+

Verknüpfte Passagen

+ {% if passagen %} + + + + + + + + + + + + {% for p in passagen %} + + + + + + + + {% endfor %} + +
AusschreibungTextauszugScoreKategorieDatum
{{ p.ausschreibung.titel|truncatechars:40 }}{{ p.passage|truncatechars:150 }} + + {{ p.verlaesslichkeitsscore }} + + {{ p.kategorie|default:"—" }}{{ p.erfassungsdatum }}
+ {% else %} +

Noch keine Passagen erfasst.

+ {% endif %} +
+{% endblock %} diff --git a/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_form.html b/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_form.html new file mode 100644 index 0000000..63d3d70 --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_form.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% block title %}{% if neu %}Neuer Marktbegleiter{% else %}{{ obj.name }} bearbeiten{% endif %}{% endblock %} +{% block content %} +
+

{% if neu %}Neuer Marktbegleiter{% else %}{{ obj.name }} bearbeiten{% endif %}

+ {% if not neu %} + Abbrechen + {% else %} + Abbrechen + {% endif %} +
+ +
+
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.errors %} +

{{ field.errors|join:", " }}

+ {% endif %} +
+ {% endfor %} +
+ +
+
+
+{% endblock %} diff --git a/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_liste.html b/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_liste.html new file mode 100644 index 0000000..8d6c3d8 --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/marktbegleiter_liste.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% block title %}Marktbegleiter{% endblock %} +{% block content %} +
+

Marktbegleiter

+ + Neu +
+ +
+
+
+ + +
+
+ + +
+ +
+
+ +
+ {% if marktbegleiter_liste %} + + + + + + + + + + + + {% for mb in marktbegleiter_liste %} + + + + + + + + {% endfor %} + +
NameBranchenVertraulichkeitAktualisiert
+ {{ mb.name }} + {{ mb.relevante_branchen|truncatechars:80|default:"—" }}{{ mb.get_vertraulichkeit_display }}{{ mb.letzte_aktualisierung|default:"—" }} + Bearbeiten +
+ {% else %} +

Keine Marktbegleiter gefunden.

+ {% endif %} +
+{% endblock %} diff --git a/vergabe_teilnahme/templates/marktbegleiter/passage_detail.html b/vergabe_teilnahme/templates/marktbegleiter/passage_detail.html new file mode 100644 index 0000000..80b81d6 --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/passage_detail.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% block title %}Passage — {{ passage.marktbegleiter.name }}{% endblock %} +{% block content %} +
+

Passage: {{ passage.marktbegleiter.name }}

+ +
+ +
+
+

Passage

+

{{ passage.passage }}

+
+
+
+

Verlässlichkeitsscore

+

+ {{ passage.verlaesslichkeitsscore }}/10 +

+
+
+

Kategorie

+

{{ passage.kategorie|default:"—" }}

+
+
+

Dokument

+

{{ passage.dokument|default:"—" }}

+
+
+

Fundstelle

+

{{ passage.fundstelle|default:"—" }}

+
+
+

Erfasst am

+

{{ passage.erfassungsdatum }}

+
+
+ {% if passage.begruendung_zuordnung %} +
+

Begründung

+

{{ passage.begruendung_zuordnung }}

+
+ {% endif %} +
+{% endblock %} diff --git a/vergabe_teilnahme/templates/marktbegleiter/passage_form.html b/vergabe_teilnahme/templates/marktbegleiter/passage_form.html new file mode 100644 index 0000000..b354c1e --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/passage_form.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} +{% block title %}{% if neu %}Neue Passage{% else %}Passage bearbeiten{% endif %}{% endblock %} +{% block content %} +
+

{% if neu %}Neue Passage{% else %}Passage bearbeiten{% endif %}

+ Abbrechen +
+

Ausschreibung: {{ ausschreibung.titel }}

+ +
+
+ {% csrf_token %} + {% for field in form %} +
+ + {% if field.name == 'verlaesslichkeitsscore' %} +
+ {{ field }} + + (1=sehr unsicher, 10=sehr sicher) +
+ {% else %} + {{ field }} + {% endif %} + {% if field.errors %} +

{{ field.errors|join:", " }}

+ {% endif %} +
+ {% endfor %} +
+ +
+
+
+{% endblock %} diff --git a/vergabe_teilnahme/templates/marktbegleiter/passagen_liste.html b/vergabe_teilnahme/templates/marktbegleiter/passagen_liste.html new file mode 100644 index 0000000..0aab63c --- /dev/null +++ b/vergabe_teilnahme/templates/marktbegleiter/passagen_liste.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% block title %}Marktbegleiter-Passagen — {{ ausschreibung.titel }}{% endblock %} +{% block content %} +
+

Marktbegleiter-Passagen

+
+ {{ ausschreibung.titel|truncatechars:50 }} + + Neue Passage +
+
+ +
+ {% if passagen %} + + + + + + + + + + + + + + {% for p in passagen %} + + + + + + + + + + {% endfor %} + +
MarktbegleiterTextauszugScoreKategorieDokumentDatum
+ + {{ p.marktbegleiter.name }} + + {{ p.passage|truncatechars:150 }} + {{ p.verlaesslichkeitsscore }} + {{ p.kategorie|default:"—" }}{{ p.dokument.dateiname|default:"—" }}{{ p.erfassungsdatum }} + Bearbeiten +
+ {% else %} +

Noch keine Passagen für diese Ausschreibung erfasst.

+ {% endif %} +
+{% endblock %} diff --git a/workplans/WP-0011-marktbegleiter.md b/workplans/WP-0011-marktbegleiter.md index 2d288a4..9e57b21 100644 --- a/workplans/WP-0011-marktbegleiter.md +++ b/workplans/WP-0011-marktbegleiter.md @@ -1,7 +1,7 @@ --- id: WP-0011 title: Marktbegleiter-Analyse -status: todo +status: done phase: 11-of-12 created: "2026-05-08" depends_on: WP-0010 @@ -17,7 +17,7 @@ Musterauswertung. Referenz: UC-MB-01 bis UC-MB-03. ```task id: WP-0011-T01 title: Marktbegleiter-Katalog: Liste und Anlegen (UC-MB-01) -status: todo +status: done `marktbegleiter/views.py` — marktbegleiter_liste, marktbegleiter_neu/_bearbeiten: @@ -38,7 +38,7 @@ marktbegleiter_detail: ```task id: WP-0011-T02 title: Ausschreibungspassage erfassen (UC-MB-02, UC-MB-03) -status: todo +status: done `marktbegleiter/passagen_views.py` — passagen_liste und passage_neu: @@ -61,7 +61,7 @@ und im Marktbegleiter-Profil. ```task id: WP-0011-T03 title: Marktbegleiter-Musterauswertung (UC-MB-03) -status: todo +status: done `marktbegleiter/views.py` — marktbegleiter_auswertung: @@ -97,7 +97,7 @@ Template `marktbegleiter/auswertung.html`: ```task id: WP-0011-T04 title: URL-Verkabelung und Tests -status: todo +status: done `marktbegleiter/passagen_urls.py`: ```python