feat(nachbetrachtung): Abgabe-Checkliste, Dokumentation und Nachbetrachtung (WP-0009)
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>
This commit is contained in:
parent
bca4eb15d8
commit
a1cc317b3b
10 changed files with 583 additions and 12 deletions
81
vergabe_teilnahme/templates/nachbetrachtung/abgabe.html
Normal file
81
vergabe_teilnahme/templates/nachbetrachtung/abgabe.html
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Abgabe-Checkliste — {{ ausschreibung.titel }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="page-title">Abgabe-Checkliste</h1>
|
||||
<div class="flex gap-2">
|
||||
<a href="{% url 'ausschreibungen:nachbetrachtung:abgabe:dokumentieren' ausschreibung.pk %}" class="btn-primary text-xs">Abgabe dokumentieren</a>
|
||||
<a href="{% url 'ausschreibungen:detail' ausschreibung.pk %}" class="btn-ghost text-xs">← Ausschreibung</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if ausschreibung.abgabe_bis %}
|
||||
<div class="card mb-4 bg-amber-50 border border-amber-200">
|
||||
<p class="text-sm font-medium text-amber-800">Abgabe bis: <strong>{{ ausschreibung.abgabe_bis }}</strong></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<p class="text-sm font-medium text-slate-700">Fortschritt</p>
|
||||
<p class="text-sm font-bold text-slate-700">{{ erfuellt }} / {{ gesamt }}</p>
|
||||
</div>
|
||||
<div class="w-full bg-slate-200 rounded-full h-2">
|
||||
<div class="bg-blue-600 h-2 rounded-full" style="width: {% widthratio erfuellt gesamt 100 %}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Freigaben & Entscheidungen</p>
|
||||
<ul class="space-y-2">
|
||||
{% for label, ok in punkte %}
|
||||
<li class="flex items-center gap-3">
|
||||
{% if ok %}
|
||||
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-green-100 text-green-700 text-xs font-bold">✓</span>
|
||||
<span class="text-sm text-slate-700">{{ label }}</span>
|
||||
{% else %}
|
||||
<span class="inline-flex items-center justify-center w-5 h-5 rounded-full bg-red-100 text-red-600 text-xs font-bold">✗</span>
|
||||
<span class="text-sm text-slate-400">{{ label }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide">Dokumente</p>
|
||||
<p class="text-xs text-slate-500">{{ vollstaendigkeit.dokumente_freigegeben }} / {{ vollstaendigkeit.dokumente_gesamt }} freigegeben</p>
|
||||
</div>
|
||||
{% if dokumente %}
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-slate-200 text-left text-xs text-slate-500">
|
||||
<th class="pb-2 pr-3">Bezeichnung</th>
|
||||
<th class="pb-2 pr-3">Kategorie</th>
|
||||
<th class="pb-2">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for dok in dokumente %}
|
||||
<tr>
|
||||
<td class="py-2 pr-3 text-xs">{{ dok.bezeichnung|default:dok.datei.name }}</td>
|
||||
<td class="py-2 pr-3 text-xs text-slate-500">{{ dok.get_kategorie_display }}</td>
|
||||
<td class="py-2 text-xs">
|
||||
{% if dok.status == 'final_abgegeben' or dok.status == 'freigegeben' %}
|
||||
<span class="text-green-700 font-medium">{{ dok.get_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="text-slate-400">{{ dok.get_status_display }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-sm text-slate-400">Noch keine Dokumente hochgeladen.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Abgabe dokumentieren — {{ ausschreibung.titel }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="page-title">Abgabe dokumentieren</h1>
|
||||
<a href="{% url 'ausschreibungen:nachbetrachtung:abgabe:checkliste' ausschreibung.pk %}" class="btn-ghost text-xs">← Checkliste</a>
|
||||
</div>
|
||||
|
||||
<div class="max-w-xl">
|
||||
<form method="post" enctype="multipart/form-data" class="card space-y-4">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<label class="form-label">{{ form.abgabe_zeitpunkt.label }}</label>
|
||||
{{ form.abgabe_zeitpunkt }}
|
||||
{% if form.abgabe_zeitpunkt.errors %}<p class="text-xs text-red-600 mt-1">{{ form.abgabe_zeitpunkt.errors.0 }}</p>{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">{{ form.abgabe_plattform.label }}</label>
|
||||
{{ form.abgabe_plattform }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">{{ form.verantwortlicher.label }}</label>
|
||||
{{ form.verantwortlicher }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">{{ form.abgabenachweis.label }}</label>
|
||||
{{ form.abgabenachweis }}
|
||||
<p class="text-xs text-slate-400 mt-1">Eingangsbestätigung, Screenshot o.ä.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">{{ form.kommentar.label }}</label>
|
||||
{{ form.kommentar }}
|
||||
</div>
|
||||
<div class="flex gap-3 pt-2">
|
||||
<button type="submit" class="btn-primary">Abgabe bestätigen</button>
|
||||
<a href="{% url 'ausschreibungen:nachbetrachtung:abgabe:checkliste' ausschreibung.pk %}" class="btn-ghost">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Problem bei Abgabe — {{ ausschreibung.titel }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="page-title">Problem bei Abgabe vermerken</h1>
|
||||
<a href="{% url 'ausschreibungen:nachbetrachtung:abgabe:checkliste' ausschreibung.pk %}" class="btn-ghost text-xs">← Checkliste</a>
|
||||
</div>
|
||||
|
||||
<div class="max-w-md">
|
||||
<form method="post" class="card space-y-4">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<label class="form-label">Beschreibung des Problems</label>
|
||||
<textarea name="kommentar" rows="4" class="form-input w-full" placeholder="Was ist passiert?"></textarea>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="btn-primary bg-amber-600 hover:bg-amber-700">Problem vermerken</button>
|
||||
<a href="{% url 'ausschreibungen:nachbetrachtung:abgabe:checkliste' ausschreibung.pk %}" class="btn-ghost">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
119
vergabe_teilnahme/templates/nachbetrachtung/detail.html
Normal file
119
vergabe_teilnahme/templates/nachbetrachtung/detail.html
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %}Nachbetrachtung — {{ ausschreibung.titel }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h1 class="page-title">Nachbetrachtung</h1>
|
||||
<a href="{% url 'ausschreibungen:detail' ausschreibung.pk %}" class="btn-ghost text-xs">← Ausschreibung</a>
|
||||
</div>
|
||||
|
||||
<form method="post" class="space-y-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="card">
|
||||
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Ergebnis</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
{% for value, label in form.ergebnis.field.choices %}
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="ergebnis" value="{{ value }}"
|
||||
{% if form.ergebnis.value == value %}checked{% endif %}
|
||||
class="text-blue-600">
|
||||
<span class="text-sm">{{ label }}</span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="card">
|
||||
<label class="form-label">{{ form.zuschlagsdatum.label }}</label>
|
||||
{{ form.zuschlagsdatum }}
|
||||
</div>
|
||||
<div class="card">
|
||||
<label class="form-label">{{ form.projektverantwortlicher.label }}</label>
|
||||
{{ form.projektverantwortlicher }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if kickoff_aufgabe %}
|
||||
<div class="card bg-green-50 border border-green-200">
|
||||
<p class="text-xs font-medium text-green-700 uppercase tracking-wide mb-1">Übergabe</p>
|
||||
<p class="text-sm text-green-800">
|
||||
Kickoff-Aufgabe:
|
||||
<a href="{% url 'ausschreibungen:aufgaben:detail' ausschreibung.pk kickoff_aufgabe.pk %}"
|
||||
class="underline">{{ kickoff_aufgabe.titel }}</a>
|
||||
— {{ kickoff_aufgabe.get_status_display }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card">
|
||||
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Abgegebene Unterlagen</p>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="form-label">{{ form.abgegebene_unterlagen.label }}</label>
|
||||
{{ form.abgegebene_unterlagen }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">{{ form.abgegebene_preise.label }}</label>
|
||||
{{ form.abgegebene_preise }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card" x-data="{ gruende: {{ verlustgruende_json }} }">
|
||||
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Verlustanalyse</p>
|
||||
|
||||
<div>
|
||||
<template x-for="(g, i) in gruende" :key="i">
|
||||
<div class="flex gap-2 mb-2 items-center">
|
||||
<input x-model="g.grund" class="form-input flex-1" placeholder="Verlustgrund">
|
||||
<select x-model="g.kategorie" class="form-input w-36">
|
||||
<option value="preis">Preis</option>
|
||||
<option value="referenz">Referenz</option>
|
||||
<option value="anforderung">Anforderung</option>
|
||||
<option value="subunternehmer">Subunternehmer</option>
|
||||
<option value="sonstiges">Sonstiges</option>
|
||||
</select>
|
||||
<input type="number" x-model.number="g.verlaesslichkeit" min="1" max="5"
|
||||
class="form-input w-20" placeholder="1-5" title="Verlässlichkeit 1-5">
|
||||
<button type="button" @click="gruende.splice(i, 1)" class="btn-ghost text-red-500 text-xs">✕</button>
|
||||
</div>
|
||||
</template>
|
||||
<button type="button"
|
||||
@click="gruende.push({grund:'', kategorie:'sonstiges', verlaesslichkeit:3})"
|
||||
class="btn-secondary text-xs mt-1">+ Verlustgrund</button>
|
||||
</div>
|
||||
<input type="hidden" name="verlustgruende" :value="JSON.stringify(gruende)">
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="form-label">{{ form.ausschlaggebende_zuschlagsmerkmale.label }}</label>
|
||||
{{ form.ausschlaggebende_zuschlagsmerkmale }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Lessons Learned</p>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="form-label">{{ form.lessons_learned.label }}</label>
|
||||
{{ form.lessons_learned }}
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">{{ form.empfehlungen.label }}</label>
|
||||
{{ form.empfehlungen }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
{{ form.wiederverwendbare_erkenntnisse_markiert }}
|
||||
<label class="text-sm text-slate-700">Wiederverwendbare Erkenntnisse markieren</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button type="submit" class="btn-primary">Speichern</button>
|
||||
<a href="{% url 'ausschreibungen:detail' ausschreibung.pk %}" class="btn-ghost">Abbrechen</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue