This repository has been archived on 2026-07-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
vergabe-teilnahme/vergabe_teilnahme/apps/ausschreibungen/views.py
tegwick 9ef6e0950a feat(aufgaben): Aufgaben-Panel auf Ausschreibungs-Detailseite + Sidebar-Link
Aufgaben sind jetzt direkt von der Detailseite erreichbar und anlegbar:
- Aufgaben-Panel mit HTMX-Inline-Formular auf ausschreibung_detail
- Globaler Sidebar-Link /aufgaben/ für ausschreibungsübergreifende Übersicht
- aufgaben-Queryset in ausschreibung_detail-Kontext ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 03:12:23 +02:00

253 lines
8.8 KiB
Python

from datetime import date, timedelta
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from vergabe_teilnahme.apps.accounts.models import Mitarbeiter
from .models import Ausschreibung
def _is_htmx(request):
return request.headers.get('HX-Request') == 'true'
def dashboard(request):
from vergabe_teilnahme.apps.aufgaben.models import Aufgabe
heute = date.today()
in_14_tagen = heute + timedelta(days=14)
ctx = {
'kritische_fristen': Ausschreibung.objects.filter(
abgabe_bis__date__lte=in_14_tagen,
abgabe_bis__date__gte=heute,
status__lt=10,
).order_by('abgabe_bis')[:10],
'ohne_entscheidung': Ausschreibung.objects.filter(
teilnahmeentscheidung='offen',
erstellt_am__lte=timezone.now() - timedelta(days=3),
status__lt=10,
).order_by('erstellt_am')[:10],
'ueberfaellige_aufgaben': Aufgabe.objects.filter(
frist__lt=heute,
status__in=['offen', 'in_bearbeitung', 'wartend_intern', 'wartend_sub', 'wartend_ausschreiber'],
).select_related('ausschreibung').order_by('frist')[:15],
'laufende_ausschreibungen': Ausschreibung.objects.filter(
status__range=(1, 9),
).order_by('-geaendert_am')[:10],
'breadcrumbs': [{'label': 'Übersicht', 'url': None}],
}
return render(request, 'ausschreibungen/dashboard.html', ctx)
def ausschreibung_liste(request):
qs = Ausschreibung.objects.all()
status_filter = request.GET.get('status')
if status_filter:
qs = qs.filter(status=status_filter)
archiviert = request.GET.get('archiviert', '0') == '1'
qs = qs.filter(archiviert=archiviert)
bid_manager_filter = request.GET.get('bid_manager')
if bid_manager_filter:
qs = qs.filter(bid_manager=bid_manager_filter)
from django.db.models import Count, Q
qs = qs.select_related('bid_manager').annotate(
aufgaben_total=Count('aufgaben', distinct=True),
aufgaben_erledigt=Count(
'aufgaben',
filter=Q(aufgaben__status__in=['erledigt', 'verworfen']),
distinct=True,
),
).order_by('-geaendert_am')
ctx = {
'ausschreibungen': qs,
'status_choices': Ausschreibung.STATUS_CHOICES,
'mitarbeiter': Mitarbeiter.objects.all(),
'archiviert': archiviert,
'current_status': status_filter or '',
'current_bid_manager': bid_manager_filter or '',
'breadcrumbs': [{'label': 'Ausschreibungen', 'url': None}],
}
if _is_htmx(request):
return render(request, 'ausschreibungen/liste_partial.html', ctx)
return render(request, 'ausschreibungen/liste.html', ctx)
def ausschreibung_neu(request):
from .forms import AusschreibungForm
historisch = request.GET.get('historisch') == '1'
if request.method == 'POST':
form = AusschreibungForm(request.POST)
if form.is_valid():
a = form.save()
return redirect('ausschreibungen:detail', pk=a.pk)
else:
form = AusschreibungForm()
return render(request, 'ausschreibungen/form.html', {
'form': form,
'historisch': historisch,
'titel': 'Neue Ausschreibung',
'breadcrumbs': [
{'label': 'Ausschreibungen', 'url': '/ausschreibungen/'},
{'label': 'Neu', 'url': None},
],
})
def ausschreibung_detail(request, pk):
from django.db.models import Count, Q
from vergabe_teilnahme.apps.aufgaben.models import Aufgabe
from vergabe_teilnahme.apps.core.services import aufgaben_score, build_phase_nav, get_deadline_warnings
from vergabe_teilnahme.apps.lose.models import Los
a = get_object_or_404(Ausschreibung, pk=pk)
from vergabe_teilnahme.apps.aufgaben.models import Aufgabe as AufgabeModel
aufgaben = AufgabeModel.objects.filter(ausschreibung=a).select_related(
'verantwortlicher', 'los'
).order_by('prioritaet', 'frist')
lose = Los.objects.filter(ausschreibung=a).annotate(
aufgaben_total=Count('aufgaben', distinct=True),
aufgaben_erledigt=Count(
'aufgaben',
filter=Q(aufgaben__status__in=['erledigt', 'verworfen']),
distinct=True,
),
).order_by('losnummer')
ctx = {
'ausschreibung': a,
'ausschreibung_id': pk,
'lose': lose,
'aufgaben': aufgaben,
'phases': build_phase_nav(a),
'warnungen': get_deadline_warnings(a),
'aufgaben_score': aufgaben_score(Aufgabe.objects.filter(ausschreibung=a)),
'breadcrumbs': [
{'label': 'Ausschreibungen', 'url': '/ausschreibungen/'},
{'label': a.titel, 'url': None},
],
}
return render(request, 'ausschreibungen/detail.html', ctx)
def ausschreibung_bearbeiten(request, pk):
from .forms import AusschreibungForm
a = get_object_or_404(Ausschreibung, pk=pk)
form = AusschreibungForm(request.POST or None, instance=a)
if request.method == 'POST' and form.is_valid():
form.save()
return redirect('ausschreibungen:detail', pk=pk)
return render(request, 'ausschreibungen/form.html', {
'form': form,
'titel': 'Ausschreibung bearbeiten',
'ausschreibung': a,
'breadcrumbs': [
{'label': 'Ausschreibungen', 'url': '/ausschreibungen/'},
{'label': a.titel, 'url': f'/ausschreibungen/{pk}/'},
{'label': 'Bearbeiten', 'url': None},
],
})
def ausschreibung_status(request, pk):
a = get_object_or_404(Ausschreibung, pk=pk)
if request.method == 'POST':
neuer_status = request.POST.get('status')
if neuer_status and neuer_status.isdigit():
a.status = int(neuer_status)
a.save(update_fields=['status', 'geaendert_am'])
return render(request, 'ausschreibungen/partials/status_widget.html', {'ausschreibung': a})
def ausschreibung_entscheidung(request, pk):
from .services import entscheidungsregel_auswertung
a = get_object_or_404(Ausschreibung, pk=pk)
if request.method == 'POST':
a.teilnahmeentscheidung = request.POST.get('teilnahmeentscheidung', 'offen')
a.entscheidungsbegruendung = request.POST.get('begruendung', a.entscheidungsbegruendung)
if a.teilnahmeentscheidung in ['teilnahme', 'ablehnung']:
a.status = max(a.status, 3)
a.save()
return redirect('ausschreibungen:detail', pk=pk)
from vergabe_teilnahme.apps.lose.models import Anforderung
ausschlusskriterien = Anforderung.objects.filter(
ausschreibung=a,
ausschlusskriterium=True,
erfuellungsstatus='nicht_erfuellbar',
).select_related('los')
ctx = {
'ausschreibung': a,
'regelergebnis': entscheidungsregel_auswertung(a),
'ausschlusskriterien_nicht_erfuellbar': ausschlusskriterien,
'breadcrumbs': [
{'label': 'Ausschreibungen', 'url': '/ausschreibungen/'},
{'label': a.titel, 'url': f'/ausschreibungen/{pk}/'},
{'label': 'Teilnahmeentscheidung', 'url': None},
],
}
return render(request, 'ausschreibungen/entscheidung.html', ctx)
def freigaben_uebersicht(request, pk):
from django.contrib.contenttypes.models import ContentType
from vergabe_teilnahme.apps.core.models import Freigabe
a = get_object_or_404(Ausschreibung, pk=pk)
ct = ContentType.objects.get_for_model(a)
freigaben = Freigabe.objects.filter(
content_type=ct, object_id=pk
).select_related('freigebende_person').order_by('-timestamp')
erteilte_typen = set(freigaben.filter(status='erteilt').values_list('freigabe_typ', flat=True))
erforderliche_typen = {'teilnahme', 'preis', 'abgabe'}
fehlende_typen = erforderliche_typen - erteilte_typen
ctx = {
'ausschreibung': a,
'freigaben': freigaben,
'fehlende_typen': fehlende_typen,
'ct_id': ct.pk,
'freigabe_typ_choices': Freigabe.TYP_CHOICES,
'breadcrumbs': [
{'label': 'Ausschreibungen', 'url': '/ausschreibungen/'},
{'label': a.titel, 'url': f'/ausschreibungen/{pk}/'},
{'label': 'Freigaben', 'url': None},
],
}
return render(request, 'ausschreibungen/freigaben.html', ctx)
def ausschreibung_archivieren(request, pk):
a = get_object_or_404(Ausschreibung, pk=pk)
if request.method == 'POST':
a.archiviert = True
a.status = 13
a.save(update_fields=['archiviert', 'status', 'geaendert_am'])
return redirect('ausschreibungen:liste')
return render(request, 'ausschreibungen/archivieren_confirm.html', {
'ausschreibung': a,
'breadcrumbs': [
{'label': 'Ausschreibungen', 'url': '/ausschreibungen/'},
{'label': a.titel, 'url': f'/ausschreibungen/{pk}/'},
{'label': 'Archivieren', 'url': None},
],
})