FSCash/html/aktien.php
fds2610-bot 6f377ea251 V.2.2: Unit-Tests für wichtige Funktionen, automatisch geprüft bei Update
Extrahiert die wichtigste DB-/netzwerkfreie Geschäftslogik (Monatsnavigation,
Admin-/Aktien-Rollenprüfung, Aktien-GuV-Berechnung, OpenFIGI-/Alpha-Vantage-
Antwortauswertung, Eingabevalidierung, CSV-Export, Migrations-Logik) nach
html/lib/fscash.php und deckt sie mit 31 PHPUnit-Tests ab. update.sh
installiert Composer-Abhängigkeiten bei Bedarf und führt die Tests jetzt vor
jedem Deploy aus - bei Fehlschlag wird nichts kopiert oder migriert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 17:54:30 +02:00

386 lines
10 KiB
PHP

<?php
require 'config.php';
require_once 'lib/fscash.php';
error_log('Session user_id: ' . (isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 'nicht gesetzt'));
// 1. Session-Prüfung (nur für geschützte Seiten)
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
// Ab hier: Nur für angemeldete Benutzer
error_log($_SESSION['user_id']);
$userid = $_SESSION['user_id'];
// Rollen des angemeldeten Benutzers ermitteln
$roles = fscash_user_roles((int)$_SESSION['user_id'], $admin_ids, $aktien_ids);
if ($roles['is_admin']) {
$is_admin = "true";
}
if ($roles['is_aktien']) {
$is_aktien = "true";
}
// Aktuelles Jahr und Monat ermitteln, ggf. aus GET-Parametern
$year = isset($_GET['year']) ? (int)$_GET['year'] : (int)date('Y');
$month = isset($_GET['month']) ? (int)$_GET['month'] : (int)date('m');
// Monats- und Jahresgrenzen prüfen und anpassen
['month' => $month, 'year' => $year] = fscash_normalize_month_year($month, $year);
// Daten abrufen
$sql="SELECT
id,
user_id,
WKNISIN,
Symbol,
Aktie,
kaufdatum,
kaufpreis,
anzahl,
kurs,
kursdatum,
(kaufpreis * anzahl) AS Kaufsumme
FROM
aktien
WHERE user_id = :userid;
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
'userid' => $userid
]);
$entries = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=0.8">
<title>FSCash - Dashboard</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<div class="container">
<!-- Navigation -->
<nav class="nav">
<button class="hamburger">☰</button>
<ul class="nav-list">
<li><a href="dashboard.php">Start</a></li>
<li><a href="menu5.php" style="display: none;">Menüpunkt 5</a></li>
</ul>
<!-- Container für die rechten Buttons -->
<div class="nav-right">
<a href="#" id="openUpdate" title="Hier klicken, um Kurse zu aktualisieren">
<svg class="update" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.5 2v6h-6M2.5 22v-6h6M22 11.5a10 10 0 0 1-10 10 10 10 0 0 1-10-10 10 10 0 0 1 10-10 10 10 0 0 1 10 10z"/>
</svg>
</a>
<a href="logout.php" class="logout">Logout</a>
</div>
</nav>
<!-- h2><?= strftime('%B %Y', mktime(0, 0, 0, $month, 1, $year)) ?></h2 -->
<!-- Tabelle -->
<table>
<thead>
<tr>
<th>ID</th>
<th>ISIN/Symbol</th>
<th>Aktie</th>
<th>Kaufdatum</th>
<th>Kaufpreis</th>
<th>Anzahl</th>
<th>Kaufsumme</th>
<th>Kurs</th>
<th>GuV</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<?php
$sumKaufSumme = 0;
$sumAktuellerWert = 0;
$sumGuV = 0;
foreach ($entries as $entry):
$kaufpreis = (float)$entry['kaufpreis'];
$anzahl = (float)$entry['anzahl'];
$kurs = isset($entry['kurs']) ? (float)$entry['kurs'] : 0;
$kursdatum = $entry['kursdatum'];
$guvData = fscash_aktie_guv($kaufpreis, $anzahl, $kurs);
$kaufSumme = $guvData['kaufSumme'];
$aktuellerWert = $guvData['aktuellerWert'];
$guv = $guvData['guv'];
$guvClass = $guvData['guvClass'];
$sumKaufSumme += $kaufSumme;
$sumAktuellerWert += $aktuellerWert;
$sumGuV += $guv;
?>
<tr>
<td><?= htmlspecialchars($entry['id']) ?></td>
<td class="kurs-cell" style="white-space: nowrap;">
<?= htmlspecialchars($entry['WKNISIN']) ?><br>
<span style="font-size: inherit;"><?= htmlspecialchars($entry['Symbol']) ?></span>
</td>
<td>
<a href="#" class="isin-link" data-id="<?= $entry['id'] ?>" data-name="<?= htmlspecialchars($entry['Aktie'], ENT_QUOTES) ?>" title="Hier klicken, um das Symbol zu suchen">
<?= htmlspecialchars($entry['Aktie']) ?>
</a>
</td>
<td><?= htmlspecialchars($entry['kaufdatum']) ?></td>
<td class="align-right">
<?= number_format($kaufpreis, 2, ',', '.') ?>
</td>
<td class="align-right">
<?= number_format($anzahl, 4, ',', '.') ?>
</td>
<td class="align-right">
<?= number_format($kaufSumme, 2, ',', '.') ?>
</td>
<td class="align-right kurs-cell">
<?php if ($kurs > 0): ?>
<?= number_format($kurs, 2, ',', '.') ?><br>
<span class="kurs-datum"><?= htmlspecialchars($kursdatum) ?></span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td class="align-right kurs-cell">
<?php if ($kurs > 0): ?>
<!-- Aktueller Gesamtwert -->
<?= number_format($aktuellerWert, 2, ',', '.') ?><br>
<!-- Gewinn / Verlust -->
<span class="<?= $guvClass ?>">
<?= number_format($guv, 2, ',', '.') ?>
</span>
<?php else: ?>
-
<?php endif; ?>
</td>
<td>
<a href="edit_aktie.php?id=<?= $entry['id'] ?>">
<img class="pencil" src="/assets/img/pencil.png" width="20px" />
</a>
</td>
</tr>
<?php endforeach; ?>
<tr class="sum-row">
<td colspan="6" class="align-right"><strong>Summe:</strong></td>
<td class="align-right">
<strong><?= number_format($sumKaufSumme, 2, ',', '.') ?></strong>
</td>
<td></td>
<td class="align-right">
<strong><?= number_format($sumAktuellerWert, 2, ',', '.') ?></strong><br>
<span class="<?= $sumGuV > 0 ? 'guv-positive' : ($sumGuV < 0 ? 'guv-negative' : '') ?>">
<strong><?= number_format($sumGuV, 2, ',', '.') ?></strong>
</span>
</td>
<td></td>
</tr>
</tbody>
</table>
<!-- Neu-Button -->
<a href="new_aktie.php" class="btn-xr">+</a>
</div>
<!-- Modal -->
<div id="updateModal" class="modal">
<div class="modal-content">
<h3>Kursaktualisierung</h3>
<div id="updateOutput" class="modal-body"></div>
<div class="modal-footer">
<button id="closeModal" class="btn">Schließen</button>
</div>
</div>
</div>
<!-- Modal für ISIN-Suche -->
<div id="isinModal" class="modal">
<div class="modal-content">
<h3>Symbol suchen</h3>
<div id="isinOutput" class="modal-body">
<p>Lade Ergebnisse...</p>
</div>
<div class="modal-footer">
<button id="saveSymbol" class="btn">Speichern</button>
<button id="closeIsinModal" class="btn">Schließen</button>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Hamburger-Menü
const hamburger = document.querySelector('.hamburger');
if (hamburger) {
hamburger.addEventListener('click', function() {
document.querySelector('.nav-list').classList.toggle('show');
});
}
// Modal-Elemente
const modal = document.getElementById('updateModal');
const openBtn = document.getElementById('openUpdate');
const closeBtn = document.getElementById('closeModal');
const output = document.getElementById('updateOutput');
if (!modal || !openBtn || !closeBtn || !output) {
console.error("Modal-Elemente nicht gefunden!");
return;
}
// Modal öffnen + Script laden
openBtn.addEventListener('click', function(e) {
e.preventDefault();
modal.style.display = 'flex';
output.innerHTML = "Starte Kursaktualisierung...<br>";
fetch('update_kurse.php')
.then(response => response.text())
.then(data => {
output.innerHTML += data.replace(/\n/g, "<br>");
output.scrollTop = output.scrollHeight;
})
.catch(error => {
output.innerHTML += "Fehler beim Laden.<br>";
});
});
// Modal schließen
closeBtn.addEventListener('click', function() {
modal.style.display = 'none';
location.reload();
});
// Klick außerhalb schließt Modal
window.addEventListener('click', function(e) {
if (e.target === modal) {
modal.style.display = 'none';
}
});
});
document.addEventListener("DOMContentLoaded", function() {
const isinLinks = document.querySelectorAll('.isin-link');
const isinModal = document.getElementById('isinModal');
const isinOutput = document.getElementById('isinOutput');
const closeIsinModal = document.getElementById('closeIsinModal');
const saveSymbolBtn = document.getElementById('saveSymbol');
let selectedEntryId = null;
let selectedSymbol = null;
// ISIN anklicken -> Modal öffnen
isinLinks.forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
selectedEntryId = this.dataset.id;
const aktienName = this.dataset.name;
isinModal.style.display = 'flex';
isinOutput.innerHTML = "Suche Symbole für " + aktienName + "...<br>";
// API abfragen
fetch('search_symbol.php?name=' + encodeURIComponent(aktienName))
.then(res => res.json())
.then(data => {
if (!data || data.length === 0) {
isinOutput.innerHTML = "Keine Ergebnisse gefunden.";
return;
}
// Radioboxen anzeigen
let html = '<form id="symbolForm">';
data.forEach((item, idx) => {
html += `<label>
<input type="radio" name="symbol" value="${item.symbol}" ${idx===0 ? 'checked' : ''}>
${item.symbol} (${item.name}, ${item.region})
</label><br>`;
});
html += '</form>';
isinOutput.innerHTML = html;
})
.catch(err => {
isinOutput.innerHTML = "Fehler bei der Suche.";
console.error(err);
});
});
});
// Modal schließen
closeIsinModal.addEventListener('click', () => isinModal.style.display = 'none');
window.addEventListener('click', function(e) {
if (e.target === isinModal) {
isinModal.style.display = 'none';
location.reload();
}
});
// Symbol speichern
saveSymbolBtn.addEventListener('click', () => {
const form = document.getElementById('symbolForm');
if (!form) return;
const formData = new FormData(form);
selectedSymbol = formData.get('symbol');
if (!selectedSymbol) return;
fetch('save_symbol.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ id: selectedEntryId, symbol: selectedSymbol })
})
.then(res => res.json())
.then(data => {
if (data.success) {
alert(data.message);
isinModal.style.display = 'none';
location.reload();
} else {
alert('Fehler: ' + data.message);
}
});
});
});
</script>
</body>
</html>