diff --git a/api key b/api key
new file mode 100644
index 0000000..8142069
--- /dev/null
+++ b/api key
@@ -0,0 +1,3 @@
+API Key Alphavantage.co
+
+NEMNJQYA35TJ1W9R
diff --git a/modules/boersenchecker/bootstrap.php b/modules/boersenchecker/bootstrap.php
new file mode 100644
index 0000000..f6e6a9c
--- /dev/null
+++ b/modules/boersenchecker/bootstrap.php
@@ -0,0 +1,402 @@
+registerFunction($moduleName, 'table', static function (string $name): string {
+ $prefix = 'boersencheck_';
+ $sanitized = preg_replace('/[^a-zA-Z0-9_]/', '', $name);
+ return $prefix . $sanitized;
+});
+
+$mm->registerFunction($moduleName, 'pdo', function () use ($moduleName): \PDO {
+ $settings = modules()->settings($moduleName);
+ $useSeparate = !empty($settings['use_separate_db']);
+
+ if ($useSeparate) {
+ $module = modules()->get($moduleName);
+ $fallback = $module['db_defaults'] ?? [];
+ return modules()->modulePdo($moduleName, $fallback);
+ }
+
+ $base = app()->basePdo();
+ if ($base instanceof \PDO) {
+ return $base;
+ }
+
+ throw new ModuleConfigException(
+ $moduleName,
+ 'Base-DB ist deaktiviert. Bitte Base-DB aktivieren oder eine eigene Modul-DB konfigurieren.'
+ );
+});
+
+$mm->registerFunction($moduleName, 'ensure_schema', function () use ($moduleName): void {
+ $pdo = module_fn($moduleName, 'pdo');
+ $table = static fn (string $name): string => module_fn($moduleName, 'table', $name);
+ $driver = strtolower((string) $pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
+
+ $portfolioTable = $table('portfolios');
+ $instrumentTable = $table('instruments');
+ $positionTable = $table('positions');
+ $quoteTable = $table('quotes');
+
+ if ($driver === 'pgsql') {
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$portfolioTable} (
+ id SERIAL PRIMARY KEY,
+ owner_sub VARCHAR(190) NOT NULL,
+ name VARCHAR(190) NOT NULL,
+ base_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
+ notes TEXT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$instrumentTable} (
+ id SERIAL PRIMARY KEY,
+ isin VARCHAR(32) NULL,
+ wkn VARCHAR(32) NULL,
+ symbol VARCHAR(32) NULL,
+ name VARCHAR(255) NOT NULL,
+ quote_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
+ market VARCHAR(120) NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$positionTable} (
+ id SERIAL PRIMARY KEY,
+ owner_sub VARCHAR(190) NOT NULL,
+ portfolio_id INTEGER NOT NULL,
+ instrument_id INTEGER NOT NULL,
+ quantity NUMERIC(20,6) NOT NULL,
+ purchase_price NUMERIC(20,8) NOT NULL,
+ purchase_currency VARCHAR(10) NOT NULL,
+ purchase_date DATE NOT NULL,
+ fees NUMERIC(20,8) NULL,
+ notes TEXT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$quoteTable} (
+ id SERIAL PRIMARY KEY,
+ instrument_id INTEGER NOT NULL,
+ price NUMERIC(20,8) NOT NULL,
+ currency VARCHAR(10) NOT NULL,
+ quoted_at TIMESTAMP NOT NULL,
+ source VARCHAR(64) NOT NULL DEFAULT 'manual',
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$portfolioTable}_owner_idx ON {$portfolioTable} (owner_sub)");
+ $pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS {$instrumentTable}_isin_uniq ON {$instrumentTable} (isin) WHERE isin IS NOT NULL");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$instrumentTable}_symbol_idx ON {$instrumentTable} (symbol)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_owner_idx ON {$positionTable} (owner_sub)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_portfolio_idx ON {$positionTable} (portfolio_id)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_instrument_idx ON {$positionTable} (instrument_id)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$quoteTable}_instrument_time_idx ON {$quoteTable} (instrument_id, quoted_at DESC)");
+ } elseif ($driver === 'mysql') {
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$portfolioTable} (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ owner_sub VARCHAR(190) NOT NULL,
+ name VARCHAR(190) NOT NULL,
+ base_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
+ notes TEXT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ KEY {$portfolioTable}_owner_idx (owner_sub)
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$instrumentTable} (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ isin VARCHAR(32) NULL,
+ wkn VARCHAR(32) NULL,
+ symbol VARCHAR(32) NULL,
+ name VARCHAR(255) NOT NULL,
+ quote_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
+ market VARCHAR(120) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE KEY {$instrumentTable}_isin_uniq (isin),
+ KEY {$instrumentTable}_symbol_idx (symbol)
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$positionTable} (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ owner_sub VARCHAR(190) NOT NULL,
+ portfolio_id INTEGER NOT NULL,
+ instrument_id INTEGER NOT NULL,
+ quantity DECIMAL(20,6) NOT NULL,
+ purchase_price DECIMAL(20,8) NOT NULL,
+ purchase_currency VARCHAR(10) NOT NULL,
+ purchase_date DATE NOT NULL,
+ fees DECIMAL(20,8) NULL,
+ notes TEXT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ KEY {$positionTable}_owner_idx (owner_sub),
+ KEY {$positionTable}_portfolio_idx (portfolio_id),
+ KEY {$positionTable}_instrument_idx (instrument_id)
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$quoteTable} (
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
+ instrument_id INTEGER NOT NULL,
+ price DECIMAL(20,8) NOT NULL,
+ currency VARCHAR(10) NOT NULL,
+ quoted_at DATETIME NOT NULL,
+ source VARCHAR(64) NOT NULL DEFAULT 'manual',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ KEY {$quoteTable}_instrument_time_idx (instrument_id, quoted_at)
+ )");
+ } else {
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$portfolioTable} (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ owner_sub VARCHAR(190) NOT NULL,
+ name VARCHAR(190) NOT NULL,
+ base_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
+ notes TEXT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$instrumentTable} (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ isin VARCHAR(32) NULL,
+ wkn VARCHAR(32) NULL,
+ symbol VARCHAR(32) NULL,
+ name VARCHAR(255) NOT NULL,
+ quote_currency VARCHAR(10) NOT NULL DEFAULT 'EUR',
+ market VARCHAR(120) NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$positionTable} (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ owner_sub VARCHAR(190) NOT NULL,
+ portfolio_id INTEGER NOT NULL,
+ instrument_id INTEGER NOT NULL,
+ quantity DECIMAL(20,6) NOT NULL,
+ purchase_price DECIMAL(20,8) NOT NULL,
+ purchase_currency VARCHAR(10) NOT NULL,
+ purchase_date DATE NOT NULL,
+ fees DECIMAL(20,8) NULL,
+ notes TEXT NULL,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE TABLE IF NOT EXISTS {$quoteTable} (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ instrument_id INTEGER NOT NULL,
+ price DECIMAL(20,8) NOT NULL,
+ currency VARCHAR(10) NOT NULL,
+ quoted_at DATETIME NOT NULL,
+ source VARCHAR(64) NOT NULL DEFAULT 'manual',
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
+ )");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$portfolioTable}_owner_idx ON {$portfolioTable} (owner_sub)");
+ $pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS {$instrumentTable}_isin_uniq ON {$instrumentTable} (isin)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$instrumentTable}_symbol_idx ON {$instrumentTable} (symbol)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_owner_idx ON {$positionTable} (owner_sub)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_portfolio_idx ON {$positionTable} (portfolio_id)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$positionTable}_instrument_idx ON {$positionTable} (instrument_id)");
+ $pdo->exec("CREATE INDEX IF NOT EXISTS {$quoteTable}_instrument_time_idx ON {$quoteTable} (instrument_id, quoted_at DESC)");
+ }
+});
+
+$mm->registerFunction($moduleName, 'fx_service', static function (): ?object {
+ if (!is_dir(dirname(__DIR__) . '/mining-checker')) {
+ return null;
+ }
+
+ try {
+ $config = \Modules\MiningChecker\Infrastructure\ModuleConfig::load(dirname(__DIR__) . '/mining-checker');
+ $repo = new \Modules\MiningChecker\Infrastructure\MiningRepository(
+ \Modules\MiningChecker\Infrastructure\ConnectionFactory::make($config),
+ $config->tablePrefix()
+ );
+ $fx = $config->fx();
+
+ return new \Modules\MiningChecker\Domain\FxService(
+ $repo,
+ (string) ($fx['url'] ?? 'https://currencyapi.net'),
+ (string) ($fx['currencies_url'] ?? ($fx['url'] ?? 'https://currencyapi.net')),
+ (int) ($fx['timeout'] ?? 10),
+ (int) ($fx['cache_ttl'] ?? 21600),
+ false,
+ (string) ($fx['provider'] ?? 'currencyapi'),
+ (string) ($fx['api_key'] ?? '')
+ );
+ } catch (\Throwable) {
+ return null;
+ }
+});
+
+$mm->registerFunction($moduleName, 'fx_refresh', static function (string $baseCurrency = 'EUR', float $maxAgeHours = 6.0): array {
+ $service = module_fn('boersenchecker', 'fx_service');
+ if (!$service || !method_exists($service, 'ensureFreshLatestRates')) {
+ return [
+ 'ok' => false,
+ 'message' => 'Mining-Checker FX-Service ist aktuell nicht verfuegbar.',
+ ];
+ }
+
+ try {
+ $result = $service->ensureFreshLatestRates($maxAgeHours, strtoupper(trim($baseCurrency)) ?: 'EUR');
+ return [
+ 'ok' => true,
+ 'message' => !empty($result['reused'])
+ ? 'Vorhandene FX-Daten weiterverwendet.'
+ : 'FX-Daten aus dem Mining-Checker aktualisiert.',
+ 'result' => $result,
+ ];
+ } catch (\Throwable $e) {
+ return [
+ 'ok' => false,
+ 'message' => 'FX-Aktualisierung fehlgeschlagen: ' . $e->getMessage(),
+ ];
+ }
+});
+
+$mm->registerFunction($moduleName, 'alpha_vantage_fetch_quote', static function (string $symbol): array {
+ $settings = modules()->settings('boersenchecker');
+ $apiKey = trim((string) ($settings['alpha_vantage_api_key'] ?? ''));
+ $timeout = (int) ($settings['alpha_vantage_timeout_sec'] ?? 12);
+ $timeout = $timeout > 0 ? $timeout : 12;
+ $symbol = strtoupper(trim($symbol));
+
+ if ($symbol === '') {
+ return [
+ 'ok' => false,
+ 'message' => 'Kein API-Symbol hinterlegt.',
+ ];
+ }
+
+ if ($apiKey === '') {
+ return [
+ 'ok' => false,
+ 'message' => 'Alpha-Vantage-API-Key fehlt. Bitte im Modul-Setup hinterlegen.',
+ ];
+ }
+
+ $url = 'https://www.alphavantage.co/query?' . http_build_query([
+ 'function' => 'GLOBAL_QUOTE',
+ 'symbol' => $symbol,
+ 'apikey' => $apiKey,
+ ]);
+
+ $responseBody = null;
+
+ if (function_exists('curl_init')) {
+ $ch = curl_init($url);
+ if ($ch !== false) {
+ curl_setopt_array($ch, [
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_TIMEOUT => $timeout,
+ CURLOPT_CONNECTTIMEOUT => min(5, $timeout),
+ CURLOPT_HTTPHEADER => ['Accept: application/json'],
+ ]);
+ $responseBody = curl_exec($ch);
+ $curlError = curl_error($ch);
+ $httpCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
+ curl_close($ch);
+
+ if (!is_string($responseBody) || $responseBody === '') {
+ return [
+ 'ok' => false,
+ 'message' => 'Alpha Vantage Anfrage fehlgeschlagen.'
+ . ($curlError !== '' ? ' ' . $curlError : '')
+ . ($httpCode > 0 ? ' HTTP ' . $httpCode : ''),
+ ];
+ }
+ }
+ }
+
+ if (!is_string($responseBody) || $responseBody === '') {
+ $context = stream_context_create([
+ 'http' => [
+ 'method' => 'GET',
+ 'timeout' => $timeout,
+ 'header' => "Accept: application/json\r\n",
+ ],
+ ]);
+ $responseBody = @file_get_contents($url, false, $context);
+ if (!is_string($responseBody) || $responseBody === '') {
+ return [
+ 'ok' => false,
+ 'message' => 'Alpha Vantage Anfrage lieferte keine Daten.',
+ ];
+ }
+ }
+
+ $decoded = json_decode($responseBody, true);
+ if (!is_array($decoded)) {
+ return [
+ 'ok' => false,
+ 'message' => 'Alpha Vantage Antwort ist kein gueltiges JSON.',
+ ];
+ }
+
+ if (!empty($decoded['Note'])) {
+ return [
+ 'ok' => false,
+ 'message' => 'Alpha Vantage Limit-Hinweis: ' . trim((string) $decoded['Note']),
+ ];
+ }
+
+ if (!empty($decoded['Information'])) {
+ return [
+ 'ok' => false,
+ 'message' => trim((string) $decoded['Information']),
+ ];
+ }
+
+ if (!empty($decoded['Error Message'])) {
+ return [
+ 'ok' => false,
+ 'message' => trim((string) $decoded['Error Message']),
+ ];
+ }
+
+ $quote = is_array($decoded['Global Quote'] ?? null) ? $decoded['Global Quote'] : [];
+ $price = $quote['05. price'] ?? null;
+ if (!is_numeric($price)) {
+ return [
+ 'ok' => false,
+ 'message' => 'Alpha Vantage lieferte keinen Preis fuer das Symbol ' . $symbol . '.',
+ ];
+ }
+
+ return [
+ 'ok' => true,
+ 'symbol' => (string) ($quote['01. symbol'] ?? $symbol),
+ 'price' => (float) $price,
+ 'latest_trading_day' => (string) ($quote['07. latest trading day'] ?? ''),
+ 'previous_close' => is_numeric($quote['08. previous close'] ?? null) ? (float) $quote['08. previous close'] : null,
+ 'change' => is_numeric($quote['09. change'] ?? null) ? (float) $quote['09. change'] : null,
+ 'change_percent' => (string) ($quote['10. change percent'] ?? ''),
+ 'fetched_at' => date('Y-m-d H:i:s'),
+ 'source' => 'alpha_vantage:GLOBAL_QUOTE',
+ 'raw' => $quote,
+ ];
+});
diff --git a/modules/boersenchecker/module.json b/modules/boersenchecker/module.json
index c4eb0f8..74b7802 100644
--- a/modules/boersenchecker/module.json
+++ b/modules/boersenchecker/module.json
@@ -1,21 +1,44 @@
{
"title": "Börsenchecker",
- "version": "0.1.0",
- "description": "Grundgeruest fuer ein Nexus-Modul zur Beobachtung und Auswertung von Boersenwerten.",
+ "version": "0.2.0",
+ "description": "Depotverwaltung fuer Aktien, Kaufdaten, Kursverlauf und Waehrungsumrechnung.",
"enabled_by_default": false,
"menu": [
- { "label": "Übersicht", "href": "/module/boersenchecker" }
+ { "label": "Uebersicht", "href": "/module/boersenchecker" }
],
"sidebar": {
"enabled": true,
"collapsible": true,
"default": "collapsed",
"items": [
- { "label": "Übersicht", "href": "/module/boersenchecker" }
+ { "label": "Uebersicht", "href": "/module/boersenchecker" }
]
},
"setup": {
- "fields": []
+ "fields": [
+ { "name": "use_separate_db", "label": "Eigene Modul-DB nutzen", "type": "checkbox", "required": false, "help": "Wenn aktiv, werden die DB-Daten unten verwendet. Sonst wird die Nexus-Base-DB genutzt." },
+ { "name": "db.driver", "label": "DB Driver", "type": "text", "required": false, "help": "z.B. pgsql, mysql, sqlite" },
+ { "name": "db.host", "label": "DB Host", "type": "text", "required": false },
+ { "name": "db.port", "label": "DB Port", "type": "number", "required": false },
+ { "name": "db.dbname", "label": "DB Name", "type": "text", "required": false },
+ { "name": "db.schema", "label": "DB Schema", "type": "text", "required": false },
+ { "name": "db.user", "label": "DB User", "type": "text", "required": false },
+ { "name": "db.password", "label": "DB Passwort", "type": "password", "required": false },
+ { "name": "report_currency", "label": "Standard-Berichtswahrung", "type": "text", "required": false, "help": "Zielwaehrung fuer Portfolio-Summen, z.B. EUR." },
+ { "name": "fx_max_age_hours", "label": "Maximales FX-Alter (Stunden)", "type": "number", "required": false, "help": "Wird bei manueller Aktualisierung ueber den Mining-Checker genutzt." },
+ { "name": "alpha_vantage_api_key", "label": "Alpha Vantage API Key", "type": "password", "required": false, "help": "API Key fuer Aktienkursabrufe ueber GLOBAL_QUOTE." },
+ { "name": "alpha_vantage_timeout_sec", "label": "Alpha Vantage Timeout (Sek.)", "type": "number", "required": false, "help": "HTTP-Timeout fuer API-Abrufe." },
+ { "name": "alpha_vantage_min_interval_minutes", "label": "Alpha Vantage Mindestabstand (Min.)", "type": "number", "required": false, "help": "Wenn bereits ein frischer Alpha-Vantage-Kurs existiert, wird dieser wiederverwendet statt erneut abzurufen." }
+ ]
+ },
+ "db_defaults": {
+ "driver": "pgsql",
+ "host": "localhost",
+ "port": 5432,
+ "dbname": "",
+ "schema": "public",
+ "user": "",
+ "password": ""
},
"auth": {
"required": true,
diff --git a/modules/boersenchecker/pages/index.php b/modules/boersenchecker/pages/index.php
index f2c6bce..bee9e89 100644
--- a/modules/boersenchecker/pages/index.php
+++ b/modules/boersenchecker/pages/index.php
@@ -1,28 +1,7 @@
-
-
Börsenchecker
-
Börsenchecker
-
- Das Modul ist im Nexus registriert und kann jetzt schrittweise um Datenquellen,
- Watchlists, Kennzahlen und Benachrichtigungen erweitert werden.
-
-
- Status
-
- Aktuell ist dies das initiale Grundgeruest des Moduls.
-
-
+require_auth();
-
- Nächste sinnvolle Ausbaustufen
-
-
Watchlist fuer Ticker und ISINs
-
Kursdaten per API einlesen
-
Performance, Tagesdelta und historische Trends anzeigen
-
Alerts fuer Schwellwerte definieren
-
-
-
+$page = new \Modules\Boersenchecker\Support\DashboardPage();
+module_tpl('boersenchecker', 'dashboard', $page->handle());
diff --git a/modules/boersenchecker/partials/dashboard.php b/modules/boersenchecker/partials/dashboard.php
new file mode 100644
index 0000000..e92435f
--- /dev/null
+++ b/modules/boersenchecker/partials/dashboard.php
@@ -0,0 +1,388 @@
+
+
Boersenchecker
+
Depotverwaltung
+
+ Depots, Positionen und manuelle Kursverlaeufe. Die Waehrungsumrechnung nutzt, sofern verfuegbar,
+ die bestehende FX-Logik des Mining-Checkers weiter.
+
+ Die Umrechnung liest gespeicherte FX-Daten aus dem Mining-Checker. Eine Aktualisierung wird nur manuell
+ angestossen und respektiert die dortige Max-Age-Logik.
+
+
+ Aktienkurse werden ueber Alpha Vantage anhand des hinterlegten API-Symbols / Tickers pro Aktie abgerufen.
+
+
+
+
+
+
+ Alpha Vantage Mindestabstand: = e((string) $alphaMinIntervalMinutes) ?> Min.
+
+
+ API-Key und Timeout werden ueber Modul-Setup gepflegt.
+