Vollständigkeitsprüfung mit Freigaben-Check, Abgabe dokumentieren mit Nachweis-Upload, Nachbetrachtung mit Kickoff-Aufgabe (gewonnen) und Alpine.js-gesteuerter Verlustanalyse (verloren). 5 Tests grün. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
4.3 KiB
Python
102 lines
4.3 KiB
Python
import json
|
|
|
|
from django import forms
|
|
from django.contrib import messages
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
|
|
from vergabe_teilnahme.apps.accounts.models import Mitarbeiter
|
|
from vergabe_teilnahme.apps.aufgaben.models import Aufgabe
|
|
from vergabe_teilnahme.apps.ausschreibungen.models import Ausschreibung
|
|
|
|
from .models import Nachbetrachtung
|
|
|
|
|
|
class NachbetrachtungForm(forms.ModelForm):
|
|
class Meta:
|
|
model = Nachbetrachtung
|
|
fields = [
|
|
'ergebnis', 'zuschlagsdatum', 'projektverantwortlicher',
|
|
'abgegebene_unterlagen', 'abgegebene_preise',
|
|
'ausschlaggebende_zuschlagsmerkmale', 'lessons_learned',
|
|
'empfehlungen', 'wiederverwendbare_erkenntnisse_markiert',
|
|
]
|
|
widgets = {
|
|
'ergebnis': forms.RadioSelect(attrs={'class': 'mr-2'}),
|
|
'zuschlagsdatum': forms.DateInput(attrs={'type': 'date', 'class': 'form-input'}),
|
|
'projektverantwortlicher': forms.Select(attrs={'class': 'form-select'}),
|
|
'abgegebene_unterlagen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
|
|
'abgegebene_preise': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
|
|
'ausschlaggebende_zuschlagsmerkmale': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
|
|
'lessons_learned': forms.Textarea(attrs={'class': 'form-input', 'rows': 4}),
|
|
'empfehlungen': forms.Textarea(attrs={'class': 'form-input', 'rows': 3}),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['projektverantwortlicher'].queryset = Mitarbeiter.objects.all()
|
|
self.fields['projektverantwortlicher'].required = False
|
|
self.fields['zuschlagsdatum'].required = False
|
|
|
|
|
|
def nachbetrachtung_detail(request, ausschreibung_id):
|
|
ausschreibung = get_object_or_404(Ausschreibung, pk=ausschreibung_id)
|
|
nb, _ = Nachbetrachtung.objects.get_or_create(ausschreibung=ausschreibung)
|
|
|
|
if request.method == 'POST':
|
|
form = NachbetrachtungForm(request.POST, instance=nb)
|
|
verlustgruende_raw = request.POST.get('verlustgruende', '[]')
|
|
try:
|
|
verlustgruende = json.loads(verlustgruende_raw)
|
|
except (json.JSONDecodeError, ValueError):
|
|
verlustgruende = []
|
|
|
|
if form.is_valid():
|
|
nb = form.save(commit=False)
|
|
nb.verlustgruende = verlustgruende
|
|
nb.save()
|
|
|
|
ergebnis = form.cleaned_data['ergebnis']
|
|
if ergebnis == 'gewonnen':
|
|
ausschreibung.status = 10
|
|
ausschreibung.save(update_fields=['status', 'geaendert_am'])
|
|
aufgabe, created = Aufgabe.objects.get_or_create(
|
|
ausschreibung=ausschreibung,
|
|
titel='Kickoff vorbereiten',
|
|
defaults={
|
|
'typ': 'fachlich',
|
|
'prioritaet': 1,
|
|
'verantwortlicher': form.cleaned_data.get('projektverantwortlicher'),
|
|
'beschreibung': (
|
|
f'Kickoff für {ausschreibung.titel}. '
|
|
'Angebotsumfang und Annahmen übergeben.'
|
|
),
|
|
},
|
|
)
|
|
if created:
|
|
pv = form.cleaned_data.get('projektverantwortlicher')
|
|
pv_name = str(pv) if pv else '—'
|
|
messages.success(request, f'Kickoff-Aufgabe erstellt für {pv_name}.')
|
|
elif ergebnis == 'verloren':
|
|
ausschreibung.status = 11
|
|
ausschreibung.save(update_fields=['status', 'geaendert_am'])
|
|
|
|
messages.success(request, 'Nachbetrachtung gespeichert.')
|
|
return redirect('ausschreibungen:nachbetrachtung:detail',
|
|
ausschreibung_id=ausschreibung_id)
|
|
else:
|
|
form = NachbetrachtungForm(instance=nb)
|
|
|
|
kickoff_aufgabe = None
|
|
if nb.ergebnis == 'gewonnen':
|
|
kickoff_aufgabe = Aufgabe.objects.filter(
|
|
ausschreibung=ausschreibung, titel='Kickoff vorbereiten'
|
|
).first()
|
|
|
|
ctx = {
|
|
'ausschreibung': ausschreibung,
|
|
'nachbetrachtung': nb,
|
|
'form': form,
|
|
'kickoff_aufgabe': kickoff_aufgabe,
|
|
'verlustgruende_json': json.dumps(nb.verlustgruende),
|
|
}
|
|
return render(request, 'nachbetrachtung/detail.html', ctx)
|