Files
domili/kasse.php
2026-02-02 12:11:12 +01:00

509 lines
21 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
include('inc/check_login.php');
require_once('inc/db.php');
$is_admin = ($_SESSION['role'] === 'admin');
// --- JAHRESABSCHLUSS ---
if ($is_admin && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'close_year') {
$today = date('Y-m-d');
$user_id = $_SESSION['user_id'] ?? 0;
$last_closing_date = '2020-01-01';
$result_last = mysqli_query($conn, "SELECT MAX(closing_date) AS last_date FROM penalty_closings");
if ($result_last) {
$row = mysqli_fetch_assoc($result_last);
if ($row && !is_null($row['last_date'])) {
$last_closing_date = $row['last_date'];
}
}
$check = mysqli_prepare($conn, "SELECT 1 FROM penalty_closings WHERE closing_date = ?");
mysqli_stmt_bind_param($check, "s", $today);
mysqli_stmt_execute($check);
$exists = mysqli_stmt_get_result($check)->num_rows > 0;
mysqli_stmt_close($check);
if (!$exists) {
// 1. Abschluss in penalty_closings protokollieren
$insert = mysqli_prepare($conn, "INSERT INTO penalty_closings (closing_date, closed_by) VALUES (?, ?)");
mysqli_stmt_bind_param($insert, "si", $today, $user_id);
mysqli_stmt_execute($insert);
mysqli_stmt_close($insert);
// 2. E-Mail-Daten: Strafen seit letztem Abschluss (für Benachrichtigung)
$penalty_summary = [];
$stmt_email = mysqli_prepare($conn, "
SELECT u.username, COUNT(*) AS penalty_count
FROM meeting_teilnehmer mt
JOIN meetings m ON mt.meeting_id = m.id
JOIN users u ON mt.user_id = u.id
WHERE m.meeting_date > ? AND m.meeting_date <= ?
AND mt.attended = 1 AND mt.wore_color = 0 AND m.is_completed = 1
GROUP BY u.username
");
mysqli_stmt_bind_param($stmt_email, "ss", $last_closing_date, $today);
mysqli_stmt_execute($stmt_email);
$result_email = mysqli_stmt_get_result($stmt_email);
while ($row = mysqli_fetch_assoc($result_email)) {
$penalty_summary[] = $row;
}
mysqli_stmt_close($stmt_email);
// 3. NEU: Kumulierte Strafen JEDES Benutzers bis heute in penalty_history speichern
$stmt_save = mysqli_prepare($conn, "
INSERT INTO penalty_history (closing_date, user_id, username_at_close, penalty_count)
SELECT ?, u.id, u.username, COUNT(mt.id)
FROM users u
LEFT JOIN meeting_teilnehmer mt ON u.id = mt.user_id
LEFT JOIN meetings m ON mt.meeting_id = m.id
AND m.meeting_date <= ?
AND mt.attended = 1
AND mt.wore_color = 0
AND m.is_completed = 1
GROUP BY u.id, u.username
");
mysqli_stmt_bind_param($stmt_save, "ss", $today, $today);
mysqli_stmt_execute($stmt_save);
mysqli_stmt_close($stmt_save);
// 4. E-Mail-Versand (unverändert)
$all_users = [];
$stmt_users = mysqli_prepare($conn, "SELECT id, username, email FROM users WHERE email IS NOT NULL AND email != ''");
mysqli_stmt_execute($stmt_users);
$result_users = mysqli_stmt_get_result($stmt_users);
while ($row = mysqli_fetch_assoc($result_users)) {
$all_users[] = $row;
}
mysqli_stmt_close($stmt_users);
if (!empty($all_users) && file_exists(__DIR__ . '/vendor/autoload.php')) {
require_once __DIR__ . '/vendor/autoload.php';
$today_formatted = date('d.m.Y', strtotime($today));
$last_closing_formatted = date('d.m.Y', strtotime($last_closing_date));
foreach ($all_users as $recipient) {
$recipient_email = $recipient['email'];
$recipient_name = $recipient['username'] ?? 'Benutzer';
try {
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
$mail->CharSet = 'UTF-8';
$mail->isSMTP();
$mail->Host = SMTP_HOST;
$mail->SMTPAuth = true;
$mail->Username = SMTP_USERNAME;
$mail->Password = SMTP_PASSWORD;
$mail->SMTPSecure = SMTP_ENCRYPTION;
$mail->Port = SMTP_PORT;
$mail->setFrom(MAIL_FROM_ADDRESS, MAIL_FROM_NAME);
$text_body = "Hallo $recipient_name,\n\n";
$text_body .= "Der Jahresabschluss wurde am $today_formatted durchgeführt.\n";
$text_body .= "Strafen der abgeschlossenen Periode (vom $last_closing_formatted bis $today_formatted):\n\n";
if (empty($penalty_summary)) {
$text_body .= "Keine Strafen in diesem Zeitraum.\n";
} else {
foreach ($penalty_summary as $row) {
$text_body .= "- " . $row['username'] . ": " . (int)$row['penalty_count'] . " Strafen (1 € pro Strafe)\n";
}
}
$text_body .= "\nDein DoMiLi-Admin.";
$html_table_rows = '';
if (empty($penalty_summary)) {
$html_table_rows = '<tr><td colspan="3" style="text-align:center;">Keine Strafen in diesem Zeitraum.</td></tr>';
} else {
foreach ($penalty_summary as $row) {
$html_table_rows .= sprintf(
'<tr>
<td style="padding:8px;border:1px solid #ddd;">%s</td>
<td style="padding:8px;border:1px solid #ddd;text-align:center;">%d</td>
<td style="padding:8px;border:1px solid #ddd;text-align:right;">%s</td>
</tr>',
htmlspecialchars($row['username']),
(int)$row['penalty_count'],
number_format((float)($row['penalty_count']), 2, ',', '.') . ' €'
);
}
}
$html_body = "
<p>Hallo <strong>$recipient_name</strong>,</p>
<p>der Jahresabschluss wurde am <strong>$today_formatted</strong> durchgeführt.</p>
<p><strong>Strafen der abgeschlossenen Periode</strong> (vom $last_closing_formatted bis $today_formatted):</p>
<table style=\"border-collapse:collapse;width:100%;margin:15px 0;\">
<thead>
<tr>
<th style=\"padding:8px;border:1px solid #ddd;text-align:left;background:#f2f2f2;\">Mitglied</th>
<th style=\"padding:8px;border:1px solid #ddd;text-align:center;background:#f2f2f2;\">Anzahl Strafen</th>
<th style=\"padding:8px;border:1px solid #ddd;text-align:right;background:#f2f2f2;\">Betrag</th>
</tr>
</thead>
<tbody>
$html_table_rows
</tbody>
</table>
<p><em>Dein DoMiLi-Admin.</em></p>";
$mail->isHTML(true);
$mail->Subject = "DoMiLi Jahresabschluss abgeschlossen am $today_formatted";
$mail->Body = $html_body;
$mail->AltBody = $text_body;
$mail->addAddress($recipient_email);
$mail->send();
} catch (Exception $e) {
error_log("PHPMailer Fehler: " . $mail->ErrorInfo);
}
}
}
$close_success = "Jahresabschluss erfolgreich durchgeführt!";
} else {
$close_error = "Heute wurde bereits ein Abschluss durchgeführt.";
}
}
// --- DATEN FÜR ANZEIGE ---
// Letztes Abschlussdatum ermitteln
$last_closing_date = '2020-01-01';
$result_last = mysqli_query($conn, "SELECT MAX(closing_date) AS last_date FROM penalty_closings");
if ($result_last) {
$row = mysqli_fetch_assoc($result_last);
if ($row && !is_null($row['last_date'])) {
$last_closing_date = $row['last_date'];
}
}
$penalty_amount = 1;
// 🔹 Alle Mitglieder initialisieren
$penalties_data = [];
$stmt_members = mysqli_prepare($conn, "SELECT username FROM users ORDER BY username");
mysqli_stmt_execute($stmt_members);
$result_members = mysqli_stmt_get_result($stmt_members);
while ($row = mysqli_fetch_assoc($result_members)) {
$penalties_data[$row['username']] = [
'username' => $row['username'],
'total_penalty_count' => 0,
'open_penalty_count' => 0
];
}
mysqli_stmt_close($stmt_members);
// 🔹 "Strafen gesamt" = KUMULIERT seit Beginn (nur echte Strafen)
$stmt_total = mysqli_prepare($conn, "
SELECT u.username, COUNT(penalty.meeting_id) AS total_penalty_count
FROM users u
LEFT JOIN (
SELECT mt.user_id, mt.meeting_id
FROM meeting_teilnehmer mt
INNER JOIN meetings m ON mt.meeting_id = m.id
WHERE mt.attended = 1
AND mt.wore_color = 0
AND m.is_completed = 1
) AS penalty ON u.id = penalty.user_id
GROUP BY u.id, u.username
");
mysqli_stmt_execute($stmt_total);
$result_total = mysqli_stmt_get_result($stmt_total);
while ($row = mysqli_fetch_assoc($result_total)) {
if (isset($penalties_data[$row['username']])) {
$penalties_data[$row['username']]['total_penalty_count'] = (int)$row['total_penalty_count'];
}
}
mysqli_stmt_close($stmt_total);
// 🔹 "offener Betrag" = Strafen SEIT letztem Abschluss (>)
$stmt_open = mysqli_prepare($conn, "
SELECT u.username, COUNT(*) AS open_penalty_count
FROM meeting_teilnehmer mt
JOIN meetings m ON mt.meeting_id = m.id
JOIN users u ON mt.user_id = u.id
WHERE m.meeting_date > ? AND mt.attended = 1 AND mt.wore_color = 0 AND m.is_completed = 1
GROUP BY u.username
");
mysqli_stmt_bind_param($stmt_open, "s", $last_closing_date);
mysqli_stmt_execute($stmt_open);
$result_open = mysqli_stmt_get_result($stmt_open);
while ($row = mysqli_fetch_assoc($result_open)) {
if (isset($penalties_data[$row['username']])) {
$penalties_data[$row['username']]['open_penalty_count'] = (int)$row['open_penalty_count'];
}
}
mysqli_stmt_close($stmt_open);
// 🔹 Sortieren
$penalties_data = array_values($penalties_data);
usort($penalties_data, function ($a, $b) {
if ($b['open_penalty_count'] !== $a['open_penalty_count']) {
return $b['open_penalty_count'] <=> $a['open_penalty_count'];
}
return strcmp($a['username'], $b['username']);
});
$total_open = array_sum(array_column($penalties_data, 'open_penalty_count'));
$total_due = $total_open * $penalty_amount;
// 🔹 Historische Strafen: pro Abschluss-Zeitraum
$historical_periods = [];
$closing_dates = [];
$result_closings = mysqli_query($conn, "SELECT closing_date FROM penalty_closings ORDER BY closing_date");
while ($row = mysqli_fetch_assoc($result_closings)) {
$closing_dates[] = $row['closing_date'];
}
if (count($closing_dates) >= 2) {
for ($i = 1; $i < count($closing_dates); $i++) {
$start_date = $closing_dates[$i - 1];
$end_date = $closing_dates[$i];
$period_label = date('d.m.Y', strtotime($start_date)) . ' ' . date('d.m.Y', strtotime($end_date));
$stmt_hist = mysqli_prepare($conn, "
SELECT u.username, COUNT(*) AS penalty_count
FROM meeting_teilnehmer mt
JOIN meetings m ON mt.meeting_id = m.id
JOIN users u ON mt.user_id = u.id
WHERE m.meeting_date > ? AND m.meeting_date <= ?
AND mt.attended = 1 AND mt.wore_color = 0 AND m.is_completed = 1
GROUP BY u.username
");
mysqli_stmt_bind_param($stmt_hist, "ss", $start_date, $end_date);
mysqli_stmt_execute($stmt_hist);
$result_hist = mysqli_stmt_get_result($stmt_hist);
while ($row = mysqli_fetch_assoc($result_hist)) {
$historical_periods[] = [
'username' => $row['username'],
'period' => $period_label,
'count' => (int)$row['penalty_count']
];
}
mysqli_stmt_close($stmt_hist);
}
} elseif (count($closing_dates) === 1) {
$end_date = $closing_dates[0];
$period_label = '17.07.2025 ' . date('d.m.Y', strtotime($end_date));
$stmt_hist = mysqli_prepare($conn, "
SELECT u.username, COUNT(*) AS penalty_count
FROM meeting_teilnehmer mt
JOIN meetings m ON mt.meeting_id = m.id
JOIN users u ON mt.user_id = u.id
WHERE m.meeting_date <= ?
AND mt.attended = 1 AND mt.wore_color = 0 AND m.is_completed = 1
GROUP BY u.username
");
mysqli_stmt_bind_param($stmt_hist, "s", $end_date);
mysqli_stmt_execute($stmt_hist);
$result_hist = mysqli_stmt_get_result($stmt_hist);
while ($row = mysqli_fetch_assoc($result_hist)) {
$historical_periods[] = [
'username' => $row['username'],
'period' => $period_label,
'count' => (int)$row['penalty_count']
];
}
mysqli_stmt_close($stmt_hist);
}
// --- Rechnungs-Statistik (unverändert) ---
$paid_stats = [];
$sql_paid = "
SELECT u.username, COUNT(mt.user_id) AS paid_count
FROM meeting_teilnehmer mt
JOIN meetings m ON mt.meeting_id = m.id
JOIN users u ON mt.user_id = u.id
WHERE mt.paid = 1
AND mt.birthday_pay = 0
AND m.is_completed = 1
GROUP BY u.username
ORDER BY paid_count ASC, u.username ASC
";
$result_paid = mysqli_query($conn, $sql_paid);
while ($row = mysqli_fetch_assoc($result_paid)) {
$paid_stats[] = $row;
}
require_once 'inc/header.php';
?>
<div class="container mt-5">
<h2 class="mb-4">Kasse</h2>
<?php if (!empty($close_success)): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= htmlspecialchars($close_success) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (!empty($close_error)): ?>
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<?= htmlspecialchars($close_error) ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<div class="card shadow mb-4">
<div class="card-header bg-primary-subtle text-secondary d-flex justify-content-between align-items-center">
<h4 class="mb-0">Strafen</h4>
<?php if ($is_admin): ?>
<form method="POST" class="d-flex align-items-center m-0" onsubmit="return confirm('Jahresabschluss durchführen?');">
<input type="hidden" name="action" value="close_year">
<button type="submit" class="btn btn-sm d-flex align-items-center justify-content-center p-0" style="height: 24px; font-size: 0.875rem;">
Jahresabschluss
<span class="material-symbols-outlined ms-1" style="font-size: 18px; line-height: 1;">add</span>
</button>
</form>
<?php endif; ?>
</div>
<div class="card-body">
<p class="fs-5 fw-bold text-center">
Strafkasse: <span class="text-danger fw-bold"><?= number_format($total_due, 2, ',', '.') ?> €</span>
</p>
<?php if (empty($penalties_data)): ?>
<div class="alert alert-info text-center">Keine Mitglieder gefunden.</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Benutzer</th>
<th>Strafen gesamt</th>
<th>offener Betrag</th>
</tr>
</thead>
<tbody>
<?php foreach ($penalties_data as $p): ?>
<tr>
<td><?= htmlspecialchars($p['username']) ?></td>
<td><?= htmlspecialchars($p['total_penalty_count']) ?></td>
<td><?= number_format($p['open_penalty_count'] * $penalty_amount, 2, ',', '.') ?> €</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<div class="card shadow mb-4">
<div class="card-header bg-primary-subtle text-secondary">
<h4 class="mb-0">Historische Strafen</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Benutzer</th>
<th>Zeitraum</th>
<th>Strafen</th>
</tr>
</thead>
<tbody>
<?php if (!empty($historical_periods)): ?>
<?php foreach ($historical_periods as $item): ?>
<tr>
<td><?= htmlspecialchars($item['username']) ?></td>
<td><?= htmlspecialchars($item['period']) ?></td>
<td><?= htmlspecialchars($item['count']) ?></td>
</tr>
<?php endforeach; ?>
<?php else: ?>
<tr>
<td colspan="3" class="text-center text-muted">—</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="card shadow mb-4">
<div class="card-header bg-primary-subtle text-secondary">
<h4 class="mb-0">Rechnungen</h4>
</div>
<div class="card-body">
<h5 class="card-title text-center">Ranking Rechnung übernommen</h5>
<p class="text-muted text-center small mb-1">Position 1 = nächster Zahler</p>
<div style="height: 40vh;">
<canvas id="paidChart"></canvas>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const rawLabels = <?= json_encode(array_column($paid_stats, 'username') ?: []) ?>;
const rawData = <?= json_encode(array_map('intval', array_column($paid_stats, 'paid_count') ?: [])) ?>;
const ctx = document.getElementById('paidChart').getContext('2d');
if (rawLabels.length === 0 || rawData.length === 0) {
document.getElementById('paidChart').closest('.card-body').innerHTML =
'<p class="text-muted text-center mt-3">Keine bezahlten Rechnungen erfasst.</p>';
return;
}
new Chart(ctx, {
type: 'bar',
data: {
labels: rawLabels,
datasets: [{
label: 'Gezahlte Strafen',
data: rawData,
backgroundColor: 'rgba(54, 162, 235, 0.6)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Anzahl der bezahlten Rechnungen'
}
},
scales: {
x: {
beginAtZero: true,
ticks: {
stepSize: 1,
callback: function(value) {
return Number.isInteger(value) ? value : null;
}
}
},
y: {
ticks: {
font: {
size: 12
},
callback: function(value, index, ticks) {
const label = this.getLabelForValue(value);
return label.length > 15 ? label.substring(0, 15) + '…' : label;
}
}
}
}
}
});
});
</script>
<?php include('inc/footer.php'); ?>