generated from coulomb/repo-seed
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>
253 lines
8.8 KiB
Python
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},
|
|
],
|
|
})
|