feat: Veranstaltungsmodul + Serienbrief mit editierbaren Feldern (STI-35, STI-39)
Implementierung des Veranstaltungsmoduls inkl. Serienbrief-PDF-Generator mit dynamischen, editierbaren Feldern für Betreff und Unterschriften. ### Veranstaltungsmodul (STI-35) - Neues Veranstaltungs-Modell: Titel, Datum, Uhrzeit, Ort, Gasthaus-Adresse, Briefvorlage, Gästeliste (VerstaltungsGast mit freien/Destinatär-Feldern) - Views: Veranstaltungsliste, -detail, Serienbrief-PDF-Generator - Templates: list.html, detail.html, serienbrief_pdf.html (A4, einseitig) - API: Serializer + Endpunkte für Veranstaltungen - Admin: Inline-Bearbeitung der Gästeliste - Migration: 0044_veranstaltungsmodul ### Serienbrief editierbare Felder + PDF-Fix (STI-39) - Neue Felder an Veranstaltung: betreff, unterschrift_1_name/titel, unterschrift_2_name/titel (mit Defaults: Katrin Kleinpaß / Jan Remmer Siebels) - PDF-CSS: Margins, Font-Sizes und Line-Heights reduziert für einseitigen Druck - Migration: 0045_add_serienbrief_editable_fields ### Infrastruktur - scripts/init-paperless-db.sh: Erstellt separate Paperless-DB beim DB-Init - compose.yml: init-paperless-db.sh eingebunden, PAPERLESS_DBNAME-Fix - .gitignore: .claude/ ausgeschlossen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
61
app/stiftung/migrations/0044_veranstaltungsmodul.py
Normal file
61
app/stiftung/migrations/0044_veranstaltungsmodul.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Generated by Django 5.0.6 on 2026-03-10 21:47
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stiftung', '0043_destinataer_email_eingang'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Veranstaltung',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('titel', models.CharField(max_length=200, verbose_name='Titel')),
|
||||
('datum', models.DateField(verbose_name='Datum')),
|
||||
('uhrzeit', models.TimeField(blank=True, null=True, verbose_name='Uhrzeit')),
|
||||
('ort', models.CharField(max_length=200, verbose_name='Ort / Gasthaus')),
|
||||
('adresse', models.TextField(blank=True, verbose_name='Adresse Gasthaus')),
|
||||
('beschreibung', models.TextField(blank=True, verbose_name='Beschreibung / Zweck')),
|
||||
('status', models.CharField(choices=[('geplant', 'Geplant'), ('einladungen_versendet', 'Einladungen versendet'), ('abgeschlossen', 'Abgeschlossen'), ('abgesagt', 'Abgesagt')], default='geplant', max_length=30, verbose_name='Status')),
|
||||
('budget_pro_person', models.DecimalField(blank=True, decimal_places=2, help_text='Geschätztes Budget je Teilnehmer in €', max_digits=8, null=True, verbose_name='Budget pro Person (€)')),
|
||||
('briefvorlage', models.TextField(blank=True, help_text='HTML/Text-Template für Serienbrief. Platzhalter: {{ anrede }}, {{ vorname }}, {{ nachname }}, {{ strasse }}, {{ plz }}, {{ ort }}, {{ datum }}, {{ uhrzeit }}, {{ veranstaltungsort }}, {{ gasthaus_adresse }}', verbose_name='Briefvorlage')),
|
||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
||||
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Veranstaltung',
|
||||
'verbose_name_plural': 'Veranstaltungen',
|
||||
'ordering': ['-datum'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Veranstaltungsteilnehmer',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('anrede', models.CharField(blank=True, choices=[('Herr', 'Herr'), ('Frau', 'Frau'), ('', 'Keine Anrede')], max_length=10, verbose_name='Anrede')),
|
||||
('vorname', models.CharField(max_length=100, verbose_name='Vorname')),
|
||||
('nachname', models.CharField(max_length=100, verbose_name='Nachname')),
|
||||
('strasse', models.CharField(blank=True, max_length=200, verbose_name='Straße')),
|
||||
('plz', models.CharField(blank=True, max_length=10, verbose_name='PLZ')),
|
||||
('ort', models.CharField(blank=True, max_length=100, verbose_name='Ort')),
|
||||
('email', models.EmailField(blank=True, help_text='Optional, für späteren E-Mail-Versand', max_length=254, verbose_name='E-Mail')),
|
||||
('rsvp_status', models.CharField(choices=[('eingeladen', 'Eingeladen'), ('zugesagt', 'Zugesagt'), ('abgesagt', 'Abgesagt'), ('keine_rueckmeldung', 'Keine Rückmeldung')], default='eingeladen', max_length=20, verbose_name='RSVP-Status')),
|
||||
('bemerkungen', models.TextField(blank=True, verbose_name='Bemerkungen')),
|
||||
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
||||
('destinataer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.destinataer', verbose_name='Destinatär (optional)')),
|
||||
('paechter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.paechter', verbose_name='Pächter (optional)')),
|
||||
('veranstaltung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teilnehmer', to='stiftung.veranstaltung', verbose_name='Veranstaltung')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Veranstaltungsteilnehmer',
|
||||
'verbose_name_plural': 'Veranstaltungsteilnehmer',
|
||||
'ordering': ['nachname', 'vorname'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.0.6 on 2026-03-10 22:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stiftung', '0044_veranstaltungsmodul'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='veranstaltung',
|
||||
name='betreff',
|
||||
field=models.CharField(blank=True, help_text='Betreffzeile des Serienbriefs. Leer = Standardbetreff.', max_length=300, verbose_name='Betreff'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='veranstaltung',
|
||||
name='unterschrift_1_name',
|
||||
field=models.CharField(blank=True, default='Katrin Kleinpaß', max_length=100, verbose_name='Unterschrift 1 – Name'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='veranstaltung',
|
||||
name='unterschrift_1_titel',
|
||||
field=models.CharField(blank=True, default='Rentmeisterin', max_length=100, verbose_name='Unterschrift 1 – Titel'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='veranstaltung',
|
||||
name='unterschrift_2_name',
|
||||
field=models.CharField(blank=True, default='Jan Remmer Siebels', max_length=100, verbose_name='Unterschrift 2 – Name'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='veranstaltung',
|
||||
name='unterschrift_2_titel',
|
||||
field=models.CharField(blank=True, default='Rentmeister', max_length=100, verbose_name='Unterschrift 2 – Titel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='vierteljahresnachweis',
|
||||
name='faelligkeitsdatum',
|
||||
field=models.DateField(blank=True, help_text='Veraltet - wird durch studiennachweis_faelligkeitsdatum und zahlung_faelligkeitsdatum ersetzt', null=True, verbose_name='Fälligkeitsdatum'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user