Aufgaben und Bieterfragen

This commit is contained in:
tegwick 2026-05-08 17:43:23 +02:00
parent f202b71c75
commit 70ece97587
17 changed files with 1038 additions and 14 deletions

View file

@ -0,0 +1,126 @@
{% extends "base.html" %}
{% load vergabe_tags %}
{% block title %}Bieterfrage{% endblock %}
{% block content %}
<div class="flex items-center justify-between mb-4">
<h1 class="page-title">Bieterfrage</h1>
<a href="{% url 'ausschreibungen:bieterfragen:liste' ausschreibung.pk %}" class="btn-ghost text-xs">← Übersicht</a>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2 space-y-4">
<div class="card space-y-3">
<p class="text-sm font-medium text-slate-800 whitespace-pre-wrap">{{ bieterfrage.fragentext }}</p>
{% if bieterfrage.begruendung %}
<div class="border-t border-slate-100 pt-3">
<p class="text-xs text-slate-500 mb-1">Begründung</p>
<p class="text-sm text-slate-700 whitespace-pre-wrap">{{ bieterfrage.begruendung }}</p>
</div>
{% endif %}
</div>
{% if bieterfrage.antwort or bieterfrage.status == 'eingereicht' or bieterfrage.status == 'beantwortet' or bieterfrage.status == 'eingearbeitet' or show_antwort_form %}
<div class="card space-y-3">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide">Antwort</p>
{% if bieterfrage.antwort %}
<p class="text-sm text-slate-800 whitespace-pre-wrap">{{ bieterfrage.antwort }}</p>
{% if bieterfrage.auswirkung_angebot %}
<div class="border-t border-slate-100 pt-3">
<p class="text-xs text-slate-500 mb-1">Auswirkung auf Angebot</p>
<p class="text-sm text-slate-700 whitespace-pre-wrap">{{ bieterfrage.auswirkung_angebot }}</p>
</div>
{% endif %}
{% endif %}
{% if bieterfrage.status == 'eingereicht' or bieterfrage.status == 'beantwortet' or show_antwort_form %}
<form method="post" action="{% url 'ausschreibungen:bieterfragen:antwort' ausschreibung.pk bieterfrage.pk %}"
class="space-y-3 border-t border-slate-100 pt-3">
{% csrf_token %}
<div>
<label class="form-label">Antwort eintragen</label>
<textarea name="antwort" rows="4" class="form-input w-full">{{ bieterfrage.antwort }}</textarea>
</div>
<div>
<label class="form-label">Auswirkung auf Angebot</label>
<textarea name="auswirkung_angebot" rows="2" class="form-input w-full">{{ bieterfrage.auswirkung_angebot }}</textarea>
</div>
<button type="submit" class="btn-primary text-xs">Antwort speichern</button>
</form>
{% endif %}
</div>
{% endif %}
</div>
<div class="space-y-4">
<div class="card">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Status-Verlauf</p>
<ol class="relative border-l border-slate-200 ml-2 space-y-4 mb-4">
{% for val, label in bieterfrage.STATUS_CHOICES %}
<li class="ml-4">
<span class="absolute -left-1.5 mt-1 w-3 h-3 rounded-full border-2
{% if val == bieterfrage.status %}border-blue-600 bg-blue-600{% else %}border-slate-300 bg-white{% endif %}">
</span>
<p class="text-sm {% if val == bieterfrage.status %}font-semibold text-blue-700{% else %}text-slate-400{% endif %}">
{{ label }}
{% if val == 'eingereicht' and bieterfrage.einreichungsdatum %}
<span class="text-xs font-normal text-slate-500 ml-1">({{ bieterfrage.einreichungsdatum }})</span>
{% endif %}
</p>
</li>
{% endfor %}
</ol>
{% if bieterfrage.status == 'entwurf' %}
<form method="post" action="{% url 'ausschreibungen:bieterfragen:status' ausschreibung.pk bieterfrage.pk %}">
{% csrf_token %}
<input type="hidden" name="status" value="abgestimmt">
<button type="submit" class="btn-primary text-xs w-full">Abstimmen →</button>
</form>
{% elif bieterfrage.status == 'abgestimmt' %}
<form method="post" action="{% url 'ausschreibungen:bieterfragen:status' ausschreibung.pk bieterfrage.pk %}">
{% csrf_token %}
<input type="hidden" name="status" value="eingereicht">
<button type="submit" class="btn-primary text-xs w-full">Einreichen →</button>
</form>
{% elif bieterfrage.status == 'eingereicht' %}
<form method="post" action="{% url 'ausschreibungen:bieterfragen:status' ausschreibung.pk bieterfrage.pk %}">
{% csrf_token %}
<input type="hidden" name="status" value="beantwortet">
<button type="submit" class="btn-secondary text-xs w-full">Als beantwortet markieren</button>
</form>
{% elif bieterfrage.status == 'beantwortet' %}
<form method="post" action="{% url 'ausschreibungen:bieterfragen:status' ausschreibung.pk bieterfrage.pk %}">
{% csrf_token %}
<input type="hidden" name="status" value="eingearbeitet">
<button type="submit" class="btn-primary text-xs w-full">Einarbeiten ✓</button>
</form>
{% endif %}
</div>
<div class="card space-y-2">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide">Details</p>
<p class="text-xs text-slate-600">Priorität: <span class="font-medium">{{ bieterfrage.get_prioritaet_display }}</span></p>
{% if bieterfrage.verfasser %}
<p class="text-xs text-slate-600">Verfasser: {{ bieterfrage.verfasser }}</p>
{% endif %}
{% if bieterfrage.anforderung %}
<div class="border-t border-slate-100 pt-2 mt-2">
<p class="text-xs text-slate-500 mb-1">Anforderung</p>
<a href="{% url 'ausschreibungen:lose:anforderung_detail' ausschreibung.pk bieterfrage.anforderung.pk %}"
class="text-xs text-blue-600 hover:underline">{{ bieterfrage.anforderung.titel|truncatechars:50 }}</a>
<div class="mt-2">
<a href="{% url 'ausschreibungen:lose:anforderung_detail' ausschreibung.pk bieterfrage.anforderung.pk %}"
class="text-xs btn-secondary">Anforderungsstatus aktualisieren →</a>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block title %}{{ titel }}{% endblock %}
{% block content %}
<div class="max-w-2xl mx-auto">
<h1 class="page-title mb-6">{{ titel }}</h1>
<form method="post" class="space-y-4">
{% csrf_token %}
<div class="card space-y-4">
<div>
<label class="form-label">Fragentext</label>
{{ form.fragentext }}
{% if form.fragentext.errors %}<p class="text-xs text-red-600 mt-1">{{ form.fragentext.errors.0 }}</p>{% endif %}
</div>
<div>
<label class="form-label">Begründung / Hintergrund</label>
{{ form.begruendung }}
</div>
<div>
<label class="form-label">Priorität</label>
<div class="flex gap-4 mt-1">
{% for radio in form.prioritaet %}
<label class="flex items-center gap-1 text-sm text-slate-700 cursor-pointer">
{{ radio.tag }} {{ radio.choice_label }}
</label>
{% endfor %}
</div>
</div>
</div>
<div class="card space-y-4">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide">Verknüpfungen (optional)</p>
<div>
<label class="form-label">Anforderung</label>
{{ form.anforderung }}
</div>
<div>
<label class="form-label">Dokument</label>
{{ form.dokument }}
</div>
<div>
<label class="form-label">Verfasser</label>
{{ form.verfasser }}
</div>
</div>
<div class="flex gap-3">
<button type="submit" class="btn-primary">Speichern</button>
<a href="{% url 'ausschreibungen:bieterfragen:liste' ausschreibung.pk %}" class="btn-ghost">Abbrechen</a>
</div>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,80 @@
{% extends "base.html" %}
{% load vergabe_tags %}
{% block title %}Bieterfragen — {{ ausschreibung.titel }}{% endblock %}
{% block content %}
<div class="flex items-center justify-between mb-4">
<h1 class="page-title">Bieterfragen</h1>
<a href="{% url 'ausschreibungen:bieterfragen:neu' ausschreibung.pk %}" class="btn-primary text-xs">+ Bieterfrage anlegen</a>
</div>
{% if ausschreibung.bieterfragen_frist %}
{% with tage=ausschreibung.bieterfragen_frist|timeuntil %}
<div class="{% if tage == '0 minutes' %}bg-red-50 border-red-300 text-red-700{% else %}bg-yellow-50 border-yellow-300 text-yellow-700{% endif %} border rounded-lg p-3 mb-4 text-sm">
Bieterfragen bis: <strong>{{ ausschreibung.bieterfragen_frist|date:"d.m.Y" }}</strong>
{% if tage != '0 minutes' %}— noch {{ tage }}{% else %}— Frist abgelaufen{% endif %}
</div>
{% endwith %}
{% endif %}
<form class="card mb-4 flex flex-wrap gap-3 items-end">
<div>
<label class="form-label">Status</label>
<select name="status" class="form-input text-xs"
hx-get="" hx-target="#bieterfragen-list" hx-push-url="true" hx-trigger="change">
<option value="">Alle</option>
{% for val, label in status_choices %}
<option value="{{ val }}"{% if current_status == val %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="form-label">Priorität</label>
<select name="prioritaet" class="form-input text-xs"
hx-get="" hx-target="#bieterfragen-list" hx-push-url="true" hx-trigger="change">
<option value="">Alle</option>
{% for val, label in prioritaet_choices %}
<option value="{{ val }}">{{ label }}</option>
{% endfor %}
</select>
</div>
</form>
<div id="bieterfragen-list" class="card">
{% if bieterfragen %}
<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-4">Frage</th>
<th class="pb-2 pr-4">Status</th>
<th class="pb-2 pr-4">Priorität</th>
<th class="pb-2 pr-4">Eingereicht</th>
<th class="pb-2">Verfasser</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for bf in bieterfragen %}
<tr class="hover:bg-slate-50">
<td class="py-2 pr-4">
<a href="{% url 'ausschreibungen:bieterfragen:detail' ausschreibung.pk bf.pk %}"
class="font-medium text-slate-800 hover:text-blue-600">
{{ bf.fragentext|truncatechars:80 }}
</a>
{% if bf.anforderung %}
<p class="text-xs text-slate-400 mt-0.5">Anforderung: {{ bf.anforderung.titel|truncatechars:40 }}</p>
{% endif %}
</td>
<td class="py-2 pr-4">{% status_badge bf.status bf.get_status_display %}</td>
<td class="py-2 pr-4 text-xs">{{ bf.get_prioritaet_display }}</td>
<td class="py-2 pr-4 text-xs text-slate-600">{{ bf.einreichungsdatum|default:"—" }}</td>
<td class="py-2 text-xs text-slate-600">{{ bf.verfasser|default:"—" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-sm text-slate-500 text-center py-8">Noch keine Bieterfragen angelegt.</p>
{% endif %}
</div>
{% endblock %}

View file

@ -0,0 +1,67 @@
{% extends "base.html" %}
{% load vergabe_tags %}
{% block title %}{{ aufgabe.titel }}{% endblock %}
{% block content %}
<div class="flex items-center justify-between mb-4">
<h1 class="page-title">{{ aufgabe.titel }}</h1>
<div class="flex gap-2">
<a href="{% url 'ausschreibungen:aufgaben:bearbeiten' ausschreibung.pk aufgabe.pk %}" class="btn-secondary text-xs">Bearbeiten</a>
<a href="{% url 'ausschreibungen:aufgaben:loeschen' ausschreibung.pk aufgabe.pk %}" class="btn-ghost text-xs text-red-600">Verwerfen</a>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="col-span-2 space-y-4">
<div class="card space-y-3">
{% render_field aufgabe "typ" "Typ" %}
{% render_field aufgabe "prioritaet" "Priorität" %}
{% render_field aufgabe "status" "Status" %}
{% render_field aufgabe "frist" "Frist" %}
{% render_field aufgabe "verantwortlicher" "Verantwortlich" %}
{% if aufgabe.beschreibung %}
<div>
<p class="text-xs text-slate-500 mb-1">Beschreibung</p>
<p class="text-sm text-slate-800 whitespace-pre-wrap">{{ aufgabe.beschreibung }}</p>
</div>
{% endif %}
</div>
{% if aufgabe.status == 'erledigt' or aufgabe.ergebnis %}
<div class="card">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-3">Ergebnis</p>
<form method="post" action="{% url 'ausschreibungen:aufgaben:ergebnis' ausschreibung.pk aufgabe.pk %}">
{% csrf_token %}
<textarea name="ergebnis" rows="3" class="form-input w-full">{{ aufgabe.ergebnis }}</textarea>
<button type="submit" class="btn-primary text-xs mt-2">Speichern</button>
</form>
</div>
{% endif %}
</div>
<div class="space-y-4">
{% if aufgabe.anforderung %}
<div class="card">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-2">Anforderung</p>
<a href="{% url 'ausschreibungen:lose:anforderung_detail' ausschreibung.pk aufgabe.anforderung.pk %}"
class="text-sm text-blue-600 hover:underline">{{ aufgabe.anforderung.titel }}</a>
</div>
{% endif %}
{% if aufgabe.bieterfrage %}
<div class="card">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-2">Bieterfrage</p>
<a href="{% url 'ausschreibungen:bieterfragen:detail' ausschreibung.pk aufgabe.bieterfrage.pk %}"
class="text-sm text-blue-600 hover:underline">{{ aufgabe.bieterfrage }}</a>
</div>
{% endif %}
{% if aufgabe.los %}
<div class="card">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide mb-2">Los</p>
<a href="{% url 'ausschreibungen:lose:detail' ausschreibung.pk aufgabe.los.pk %}"
class="text-sm text-blue-600 hover:underline">{{ aufgabe.los.lostitel }}</a>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,64 @@
{% extends "base.html" %}
{% block title %}{{ titel }}{% endblock %}
{% block content %}
<div class="max-w-2xl mx-auto">
<h1 class="page-title mb-6">{{ titel }}</h1>
<form method="post" class="space-y-4">
{% csrf_token %}
<div class="card space-y-4">
<div>
<label class="form-label">Titel</label>
{{ form.titel }}
{% if form.titel.errors %}<p class="text-xs text-red-600 mt-1">{{ form.titel.errors.0 }}</p>{% endif %}
</div>
<div>
<label class="form-label">Beschreibung</label>
{{ form.beschreibung }}
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="form-label">Typ</label>
{{ form.typ }}
</div>
<div>
<label class="form-label">Frist</label>
{{ form.frist }}
</div>
</div>
<div>
<label class="form-label">Priorität</label>
<div class="flex gap-4 mt-1">
{% for radio in form.prioritaet %}
<label class="flex items-center gap-1 text-sm text-slate-700 cursor-pointer">
{{ radio.tag }} {{ radio.choice_label }}
</label>
{% endfor %}
</div>
</div>
<div>
<label class="form-label">Verantwortlich</label>
{{ form.verantwortlicher }}
</div>
</div>
<div class="card space-y-4">
<p class="text-xs font-medium text-slate-500 uppercase tracking-wide">Verknüpfungen (optional)</p>
<div>
<label class="form-label">Los</label>
{{ form.los }}
</div>
<div>
<label class="form-label">Anforderung</label>
{{ form.anforderung }}
</div>
<div>
<label class="form-label">Bieterfrage</label>
{{ form.bieterfrage }}
</div>
</div>
<div class="flex gap-3">
<button type="submit" class="btn-primary">Speichern</button>
<a href="{% url 'ausschreibungen:aufgaben:liste' ausschreibung.pk %}" class="btn-ghost">Abbrechen</a>
</div>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% load vergabe_tags %}
{% block title %}Aufgaben{% if ausschreibung %} — {{ ausschreibung.titel }}{% endif %}{% endblock %}
{% block content %}
<div class="flex items-center justify-between mb-4">
<h1 class="page-title">Aufgaben</h1>
{% if ausschreibung %}
<a href="{% url 'ausschreibungen:aufgaben:neu' ausschreibung.pk %}" class="btn-primary text-xs">+ Aufgabe anlegen</a>
{% endif %}
</div>
<form class="card mb-4 flex flex-wrap gap-3 items-end"
hx-get="" hx-target="#aufgaben-container" hx-push-url="true"
hx-trigger="change from:select, submit">
{% csrf_token %}
<div>
<label class="form-label">Status</label>
<select name="status" class="form-input text-xs">
<option value="">Alle</option>
{% for val, label in status_choices %}
<option value="{{ val }}"{% if current_status == val %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="form-label">Typ</label>
<select name="typ" class="form-input text-xs">
<option value="">Alle</option>
{% for val, label in typ_choices %}
<option value="{{ val }}"{% if current_typ == val %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div>
<label class="form-label">Verantwortlich</label>
<select name="verantwortlicher" class="form-input text-xs">
<option value="">Alle</option>
{% for m in mitarbeiter %}
<option value="{{ m.pk }}"{% if current_verantwortlicher == m.pk|stringformat:"s" %} selected{% endif %}>{{ m.get_full_name|default:m.username }}</option>
{% endfor %}
</select>
</div>
{% if not ausschreibung %}
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="checkbox" name="nur_meine" value="1"
{% if request.GET.nur_meine == "1" %}checked{% endif %}>
Nur meine
</label>
{% endif %}
</form>
<div id="aufgaben-container" class="card">
{% include "aufgaben/liste_partial.html" %}
</div>
{% endblock %}

View file

@ -0,0 +1,22 @@
{% load vergabe_tags %}
{% if aufgaben %}
<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-4">Titel</th>
<th class="pb-2 pr-4">Typ</th>
<th class="pb-2 pr-4">Frist</th>
<th class="pb-2 pr-4">Verantwortlich</th>
<th class="pb-2 pr-4">Status</th>
<th class="pb-2">Ergebnis</th>
</tr>
</thead>
<tbody id="aufgaben-table" class="divide-y divide-slate-100">
{% for aufgabe in aufgaben %}
{% include "aufgaben/partials/aufgabe_row.html" %}
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-sm text-slate-500 text-center py-8">Keine Aufgaben gefunden.</p>
{% endif %}

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}Aufgabe verwerfen{% endblock %}
{% block content %}
<div class="max-w-md mx-auto mt-16">
<div class="card space-y-4">
<h1 class="text-lg font-semibold text-slate-800">Aufgabe verwerfen?</h1>
<p class="text-sm text-slate-600">
Die Aufgabe wird nicht gelöscht, sondern auf Status <strong>Verworfen</strong> gesetzt.
</p>
<p class="text-sm font-medium text-slate-800 bg-slate-50 rounded p-3">{{ aufgabe.titel }}</p>
<form method="post" class="flex gap-3">
{% csrf_token %}
<button type="submit" class="btn-primary bg-red-600 hover:bg-red-700">Verwerfen</button>
<a href="{% url 'ausschreibungen:aufgaben:detail' ausschreibung.pk aufgabe.pk %}" class="btn-ghost">Abbrechen</a>
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,45 @@
{% load vergabe_tags %}
<tr class="hover:bg-slate-50{% if aufgabe.status == 'ueberfaellig' %} bg-red-50{% endif %}">
<td class="py-2 pr-4">
<a href="{% url 'ausschreibungen:aufgaben:detail' ausschreibung.pk aufgabe.pk %}"
class="font-medium text-slate-800 hover:text-blue-600 line-clamp-1">
{{ aufgabe.titel|truncatechars:60 }}
</a>
</td>
<td class="py-2 pr-4 text-xs">{% status_badge aufgabe.typ aufgabe.get_typ_display %}</td>
<td class="py-2 pr-4 text-xs">
{% if aufgabe.frist %}
<span class="{% if aufgabe.status == 'ueberfaellig' %}text-red-600 font-medium{% else %}text-slate-600{% endif %}">
{{ aufgabe.frist }}
</span>
{% else %}
<span class="text-slate-400"></span>
{% endif %}
</td>
<td class="py-2 pr-4 text-xs text-slate-600">{{ aufgabe.verantwortlicher|default:"—" }}</td>
<td class="py-2 pr-4">
<select name="status"
hx-post="{% url 'ausschreibungen:aufgaben:status' ausschreibung.pk aufgabe.pk %}"
hx-target="closest tr"
hx-swap="outerHTML"
class="form-input text-xs py-1">
{% for val, label in aufgabe.STATUS_CHOICES %}
<option value="{{ val }}"{% if val == aufgabe.status %} selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</td>
{% if aufgabe.status == 'erledigt' %}
<td class="py-2">
<form hx-post="{% url 'ausschreibungen:aufgaben:ergebnis' ausschreibung.pk aufgabe.pk %}"
hx-target="closest tr" hx-swap="outerHTML"
class="flex gap-1 items-center">
{% csrf_token %}
<input type="text" name="ergebnis" value="{{ aufgabe.ergebnis }}"
placeholder="Ergebnis…" class="form-input text-xs py-1 w-40">
<button type="submit" class="btn-primary text-xs py-1 px-2">OK</button>
</form>
</td>
{% else %}
<td class="py-2 text-xs text-slate-500">{{ aufgabe.ergebnis|truncatechars:30 }}</td>
{% endif %}
</tr>