fix: configure CI database connection properly

- Add dotenv loading to Django settings
- Update CI workflow to use correct environment variables
- Set POSTGRES_* variables instead of DATABASE_URL
- Add environment variables to all Django management commands
- Fixes CI test failures due to database connection issues
This commit is contained in:
Stiftung Development
2025-09-06 18:47:23 +02:00
parent dcc91b9f49
commit 35ba089a84
64 changed files with 7040 additions and 1419 deletions

View File

@@ -0,0 +1,492 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Unterstützungen - Stiftungsverwaltung{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">
<i class="fas fa-hand-holding-usd text-primary me-2"></i>Unterstützungen
</h1>
<div>
<button type="button" class="btn btn-outline-secondary me-2" data-bs-toggle="modal" data-bs-target="#exportModal">
<i class="fas fa-download me-2"></i>Exportieren
</button>
<a href="{% url 'stiftung:wiederkehrende_unterstuetzungen' %}" class="btn btn-outline-info me-2">
<i class="fas fa-sync-alt me-2"></i> Wiederkehrende
</a>
<a href="{% url 'stiftung:unterstuetzung_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i> Neue Unterstützung
</a>
</div>
</div>
<!-- Export Modal -->
<div class="modal fade" id="exportModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Export Optionen</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="exportForm" method="post">
{% csrf_token %}
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<h6 class="fw-bold">Export Format</h6>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="format" id="format_csv" value="csv" checked>
<label class="form-check-label" for="format_csv">
<i class="fas fa-file-csv me-2"></i>CSV (Excel-kompatibel)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="format" id="format_pdf" value="pdf">
<label class="form-check-label" for="format_pdf">
<i class="fas fa-file-pdf me-2"></i>PDF (Tabelle)
</label>
</div>
</div>
<h6 class="fw-bold">Export Umfang</h6>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="scope" id="scope_all" value="all" checked>
<label class="form-check-label" for="scope_all">
Alle Einträge (<span id="total-count">{{ unterstuetzungen.count }}</span>)
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="scope" id="scope_selected" value="selected">
<label class="form-check-label" for="scope_selected">
Nur ausgewählte Einträge (<span id="selected-count">0</span>)
</label>
</div>
</div>
</div>
<div class="col-md-6" id="field-selection" style="max-height: 400px; overflow-y: auto;">
<h6 class="fw-bold">Felder auswählen</h6>
<div class="mb-2">
<button type="button" class="btn btn-sm btn-outline-primary me-2" onclick="selectAllFields()">Alle auswählen</button>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="selectDefaultFields()">Standard</button>
</div>
<div class="field-groups">
<!-- Core Payment Fields -->
<div class="mb-3">
<h6 class="text-primary mb-2">Zahlungsdaten</h6>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="destinataer_name" id="field_destinataer_name" checked>
<label class="form-check-label" for="field_destinataer_name">Destinatär Name</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="betrag" id="field_betrag" checked>
<label class="form-check-label" for="field_betrag">Betrag (€)</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="faellig_am" id="field_faellig_am" checked>
<label class="form-check-label" for="field_faellig_am">Fällig am</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="status" id="field_status" checked>
<label class="form-check-label" for="field_status">Status</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="beschreibung" id="field_beschreibung" checked>
<label class="form-check-label" for="field_beschreibung">Beschreibung</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="ausgezahlt_am" id="field_ausgezahlt_am">
<label class="form-check-label" for="field_ausgezahlt_am">Ausgezahlt am</label>
</div>
</div>
<!-- Payment Details -->
<div class="mb-3">
<h6 class="text-info mb-2">Überweisungsdetails</h6>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="empfaenger_iban" id="field_empfaenger_iban" checked>
<label class="form-check-label" for="field_empfaenger_iban">Empfänger IBAN</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="empfaenger_name" id="field_empfaenger_name" checked>
<label class="form-check-label" for="field_empfaenger_name">Empfänger Name</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="verwendungszweck" id="field_verwendungszweck">
<label class="form-check-label" for="field_verwendungszweck">Verwendungszweck</label>
</div>
</div>
<!-- Account Information -->
<div class="mb-3">
<h6 class="text-success mb-2">Kontoinformationen</h6>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="konto_name" id="field_konto_name">
<label class="form-check-label" for="field_konto_name">Konto</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="konto_bank" id="field_konto_bank">
<label class="form-check-label" for="field_konto_bank">Bank</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="konto_iban" id="field_konto_iban">
<label class="form-check-label" for="field_konto_iban">Konto IBAN</label>
</div>
</div>
<!-- Destinataer Personal Info -->
<div class="mb-3">
<h6 class="text-warning mb-2">Destinatär Details</h6>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="familienzweig" id="field_familienzweig">
<label class="form-check-label" for="field_familienzweig">Familienzweig</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="email" id="field_email">
<label class="form-check-label" for="field_email">E-Mail</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="telefon" id="field_telefon">
<label class="form-check-label" for="field_telefon">Telefon</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="adresse" id="field_adresse">
<label class="form-check-label" for="field_adresse">Adresse</label>
</div>
</div>
<!-- System Fields -->
<div class="mb-3">
<h6 class="text-secondary mb-2">System & Verlauf</h6>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="erstellt_am" id="field_erstellt_am">
<label class="form-check-label" for="field_erstellt_am">Erstellt am</label>
</div>
<div class="form-check form-check-sm">
<input class="form-check-input field-checkbox" type="checkbox" name="fields" value="ist_wiederkehrend" id="field_ist_wiederkehrend">
<label class="form-check-label" for="field_ist_wiederkehrend">Wiederkehrend</label>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-download me-2"></i>Exportieren
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<h5 class="card-title">Gesamtbetrag</h5>
<h3 class="card-text">€{{ total_betrag|floatformat:2 }}</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<h5 class="card-title">Durchschnitt</h5>
<h3 class="card-text">€{{ avg_betrag|floatformat:2 }}</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<h5 class="card-title">Anzahl</h5>
<h3 class="card-text">{{ unterstuetzungen.count }}</h3>
</div>
</div>
</div>
</div>
<!-- Filters -->
<div class="card mb-4">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-filter me-2"></i>Filter
</h6>
</div>
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-2">
<label for="status" class="form-label">Status</label>
<select name="status" id="status" class="form-select">
<option value="">Alle Status</option>
{% for status in status_choices %}
<option value="{{ status.0 }}" {% if status_filter == status.0 %}selected{% endif %}>{{ status.1 }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label for="destinataer" class="form-label">Destinatär</label>
<select name="destinataer" id="destinataer" class="form-select">
<option value="">Alle Destinatäre</option>
{% for dest in destinataer %}
<option value="{{ dest.id }}" {% if filter_destinataer == dest.id|stringformat:"s" %}selected{% endif %}>{{ dest.get_full_name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-search me-2"></i>Filtern
</button>
<a href="{% url 'stiftung:unterstuetzungen_all' %}" class="btn btn-outline-secondary">
<i class="fas fa-times me-2"></i>Zurücksetzen
</a>
</div>
</form>
</div>
</div>
<!-- Unterstützungen Table -->
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-list me-2"></i>Unterstützungen
</h6>
</div>
<div class="card-body">
{% if unterstuetzungen %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="selectAll" onchange="toggleAllCheckboxes()">
</div>
</th>
<th>Destinatär</th>
<th>Betrag</th>
<th>Fällig am</th>
<th>Konto</th>
<th>Status</th>
<th>Beschreibung</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for unterstuetzung in unterstuetzungen %}
<tr>
<td>
<div class="form-check">
<input class="form-check-input entry-checkbox" type="checkbox" name="selected_entries" value="{{ unterstuetzung.id }}" onchange="updateSelectedCount()">
</div>
</td>
<td>
<a href="{% url 'stiftung:destinataer_detail' unterstuetzung.destinataer.pk %}">
{{ unterstuetzung.destinataer.get_full_name }}
</a>
</td>
<td>€{{ unterstuetzung.betrag|floatformat:2 }}</td>
<td>{{ unterstuetzung.faellig_am|date:"d.m.Y" }}</td>
<td>
<span class="badge bg-secondary">{{ unterstuetzung.konto }}</span>
</td>
<td>
{% if unterstuetzung.status == 'geplant' %}
<span class="badge bg-secondary">{{ unterstuetzung.get_status_display }}</span>
{% elif unterstuetzung.status == 'in_bearbeitung' %}
<span class="badge bg-warning">{{ unterstuetzung.get_status_display }}</span>
{% elif unterstuetzung.status == 'ausgezahlt' %}
<span class="badge bg-success">{{ unterstuetzung.get_status_display }}</span>
{% else %}
<span class="badge bg-danger">{{ unterstuetzung.get_status_display }}</span>
{% endif %}
</td>
<td>{{ unterstuetzung.beschreibung|truncatechars:40 }}</td>
<td>
<div class="btn-group" role="group">
<a href="{% url 'stiftung:unterstuetzung_detail' unterstuetzung.pk %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-hand-holding-usd fa-3x text-muted mb-3"></i>
<h5 class="text-muted">Keine Unterstützungen gefunden</h5>
<p class="text-muted">Erstellen Sie Ihre erste Unterstützung oder passen Sie die Filter an.</p>
<a href="{% url 'stiftung:unterstuetzung_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Neue Unterstützung erstellen
</a>
</div>
{% endif %}
</div>
</div>
<script>
// Field selection functions
function selectAllFields() {
document.querySelectorAll('.field-checkbox').forEach(checkbox => {
checkbox.checked = true;
});
}
function selectDefaultFields() {
// Uncheck all first
document.querySelectorAll('.field-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
// Check default fields
const defaultFields = [
'destinataer_name', 'betrag', 'faellig_am', 'status',
'empfaenger_iban', 'empfaenger_name', 'beschreibung'
];
defaultFields.forEach(fieldName => {
const checkbox = document.getElementById('field_' + fieldName);
if (checkbox) checkbox.checked = true;
});
}
// Bulk selection functions
function toggleAllCheckboxes() {
const selectAll = document.getElementById('selectAll');
const checkboxes = document.querySelectorAll('.entry-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = selectAll.checked;
});
updateSelectedCount();
}
function updateSelectedCount() {
const selectedCheckboxes = document.querySelectorAll('.entry-checkbox:checked');
const count = selectedCheckboxes.length;
document.getElementById('selected-count').textContent = count;
// Update the select all checkbox state
const selectAll = document.getElementById('selectAll');
const allCheckboxes = document.querySelectorAll('.entry-checkbox');
if (count === 0) {
selectAll.indeterminate = false;
selectAll.checked = false;
} else if (count === allCheckboxes.length) {
selectAll.indeterminate = false;
selectAll.checked = true;
} else {
selectAll.indeterminate = true;
}
// Enable/disable the "selected only" radio button
const scopeSelected = document.getElementById('scope_selected');
const scopeSelectedLabel = scopeSelected.parentElement.querySelector('label');
if (count > 0) {
scopeSelected.disabled = false;
scopeSelectedLabel.classList.remove('text-muted');
} else {
scopeSelected.disabled = true;
scopeSelected.checked = false;
document.getElementById('scope_all').checked = true;
scopeSelectedLabel.classList.add('text-muted');
}
}
// Export form handling
document.getElementById('exportForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const format = formData.get('format');
const scope = formData.get('scope');
// Build query string for GET request with format
const params = new URLSearchParams();
params.append('format', format);
// Add selected fields to the URL
formData.getAll('fields').forEach(field => {
params.append('fields', field);
});
// If scope is "selected", we need to POST the selected IDs
if (scope === 'selected') {
const selectedIds = [];
document.querySelectorAll('.entry-checkbox:checked').forEach(checkbox => {
selectedIds.push(checkbox.value);
});
if (selectedIds.length === 0) {
alert('Bitte wählen Sie mindestens einen Eintrag aus.');
return;
}
// Create a temporary form for POST request
const tempForm = document.createElement('form');
tempForm.method = 'POST';
tempForm.action = window.location.pathname + '?' + params.toString();
tempForm.style.display = 'none';
// Add CSRF token
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = 'csrfmiddlewaretoken';
csrfInput.value = csrfToken;
tempForm.appendChild(csrfInput);
// Add selected IDs
selectedIds.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'selected_entries';
input.value = id;
tempForm.appendChild(input);
});
// Add fields
formData.getAll('fields').forEach(field => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'fields';
input.value = field;
tempForm.appendChild(input);
});
document.body.appendChild(tempForm);
tempForm.submit();
document.body.removeChild(tempForm);
} else {
// For "all" scope, use GET request
window.location.href = window.location.pathname + '?' + params.toString();
}
// Close modal
bootstrap.Modal.getInstance(document.getElementById('exportModal')).hide();
});
// Initialize counts on page load
document.addEventListener('DOMContentLoaded', function() {
updateSelectedCount();
});
</script>
{% endblock %}