generated from coulomb/repo-seed
Implementiert UC-MB-01 bis UC-MB-03: Marktbegleiter-Katalog (Liste, Detail, Anlegen/Bearbeiten), Ausschreibungspassagen mit Verlässlichkeitsscore (1–10, Validator), Musterauswertung mit Aggregationen (Ausschreiber-Häufigkeit, Ø-Score, Passagen-Anzahl). 4 Tests grün. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 lines
5.3 KiB
Python
114 lines
5.3 KiB
Python
from django.db.models import Avg, Count
|
|
from django import forms
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
|
|
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,
|
|
})
|