feat(WP-0012): Querschnitt — Freigaben, Felder, Feedback, Suche, Tests

Implements all 8 tasks of the final cross-cutting workplan:

- T01: Generisches Freigabe-Modal (freigabe_modal, freigabe_erteilen views + templates)
- T02: Freigaben-Übersicht pro Ausschreibung (freigaben_uebersicht view + template)
- T03: EntityFieldConfig Admin-Interface (/felder/<entity_type>/ with HTMX toggle)
- T04: CustomAttribute-Panel (full CRUD with sort, lazy HTMX load)
- T05: Feedback-Backlog mit Statusverwaltung + feedback_success.html template
- T06: End-to-End-Tests in vergabe_teilnahme/tests/test_e2e.py (8 tests)
- T07: Globale Suche erweitert (Dokumente, Nachweise, Referenzen, Marktbegleiter)
- T08: Alle Migrationen sauber, 68/68 Tests grün, Ruff-Fehler in neuem Code behoben

Bugfix: URL-Namespace-Fehler in Abgabe-Templates (ausschreibungen:nachbetrachtung:abgabe → ausschreibungen:abgabe)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-11 17:54:38 +02:00
parent c5ccbd665d
commit 5a231223c0
23 changed files with 828 additions and 37 deletions

View File

@@ -0,0 +1,8 @@
<div x-data="{ show: true }" x-show="show" x-cloak x-init="setTimeout(() => show = false, 3000)"
class="fixed inset-0 bg-black/30 z-50 flex items-center justify-center">
<div class="bg-white rounded-xl shadow-xl p-8 text-center max-w-sm mx-4">
<p class="text-3xl mb-2"></p>
<p class="font-medium text-slate-900">Danke für dein Feedback!</p>
<button @click="show = false" class="btn-secondary mt-4">Schließen</button>
</div>
</div>

View File

@@ -0,0 +1,31 @@
<div x-data="{ open: true }" x-show="open" x-cloak
class="fixed inset-0 bg-black/30 z-50 flex items-center justify-center">
<div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-md mx-4"
@click.outside="open = false">
<h2 class="page-title text-xl mb-4">Freigabe erteilen</h2>
<form hx-post="/freigaben/erteilen/" hx-target="#modal-container" hx-swap="innerHTML">
{% csrf_token %}
<input type="hidden" name="content_type_id" value="{{ content_type_id }}">
<input type="hidden" name="object_id" value="{{ object_id }}">
<div class="space-y-3">
<div>
<label class="form-label">Freigabe-Typ</label>
<select name="freigabe_typ" class="form-input">
{% for value, label in freigabe_typ_choices %}
<option value="{{ value }}" {% if value == freigabe_typ %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="form-label">Kommentar (optional)</label>
<textarea name="kommentar" rows="3" class="form-input"
placeholder="Begründung oder Anmerkungen..."></textarea>
</div>
</div>
<div class="flex justify-end gap-2 mt-4">
<button type="button" @click="open = false" class="btn-secondary">Abbrechen</button>
<button type="submit" class="btn-primary">Freigabe erteilen</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,8 @@
<div x-data="{ show: true }" x-show="show" x-cloak x-init="setTimeout(() => show = false, 3000)"
class="fixed inset-0 bg-black/30 z-50 flex items-center justify-center">
<div class="bg-white rounded-xl shadow-xl p-8 text-center max-w-sm mx-4">
<p class="text-3xl mb-2"></p>
<p class="font-medium text-slate-900">Freigabe erteilt!</p>
<button @click="show = false" class="btn-secondary mt-4">Schließen</button>
</div>
</div>

View File

@@ -0,0 +1,64 @@
{% if empty %}
<div class="px-4 py-3 text-sm text-slate-500">
Keine Ergebnisse für „{{ q }}"
</div>
{% else %}
<div class="py-1">
{% if ausschreibungen %}
<div class="px-3 py-1 text-xs font-medium text-slate-400 uppercase tracking-wide">Ausschreibungen</div>
{% for a in ausschreibungen %}
<a href="/ausschreibungen/{{ a.pk }}/"
class="flex items-center gap-2 px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
<span class="text-base">📋</span>
<span>{{ a.titel }}</span>
</a>
{% endfor %}
{% endif %}
{% if dokumente %}
<div class="px-3 py-1 text-xs font-medium text-slate-400 uppercase tracking-wide mt-1">Dokumente</div>
{% for d in dokumente %}
<a href="/ausschreibungen/{{ d.ausschreibung.pk }}/dokumente/"
class="flex items-center gap-2 px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
<span class="text-base">📄</span>
<span>{{ d.dateiname }}</span>
<span class="text-xs text-slate-400">— {{ d.ausschreibung.titel|truncatechars:30 }}</span>
</a>
{% endfor %}
{% endif %}
{% if nachweise %}
<div class="px-3 py-1 text-xs font-medium text-slate-400 uppercase tracking-wide mt-1">Nachweise</div>
{% for n in nachweise %}
<a href="/bibliothek/nachweise/{{ n.pk }}/"
class="flex items-center gap-2 px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
<span class="text-base">🏆</span>
<span>{{ n.titel }}</span>
</a>
{% endfor %}
{% endif %}
{% if referenzen %}
<div class="px-3 py-1 text-xs font-medium text-slate-400 uppercase tracking-wide mt-1">Referenzen</div>
{% for r in referenzen %}
<a href="/bibliothek/referenzen/{{ r.pk }}/"
class="flex items-center gap-2 px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
<span class="text-base"></span>
<span>{{ r.referenztitel }}</span>
<span class="text-xs text-slate-400">— {{ r.kunde|truncatechars:25 }}</span>
</a>
{% endfor %}
{% endif %}
{% if marktbegleiter %}
<div class="px-3 py-1 text-xs font-medium text-slate-400 uppercase tracking-wide mt-1">Marktbegleiter</div>
{% for m in marktbegleiter %}
<a href="/marktbegleiter/{{ m.pk }}/"
class="flex items-center gap-2 px-4 py-2 text-sm text-slate-700 hover:bg-slate-50">
<span class="text-base">🔍</span>
<span>{{ m.name }}</span>
</a>
{% endfor %}
{% endif %}
</div>
{% endif %}