Add Vorlagen editor, upload portal, onboarding, and participant import command
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Code Quality / quality (push) Has been cancelled

- Dokument-Vorlagen-Editor: create/edit/reset document templates (admin)
- Upload-Portal: public portal for Nachweis uploads via token
- Onboarding: invite Destinatäre via email with multi-step wizard
- Bestätigungsschreiben: preview and send confirmation letters
- Email settings: SMTP configuration UI
- Management command: import_veranstaltung_teilnehmer for bulk participant import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-21 09:25:18 +00:00
parent fdf078fa10
commit aed540fe4b
51 changed files with 5335 additions and 33 deletions

View File

@@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Unterlagen hochladen vHTV-Stiftung</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { font-family: Arial, sans-serif; font-size: 15px; color: #222; background: #f4f6f8; margin: 0; padding: 0; }
.container { max-width: 720px; margin: 40px auto; padding: 0 16px 40px; }
.header { background: #1a3a5c; color: #fff; padding: 24px 28px; border-radius: 8px 8px 0 0; }
.header h1 { margin: 0 0 4px; font-size: 20px; }
.header p { margin: 0; font-size: 13px; opacity: 0.8; }
.card { background: #fff; border-radius: 0 0 8px 8px; padding: 28px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
.badge { display: inline-block; background: #e8f0fb; color: #1a3a5c; font-weight: bold; padding: 4px 12px; border-radius: 20px; font-size: 13px; margin-bottom: 12px; }
.info-box { background: #f0f6ff; border: 1px solid #b0cce8; border-radius: 6px; padding: 14px 16px; margin-bottom: 20px; font-size: 14px; }
.error-box { background: #fff3f3; border: 1px solid #e88; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; color: #c00; }
/* Category sections */
.kategorie { border: 1px solid #dde4ed; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
.kategorie h3 { margin: 0 0 4px; font-size: 16px; color: #1a3a5c; }
.kategorie .hinweis { font-size: 13px; color: #666; margin: 0 0 12px; }
.kategorie.pflicht { border-left: 3px solid #1a3a5c; }
label { display: block; font-weight: bold; margin-bottom: 6px; font-size: 14px; }
.upload-area { border: 2px dashed #b0cce8; border-radius: 6px; padding: 20px; text-align: center; cursor: pointer; background: #fafcff; transition: border-color 0.2s; position: relative; }
.upload-area:hover, .upload-area.dragover { border-color: #1a3a5c; background: #e8f0fb; }
.upload-area input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
.upload-area .icon { font-size: 28px; margin-bottom: 4px; }
.upload-area p { margin: 2px 0; color: #555; font-size: 13px; }
.upload-area .hint { font-size: 11px; color: #888; }
.file-list { margin: 8px 0 0; font-size: 13px; color: #444; list-style: none; padding: 0; }
.file-list li { padding: 3px 0; border-bottom: 1px solid #f0f0f0; }
.oder-text { text-align: center; color: #999; font-size: 13px; margin: 10px 0; font-style: italic; }
textarea { width: 100%; border: 1px solid #ccc; border-radius: 5px; padding: 10px; font-family: inherit; font-size: 14px; resize: vertical; min-height: 60px; }
textarea:focus { border-color: #1a3a5c; outline: none; }
.submit-btn { display: block; width: 100%; background: #1a3a5c; color: #fff; border: none; border-radius: 5px; padding: 14px; font-size: 16px; font-weight: bold; cursor: pointer; margin-top: 20px; }
.submit-btn:hover { background: #14304e; }
.deadline { font-size: 13px; color: #888; margin-top: 16px; text-align: center; }
.footer { text-align: center; margin-top: 24px; font-size: 12px; color: #aaa; }
.pflicht-hinweis { font-size: 12px; color: #888; margin-bottom: 16px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>van Hees-Theyssen-Vogel'sche Stiftung</h1>
<p>Sicheres Dokumenten-Upload-Portal</p>
</div>
<div class="card">
<div class="badge">{{ halbjahr_label }}</div>
<p>Guten Tag, <strong>{{ destinataer.vorname }} {{ destinataer.nachname }}</strong>,</p>
<p>bitte laden Sie hier Ihre Unterlagen für das <strong>{{ halbjahr_label }}</strong> hoch.
Für jede Kategorie können Sie eine Datei hochladen und/oder einen Text eingeben.</p>
<p class="pflicht-hinweis">Pro Kategorie muss mindestens eine Datei <em>oder</em> ein Texteintrag eingereicht werden.</p>
{% if fehler %}
<div class="error-box">{{ fehler }}</div>
{% endif %}
<form method="post" enctype="multipart/form-data" action="">
{% csrf_token %}
<!-- 1. Studiennachweis -->
<div class="kategorie pflicht">
<h3>Studiennachweis</h3>
<p class="hinweis">Semesterbescheinigung, Ausbildungsnachweis, Leistungsnachweise (Zeugnisse, Kreditpunkte etc.)</p>
<label for="studiennachweis">Datei hochladen:</label>
<div class="upload-area" data-target="studiennachweis">
<input type="file" name="studiennachweis" id="studiennachweis" accept=".pdf,.jpg,.jpeg,.png,.tiff,.tif">
<div class="icon">&#128196;</div>
<p>Datei hierher ziehen oder klicken</p>
<p class="hint">PDF, JPG, PNG, TIFF &bull; max. {{ max_dateigroesse_mb }} MB</p>
</div>
<ul class="file-list" data-list="studiennachweis"></ul>
<p class="oder-text">— oder Texteintrag —</p>
<textarea name="studiennachweis_text" placeholder="z.B. 'Semesterbescheinigung liegt bei' oder 'Kein Studium/Ausbildung mehr seit ...'">{{ studiennachweis_text|default:"" }}</textarea>
</div>
<!-- 2. Einkommenssituation -->
<div class="kategorie pflicht">
<h3>Einkommenssituation</h3>
<p class="hinweis">Einkommensteuerbescheid, Lohn-/Gehaltsnachweis, Rentenbescheid, Bescheinigung Pflegegrad etc.</p>
<label for="einkommenssituation">Datei hochladen:</label>
<div class="upload-area" data-target="einkommenssituation">
<input type="file" name="einkommenssituation" id="einkommenssituation" accept=".pdf,.jpg,.jpeg,.png,.tiff,.tif">
<div class="icon">&#128196;</div>
<p>Datei hierher ziehen oder klicken</p>
<p class="hint">PDF, JPG, PNG, TIFF &bull; max. {{ max_dateigroesse_mb }} MB</p>
</div>
<ul class="file-list" data-list="einkommenssituation"></ul>
<p class="oder-text">— oder Texteintrag —</p>
<textarea name="einkommenssituation_text" placeholder="z.B. 'Keine Änderungen seit letzter Meldung' oder Details zu Änderungen">{{ einkommenssituation_text|default:"" }}</textarea>
</div>
<!-- 3. Vermögenssituation -->
<div class="kategorie pflicht">
<h3>Vermögenssituation</h3>
<p class="hinweis">Angaben zu Spar-/Festgeldguthaben, Aktien, Immobilien etc.</p>
<label for="vermogenssituation">Datei hochladen:</label>
<div class="upload-area" data-target="vermogenssituation">
<input type="file" name="vermogenssituation" id="vermogenssituation" accept=".pdf,.jpg,.jpeg,.png,.tiff,.tif">
<div class="icon">&#128196;</div>
<p>Datei hierher ziehen oder klicken</p>
<p class="hint">PDF, JPG, PNG, TIFF &bull; max. {{ max_dateigroesse_mb }} MB</p>
</div>
<ul class="file-list" data-list="vermogenssituation"></ul>
<p class="oder-text">— oder Texteintrag —</p>
<textarea name="vermogenssituation_text" placeholder="z.B. 'Keine Änderungen seit letzter Meldung' oder Details zu Änderungen">{{ vermogenssituation_text|default:"" }}</textarea>
</div>
<!-- 4. Weitere Dokumente (optional) -->
<div class="kategorie">
<h3>Weitere Dokumente (optional)</h3>
<p class="hinweis">Mietvertrag, Unterhaltsbelege, sonstige Nachweise</p>
<label for="weitere_dokumente">Datei hochladen:</label>
<div class="upload-area" data-target="weitere_dokumente">
<input type="file" name="weitere_dokumente" id="weitere_dokumente" accept=".pdf,.jpg,.jpeg,.png,.tiff,.tif">
<div class="icon">&#128196;</div>
<p>Datei hierher ziehen oder klicken</p>
<p class="hint">PDF, JPG, PNG, TIFF &bull; max. {{ max_dateigroesse_mb }} MB</p>
</div>
<ul class="file-list" data-list="weitere_dokumente"></ul>
<p class="oder-text">— oder Texteintrag —</p>
<textarea name="weitere_dokumente_text" placeholder="Optionale Anmerkungen oder Beschreibung">{{ weitere_dokumente_text|default:"" }}</textarea>
</div>
<button type="submit" class="submit-btn">Unterlagen jetzt einreichen</button>
</form>
<p class="deadline">&#9201; Gültig bis: {{ gueltig_bis|date:"d.m.Y" }}</p>
</div>
<div class="footer">
van Hees-Theyssen-Vogel'sche Stiftung &bull; Raesfelder Str. 3 &bull; 46499 Hamminkeln<br>
Fragen? Tel. 02858/836780
</div>
</div>
<script>
document.querySelectorAll('.upload-area').forEach(area => {
const input = area.querySelector('input[type="file"]');
const target = area.dataset.target;
const list = document.querySelector(`[data-list="${target}"]`);
function updateList(files) {
list.innerHTML = '';
Array.from(files).forEach(f => {
const li = document.createElement('li');
li.textContent = `\u2714 ${f.name} (${(f.size/1024/1024).toFixed(2)} MB)`;
list.appendChild(li);
});
}
input.addEventListener('change', () => updateList(input.files));
area.addEventListener('dragover', e => { e.preventDefault(); area.classList.add('dragover'); });
area.addEventListener('dragleave', () => area.classList.remove('dragover'));
area.addEventListener('drop', e => {
e.preventDefault();
area.classList.remove('dragover');
input.files = e.dataTransfer.files;
updateList(e.dataTransfer.files);
});
});
</script>
</body>
</html>