upgrade
All checks were successful
Deploy / deploy-staging (push) Successful in 1m6s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-09-12 23:55:58 +02:00
parent d1d8e01fdc
commit 422331b32e
4 changed files with 29 additions and 10 deletions

View File

@@ -3401,14 +3401,14 @@
return h('div', { className: 'mc-stack' }, [
h('div', { className: 'mc-two-col' }, [
renderSharedOcrPanel(),
panel('Mining manuell erfassen', 'Direkte Eingabe eines einzelnen Mining-Messpunkts mit serverseitiger Validierung.', h('form', {
panel('Mining manuell erfassen', 'Direkte Eingabe eines einzelnen Mining-Messpunkts. Ein belegter historischer Zeitpunkt wird unveraendert gespeichert.', h('form', {
className: 'mc-form',
onSubmit: function (event) {
event.preventDefault();
submitMeasurement(false);
},
}, [
displayField('Zeitpunkt', 'Wird beim Speichern automatisch auf den aktuellen Bestätigungszeitpunkt gesetzt.'),
inputField('Zeitpunkt', 'datetime-local', measurementForm.measured_at, (value) => setMeasurementForm({ ...measurementForm, measured_at: value }), '1'),
inputField('Coins total', 'number', measurementForm.coins_total, (value) => setMeasurementForm({ ...measurementForm, coins_total: value }), '0.000001'),
inputField('Kurs', 'number', measurementForm.price_per_coin, (value) => setMeasurementForm({ ...measurementForm, price_per_coin: value }), '0.000001'),
selectField('Waehrung', measurementForm.price_currency, [''].concat(selectableCurrencies.map((currency) => currency.code)), (value) => setMeasurementForm({ ...measurementForm, price_currency: value })),

View File

@@ -112,7 +112,7 @@ Beispiel:
`currencyapi.net` wird ueber das Modul `fx-rates` abgefragt. Aus dem Response werden `base`, `rates` und `updated` uebernommen; `valid` muss `true` sein. Die eigentlichen Fetches und Raten liegen im Modul `fx-rates`.
Pro Abruf entsteht genau ein Datensatz in `fx-rates` mit Basiswaehrung, Provider und Stichtag. Neue Mining-Messpunkte pruefen beim Speichern, ob ein neuer FX-Fetch noetig ist; falls nicht, wird die letzte passende `fetch_id` wiederverwendet.
Pro Abruf entsteht genau ein Datensatz in `fx-rates` mit Basiswaehrung, Provider und Stichtag. Neue Mining-Messpunkte pruefen beim Speichern, ob ein neuer FX-Fetch noetig ist; falls nicht, wird nur ein Snapshot innerhalb des zulaessigen Zeitfensters wiederverwendet. Ein historischer Messpunkt darf niemals auf einen heutigen oder sonst zeitlich fernen Fetch verweisen.
Falls noch historische Mining-Checker-Fetches in `miningcheck_fx_fetches` und `miningcheck_fx_rates` liegen, kann `POST /api/mining-checker/v1/projects/{projectKey}/legacy-fx-migrate` diese nach `fx-rates` ueberfuehren. Danach werden bestehende Messpunkte soweit moeglich auf die passende `fx_fetch_id` aktualisiert.
@@ -160,6 +160,19 @@ Beim Schema-Upgrade werden vorhandene Krypto-Miner einmalig bewertet:
Jede neu gespeicherte Buchung, die einen Kurs oder eine Waehrungsumrechnung benoetigt, muss die verwendete `fx-rates.fetch_id` gemeinsam mit Betrag, Quellwaehrung und Zielwaehrung speichern. Ein aktueller Kurs darf ausschliesslich fuer variable, noch gehaltene Bestaende verwendet werden. Fehlt fuer eine neue feste Buchung ein FX-Snapshot, muss der Speichervorgang fehlschlagen; ein stiller Fallback ist nicht erlaubt.
### Historische Nachtraege
Ein belegter, nachtraeglich erfasster Mining-Stand verwendet seinen tatsaechlichen Zeitpunkt, nicht den Speicherzeitpunkt. Der manuelle Messpunkt besitzt deshalb ein optionales Feld `Zeitpunkt`; bleibt es leer, wird der aktuelle Zeitpunkt verwendet.
Fuer eine unvollstaendige Upload-Phase werden die belegbaren Ereignisse einzeln und chronologisch erfasst:
1. Krypto-Miete mit Zeitpunkt, tatsaechlichem Coin-Preis, Laufzeit, Hashrate inklusive Bonus und dem historischen FX-Snapshot.
2. Transfer aus dem Mining-Tool in das Wallet mit Zeitpunkt und Coin-Menge.
3. Wallet-Snapshot mit dem tatsaechlichen Wallet-Bestand; dieser ist ab seinem Zeitpunkt autoritativ.
4. Mining-Messpunkt mit Screenshot-Zeitpunkt, Miner-Bestand und dem im Screenshot angezeigten Kurs.
Fehlt ein exakter historischer FX-Snapshot, darf eine feste Krypto-Ausgabe nicht mit einem aktuellen Kurs gespeichert werden. Der fehlende historische Snapshot muss zuerst aus einer nachpruefbaren externen Quelle in `fx-rates` vorliegen; der Beleg oder Screenshot bleibt als Notiz am Nachtrag erhalten.
## Break-even
Der Gesamt-Break-even verwendet die Summe aus Fiat-Investitionen und `Reinvest fix` gegen aktuelle, noch gehaltene Miner- und Walletbestaende. Krypto-Reinvestitionen muessen immer den historischen festen Gegenwert aus dem zugeordneten `fx-rates`-Snapshot verwenden; eine Bewertung zum aktuellen Coin-Kurs ist unzulässig.

View File

@@ -1475,8 +1475,10 @@ final class Router
$projectTimezone = $this->projectTimezone($projectKey);
$source = $this->enumValue($input['source'] ?? 'manual', ['manual', 'image_ocr', 'seed_import'], 'source');
$payload = [
'measured_at' => $source === 'seed_import'
? $this->requiredDateTime($input['measured_at'] ?? null, 'measured_at', $projectTimezone)
// A manual entry may document an older, verifiable upload. Only omit the
// timestamp when the user intentionally records the current state.
'measured_at' => trim((string) ($input['measured_at'] ?? '')) !== ''
? $this->requiredDateTime($input['measured_at'], 'measured_at', $projectTimezone)
: $this->currentTimestamp(),
'coins_total' => $this->requiredDecimal($input['coins_total'] ?? null, 'coins_total'),
'coin_currency' => $this->measurementCoinCurrency($projectKey, $input),
@@ -2861,18 +2863,21 @@ final class Router
}
if ($measuredAt !== '') {
$nearest = $this->fx()->nearestSnapshot('USD', $measuredAt, null, null);
// Never attach an unrelated current quote to a historical financial event.
$nearest = $this->fx()->nearestSnapshot('USD', $measuredAt, null, (int) round($maxAgeHours * 60));
if (is_array($nearest) && is_numeric($nearest['id'] ?? null)) {
return (int) $nearest['id'];
}
}
if ($measuredAt === '' || $this->isRecentTimestamp($measuredAt, $maxAgeHours)) {
$latest = $this->fx()->latestSnapshot('USD', null);
if (is_array($latest) && is_numeric($latest['id'] ?? null)) {
return (int) $latest['id'];
}
}
if ($allowRefresh) {
if ($allowRefresh && ($measuredAt === '' || $this->isRecentTimestamp($measuredAt, $maxAgeHours))) {
$fresh = $this->fx()->refreshLatestRates(null, 'USD');
if (is_array($fresh) && is_numeric($fresh['fetch_id'] ?? null)) {
return (int) $fresh['fetch_id'];

View File

@@ -135,6 +135,7 @@ Aus [custom/apps/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berl
### Aktueller Modulstand
- `Mining-Checker` ist eine Desktop-App aus `custom/apps/mining-checker/`
- Historische Mining-Nachtraege verwenden ihren belegten Zeitpunkt; fuer feste Krypto-Buchungen ist ein zeitnaher, referenzierter `fx-rates`-Snapshot Pflicht und ein aktueller Kurs darf keinen historischen Snapshot ersetzen.
- `Waehrungs-Checker` ist eine Desktop-App aus `custom/apps/fx-rates/`
- die Desktop-Shell kann Moduldefinitionen zentral erkennen und als `App` bereitstellen
- wiederverwendbare Modul-Helfer liegen im globalen Kern, die Fachlogik bleibt im Modul