from django.shortcuts import get_object_or_404, redirect, render from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung from vergabe_teilnahme.apps.bibliothek.models import Nachweis from .forms import AnforderungForm, LosForm from .models import Anforderung, Los def _is_htmx(request): return request.headers.get('HX-Request') == 'true' def _ausschreibung_breadcrumbs(ausschreibung, *extra): crumbs = [ {'label': 'Ausschreibungen', 'url': '/ausschreibungen/'}, {'label': ausschreibung.titel, 'url': f'/ausschreibungen/{ausschreibung.pk}/'}, ] for label, url in extra: crumbs.append({'label': label, 'url': url}) return crumbs # ─── Lose ──────────────────────────────────────────────────────────────────── def lose_liste(request, ausschreibung_id): from django.db.models import Count, Q ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) lose = Los.objects.filter(ausschreibung=ausschreibung).annotate( aufgaben_total=Count('aufgaben', distinct=True), aufgaben_erledigt=Count( 'aufgaben', filter=Q(aufgaben__status__in=['erledigt', 'verworfen']), distinct=True, ), ).order_by('losnummer') return render(request, 'lose/liste.html', { 'ausschreibung': ausschreibung, 'lose': lose, 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Lose', None)), }) def los_neu(request, ausschreibung_id): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) if request.method == 'POST': form = LosForm(request.POST, ausschreibung=ausschreibung) if form.is_valid(): los = form.save(commit=False) los.ausschreibung = ausschreibung los.save() if _is_htmx(request): return render(request, 'lose/partials/los_row.html', {'los': los, 'ausschreibung': ausschreibung}) return redirect('ausschreibungen:lose:liste', ausschreibung_id=ausschreibung_id) else: form = LosForm(ausschreibung=ausschreibung) if _is_htmx(request): return render(request, 'lose/partials/los_form_inline.html', { 'form': form, 'ausschreibung': ausschreibung, }) return render(request, 'lose/form.html', { 'form': form, 'ausschreibung': ausschreibung, 'titel': 'Los hinzufügen', 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Lose', f'/ausschreibungen/{ausschreibung_id}/lose/'), ('Neu', None)), }) def los_detail(request, ausschreibung_id, los_pk): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) los = get_object_or_404(Los, pk=los_pk, ausschreibung=ausschreibung) anforderungen = los.anforderungen.all().order_by('verbindlichkeit', 'titel') return render(request, 'lose/detail.html', { 'ausschreibung': ausschreibung, 'los': los, 'anforderungen': anforderungen, 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Lose', f'/ausschreibungen/{ausschreibung_id}/lose/'), (str(los), None)), }) def los_bearbeiten(request, ausschreibung_id, los_pk): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) los = get_object_or_404(Los, pk=los_pk, ausschreibung=ausschreibung) form = LosForm(request.POST or None, instance=los, ausschreibung=ausschreibung) if request.method == 'POST' and form.is_valid(): form.save() return redirect('ausschreibungen:lose:detail', ausschreibung_id=ausschreibung_id, los_pk=los_pk) return render(request, 'lose/form.html', { 'form': form, 'ausschreibung': ausschreibung, 'los': los, 'titel': 'Los bearbeiten', 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Lose', f'/ausschreibungen/{ausschreibung_id}/lose/'), (str(los), f'/ausschreibungen/{ausschreibung_id}/lose/{los_pk}/'), ('Bearbeiten', None)), }) def los_loeschen(request, ausschreibung_id, los_pk): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) los = get_object_or_404(Los, pk=los_pk, ausschreibung=ausschreibung) if request.method == 'POST': los.delete() return redirect('ausschreibungen:lose:liste', ausschreibung_id=ausschreibung_id) return render(request, 'lose/loeschen_confirm.html', { 'ausschreibung': ausschreibung, 'los': los, 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Lose', f'/ausschreibungen/{ausschreibung_id}/lose/'), (str(los), f'/ausschreibungen/{ausschreibung_id}/lose/{los_pk}/'), ('Löschen', None)), }) # ─── Anforderungen ─────────────────────────────────────────────────────────── def anforderungen_liste(request, ausschreibung_id): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) qs = Anforderung.objects.filter(ausschreibung=ausschreibung).select_related('los', 'zustaendiger') verbindlichkeit = request.GET.get('verbindlichkeit', '') if verbindlichkeit: qs = qs.filter(verbindlichkeit=verbindlichkeit) status_filter = request.GET.get('erfuellungsstatus', '') if status_filter: qs = qs.filter(erfuellungsstatus=status_filter) los_filter = request.GET.get('los', '') if los_filter == 'allgemein': qs = qs.filter(los__isnull=True) elif los_filter: qs = qs.filter(los_id=los_filter) lose = Los.objects.filter(ausschreibung=ausschreibung).order_by('losnummer') grouped = {} for los in lose: grouped[los] = [] grouped[None] = [] for a in qs.order_by('verbindlichkeit', 'titel'): grouped[a.los].append(a) grouped = {k: v for k, v in grouped.items() if v} ctx = { 'ausschreibung': ausschreibung, 'grouped': grouped, 'lose': lose, 'verbindlichkeit_choices': Anforderung.VERBINDLICHKEIT_CHOICES, 'erfuellung_choices': Anforderung.ERFUELLUNG_CHOICES, 'current_verbindlichkeit': verbindlichkeit, 'current_status': status_filter, 'current_los': los_filter, 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Anforderungen', None)), } if _is_htmx(request): return render(request, 'lose/anforderungen_liste_partial.html', ctx) return render(request, 'lose/anforderungen_liste.html', ctx) def anforderung_neu(request, ausschreibung_id): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) if request.method == 'POST': form = AnforderungForm(request.POST, ausschreibung=ausschreibung) if form.is_valid(): a = form.save(commit=False) a.ausschreibung = ausschreibung a.save() return redirect('ausschreibungen:lose:anforderung_detail', ausschreibung_id=ausschreibung_id, pk=a.pk) else: los_pk = request.GET.get('los') initial = {'los': los_pk} if los_pk else {} form = AnforderungForm(ausschreibung=ausschreibung, initial=initial) return render(request, 'lose/anforderung_form.html', { 'form': form, 'ausschreibung': ausschreibung, 'titel': 'Anforderung anlegen', 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Anforderungen', f'/ausschreibungen/{ausschreibung_id}/lose/anforderungen/'), ('Neu', None)), }) def anforderung_detail(request, ausschreibung_id, pk): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung=ausschreibung) aufgaben = anforderung.aufgaben.all().order_by('prioritaet', 'frist') return render(request, 'lose/anforderung_detail.html', { 'ausschreibung': ausschreibung, 'anforderung': anforderung, 'aufgaben': aufgaben, 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Anforderungen', f'/ausschreibungen/{ausschreibung_id}/lose/anforderungen/'), (anforderung.titel[:50], None)), }) def anforderung_bearbeiten(request, ausschreibung_id, pk): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung=ausschreibung) form = AnforderungForm(request.POST or None, instance=anforderung, ausschreibung=ausschreibung) if request.method == 'POST' and form.is_valid(): form.save() return redirect('ausschreibungen:lose:anforderung_detail', ausschreibung_id=ausschreibung_id, pk=pk) return render(request, 'lose/anforderung_form.html', { 'form': form, 'ausschreibung': ausschreibung, 'anforderung': anforderung, 'titel': 'Anforderung bearbeiten', 'breadcrumbs': _ausschreibung_breadcrumbs(ausschreibung, ('Anforderungen', f'/ausschreibungen/{ausschreibung_id}/lose/anforderungen/'), (anforderung.titel[:50], f'/ausschreibungen/{ausschreibung_id}/lose/anforderungen/{pk}/'), ('Bearbeiten', None)), }) def anforderung_status(request, ausschreibung_id, pk): anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung_id=ausschreibung_id) if request.method == 'POST': neuer_status = request.POST.get('erfuellungsstatus') if neuer_status: anforderung.erfuellungsstatus = neuer_status anforderung.save(update_fields=['erfuellungsstatus']) return render(request, 'lose/partials/erfuellungsstatus_widget.html', {'anforderung': anforderung}) # ─── Nachweise ─────────────────────────────────────────────────────────────── def nachweis_suche_modal(request, ausschreibung_id, pk): anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung_id=ausschreibung_id) q = request.GET.get('q', '') nachweise = Nachweis.objects.all() if q: nachweise = nachweise.filter(titel__icontains=q) bereits_zugeordnet = anforderung.nachweise.values_list('pk', flat=True) return render(request, 'lose/partials/nachweis_modal.html', { 'anforderung': anforderung, 'nachweise': nachweise[:20], 'bereits_zugeordnet': list(bereits_zugeordnet), 'q': q, 'ausschreibung_id': ausschreibung_id, }) def nachweis_zuordnen(request, ausschreibung_id, pk): anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung_id=ausschreibung_id) if request.method == 'POST': nachweis_pk = request.POST.get('nachweis_pk') if nachweis_pk: nachweis = get_object_or_404(Nachweis, pk=nachweis_pk) anforderung.nachweise.add(nachweis) return render(request, 'lose/partials/nachweise_liste.html', { 'anforderung': anforderung, 'ausschreibung_id': ausschreibung_id, }) def nachweis_entfernen(request, ausschreibung_id, pk, nachweis_pk): anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung_id=ausschreibung_id) if request.method == 'POST': nachweis = get_object_or_404(Nachweis, pk=nachweis_pk) anforderung.nachweise.remove(nachweis) return render(request, 'lose/partials/nachweise_liste.html', { 'anforderung': anforderung, 'ausschreibung_id': ausschreibung_id, }) # ─── Aufgabe aus Anforderung ───────────────────────────────────────────────── def anforderung_aufgabe_erstellen(request, ausschreibung_id, pk): ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id) anforderung = get_object_or_404(Anforderung, pk=pk, ausschreibung=ausschreibung) if request.method == 'POST': from vergabe_teilnahme.apps.aufgaben.models import Aufgabe Aufgabe.objects.create( ausschreibung=ausschreibung, los=anforderung.los, anforderung=anforderung, titel=f'Klärung: {anforderung.titel[:200]}', typ='fachlich', verantwortlicher=anforderung.zustaendiger, ) return redirect('ausschreibungen:lose:anforderung_detail', ausschreibung_id=ausschreibung_id, pk=pk) return render(request, 'lose/aufgabe_erstellen_confirm.html', { 'ausschreibung': ausschreibung, 'anforderung': anforderung, })