Fix historical crypto settlement calculations
This commit is contained in:
@@ -386,6 +386,42 @@ final class FxRatesRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function replaceFetchRates(int $fetchId, array $rates): void
|
||||||
|
{
|
||||||
|
if ($fetchId <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedRates = [];
|
||||||
|
foreach ($rates as $currencyCode => $rate) {
|
||||||
|
$currencyCode = strtoupper(trim((string) $currencyCode));
|
||||||
|
if ($currencyCode !== '' && is_numeric($rate) && (float) $rate > 0) {
|
||||||
|
$normalizedRates[$currencyCode] = (float) $rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pdo->prepare('DELETE FROM ' . $this->table('rates') . ' WHERE fetch_id = :fetch_id')
|
||||||
|
->execute(['fetch_id' => $fetchId]);
|
||||||
|
if ($normalizedRates === []) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = [];
|
||||||
|
$params = ['fetch_id' => $fetchId];
|
||||||
|
$index = 0;
|
||||||
|
foreach ($normalizedRates as $currencyCode => $rate) {
|
||||||
|
$codeKey = 'currency_code_' . $index;
|
||||||
|
$valueKey = 'current_value_' . $index;
|
||||||
|
$placeholders[] = "(:fetch_id, :{$codeKey}, :{$valueKey})";
|
||||||
|
$params[$codeKey] = $currencyCode;
|
||||||
|
$params[$valueKey] = $rate;
|
||||||
|
$index++;
|
||||||
|
}
|
||||||
|
$this->pdo->prepare(
|
||||||
|
'INSERT INTO ' . $this->table('rates') . ' (fetch_id, currency_code, current_value) VALUES ' . implode(', ', $placeholders)
|
||||||
|
)->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
public function findFetchByBaseAndFetchedAt(string $baseCurrency, string $fetchedAt): ?array
|
public function findFetchByBaseAndFetchedAt(string $baseCurrency, string $fetchedAt): ?array
|
||||||
{
|
{
|
||||||
$baseCurrency = strtoupper(trim($baseCurrency));
|
$baseCurrency = strtoupper(trim($baseCurrency));
|
||||||
|
|||||||
@@ -2319,6 +2319,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function repairHistoricalRecoveryFx() {
|
||||||
|
setSaving(true);
|
||||||
|
setError('');
|
||||||
|
setMessage('');
|
||||||
|
try {
|
||||||
|
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/historical-recovery-fx`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
measured_at: recoveryForm.miner_at,
|
||||||
|
doge_usd: recoveryForm.doge_usd,
|
||||||
|
usd_eur: recoveryForm.usd_eur,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
await loadBootstrap(projectKey);
|
||||||
|
setMessage('Historischer FX-Snapshot und zugehoeriger Miner wurden korrigiert.');
|
||||||
|
} catch (err) {
|
||||||
|
setError(`Historischer FX-Snapshot konnte nicht korrigiert werden: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function submitWalletSnapshotFromPreview(previewOverride) {
|
async function submitWalletSnapshotFromPreview(previewOverride) {
|
||||||
const preview = normalizeOcrPreview(previewOverride || ocrPreview);
|
const preview = normalizeOcrPreview(previewOverride || ocrPreview);
|
||||||
const raw = {
|
const raw = {
|
||||||
@@ -3334,7 +3357,6 @@
|
|||||||
value: earnedValue !== null ? fmtMoney(earnedValue, reportCurrency) : 'n/a',
|
value: earnedValue !== null ? fmtMoney(earnedValue, reportCurrency) : 'n/a',
|
||||||
sub: [
|
sub: [
|
||||||
totalHoldingsValue !== null ? `Variabel ${fmtMoney(totalHoldingsValue, reportCurrency)}` : null,
|
totalHoldingsValue !== null ? `Variabel ${fmtMoney(totalHoldingsValue, reportCurrency)}` : null,
|
||||||
settledCryptoSpendValue !== null ? `Fix ${fmtMoney(settledCryptoSpendValue, reportCurrency)}` : null,
|
|
||||||
currentSettings?.baseline_measured_at ? `Seit Baseline ${fmtDate(currentSettings.baseline_measured_at)}` : 'Benötigt Baseline',
|
currentSettings?.baseline_measured_at ? `Seit Baseline ${fmtDate(currentSettings.baseline_measured_at)}` : 'Benötigt Baseline',
|
||||||
].filter(Boolean).join(' · '),
|
].filter(Boolean).join(' · '),
|
||||||
}),
|
}),
|
||||||
@@ -3487,6 +3509,12 @@
|
|||||||
className: 'mc-button mc-button--primary',
|
className: 'mc-button mc-button--primary',
|
||||||
disabled: saving,
|
disabled: saving,
|
||||||
}, saving ? 'Speichert …' : 'Alle Daten nachtragen'),
|
}, saving ? 'Speichert …' : 'Alle Daten nachtragen'),
|
||||||
|
h('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'mc-button mc-button--ghost',
|
||||||
|
onClick: repairHistoricalRecoveryFx,
|
||||||
|
disabled: saving,
|
||||||
|
}, 'Nur historischen FX/Miner korrigieren'),
|
||||||
])),
|
])),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ Diese Regeln sind bei jeder Erweiterung der Wallet-, Miner- oder Uebersichtslogi
|
|||||||
4. Wird ein Miner mit der Kryptowaehrung bezahlt, reduziert der Kauf den Walletbestand um den gezahlten Coin-Betrag. Gleichzeitig wird dessen Gegenwert zum Kaufzeitpunkt als feste Buchung gespeichert. Dieser Betrag darf durch spaetere Kursaenderungen nicht mehr beeinflusst werden.
|
4. Wird ein Miner mit der Kryptowaehrung bezahlt, reduziert der Kauf den Walletbestand um den gezahlten Coin-Betrag. Gleichzeitig wird dessen Gegenwert zum Kaufzeitpunkt als feste Buchung gespeichert. Dieser Betrag darf durch spaetere Kursaenderungen nicht mehr beeinflusst werden.
|
||||||
5. Ein Wallet-Screenshot ist fuer seinen Zeitstempel autoritativ. Sein erkannter Hauptbestand ueberschreibt die rechnerische Wallet-Historie bis zu diesem Zeitpunkt; erst danach werden weitere Transfers, Krypto-Minerkaeufe und externe Wallet-Auszahlungen verrechnet.
|
5. Ein Wallet-Screenshot ist fuer seinen Zeitstempel autoritativ. Sein erkannter Hauptbestand ueberschreibt die rechnerische Wallet-Historie bis zu diesem Zeitpunkt; erst danach werden weitere Transfers, Krypto-Minerkaeufe und externe Wallet-Auszahlungen verrechnet.
|
||||||
6. Jede erfasste Auszahlung aus dem Mining-Tool muss den Walletbestand automatisch fortschreiben. Externe Auszahlungen aus dem hier verfolgten Wallet reduzieren ihn.
|
6. Jede erfasste Auszahlung aus dem Mining-Tool muss den Walletbestand automatisch fortschreiben. Externe Auszahlungen aus dem hier verfolgten Wallet reduzieren ihn.
|
||||||
7. Der angezeigte bisherige Wert trennt variable Werte (Coins im Mining-Tool und Wallet zum aktuellen Kurs) von fixen Werten (bereits mit Krypto bezahlte Miner zum Ausgabezeitpunkt). Die Gesamtsumme besteht aus beiden Kategorien.
|
7. `Bisher verdient gesamt` zeigt ausschliesslich variable Werte: Coins im Mining-Tool und Wallet zum aktuellen Kurs. Bereits mit Krypto bezahlte Miner sind Ausgaben und duerfen dort niemals als fixer Ertrag erscheinen.
|
||||||
8. Jede neue feste Krypto-Buchung referenziert die `fetch_id` des Moduls `fx-rates`. Der Mining-Checker fuehrt keine eigene Kurstabelle. Umrechnungen in andere Berichtswährungen verwenden fuer fixe Werte denselben historischen API-Snapshot.
|
8. Jede neue feste Krypto-Buchung referenziert die `fetch_id` des Moduls `fx-rates`. Der Mining-Checker fuehrt keine eigene Kurstabelle. Umrechnungen in andere Berichtswährungen verwenden fuer fixe Werte denselben historischen API-Snapshot.
|
||||||
9. Wird ein gemieteter Miner geloescht, wird sein kompletter Datensatz entfernt: Mietkosten, feste Krypto-Bewertung, FX-Referenz und der daraus abgeleitete Wallet-Abzug entfallen. Der Miner beeinflusst danach weder Walletbestand noch Ausgaben, Reinvest, Hashrate oder Break-even. Basis-Angebote, Mining-Uploads und andere Wallet-Buchungen bleiben unveraendert.
|
9. Wird ein gemieteter Miner geloescht, wird sein kompletter Datensatz entfernt: Mietkosten, feste Krypto-Bewertung, FX-Referenz und der daraus abgeleitete Wallet-Abzug entfallen. Der Miner beeinflusst danach weder Walletbestand noch Ausgaben, Reinvest, Hashrate oder Break-even. Basis-Angebote, Mining-Uploads und andere Wallet-Buchungen bleiben unveraendert.
|
||||||
10. `Kosten/kH/s/Tag` ist ein laufzeitbereinigter Vergleichswert: feste historische Gesamtkosten geteilt durch die gesamte Hashrate einschliesslich Bonus und die exakten Kalendertage zwischen Mietzeitpunkt und Laufzeitende. Das Laufzeitende entsteht durch das Addieren der gebuchten Kalendermonate; bei kuerzeren Zielmonaten wird auf deren letzten Kalendertag begrenzt. Fuer Krypto-Mieten sind das `settled_value_amount / (Basis-Hashrate + Bonus-Hashrate) / Laufzeittage` in `settled_value_currency`; der aktuelle Coin-Kurs und ein Angebots-Referenzpreis duerfen diese Kennzahl nicht beeinflussen. Fuer FIAT-Mieten gilt derselbe Quotient mit dem tatsaechlich gezahlten FIAT-Betrag und dessen Zahlungswaehrung.
|
10. `Kosten/kH/s/Tag` ist ein laufzeitbereinigter Vergleichswert: feste historische Gesamtkosten geteilt durch die gesamte Hashrate einschliesslich Bonus und die exakten Kalendertage zwischen Mietzeitpunkt und Laufzeitende. Das Laufzeitende entsteht durch das Addieren der gebuchten Kalendermonate; bei kuerzeren Zielmonaten wird auf deren letzten Kalendertag begrenzt. Fuer Krypto-Mieten sind das `settled_value_amount / (Basis-Hashrate + Bonus-Hashrate) / Laufzeittage` in `settled_value_currency`; der aktuelle Coin-Kurs und ein Angebots-Referenzpreis duerfen diese Kennzahl nicht beeinflussen. Fuer FIAT-Mieten gilt derselbe Quotient mit dem tatsaechlich gezahlten FIAT-Betrag und dessen Zahlungswaehrung.
|
||||||
@@ -175,6 +175,8 @@ Fehlt ein exakter historischer FX-Snapshot, darf eine feste Krypto-Ausgabe nicht
|
|||||||
|
|
||||||
Fuer zusammenhaengende, belegbare Ausfaelle steht der Bereich `Nachtragen` bereit. Er legt zuerst einen historischen `fx-rates`-Snapshot an und speichert danach in fester Reihenfolge Krypto-Miete, Mining-Tool-Transfer, Wallet-Snapshot und Mining-Messpunkt. Wiederholte Ausfuehrung des Assistenten verwendet den FX-Snapshot mit identischem Zeitpunkt erneut. Die fachlichen Buchungen selbst duerfen nach einer erfolgreichen Ausfuehrung nicht erneut gestartet werden, damit keine doppelten Miner oder Transfers entstehen.
|
Fuer zusammenhaengende, belegbare Ausfaelle steht der Bereich `Nachtragen` bereit. Er legt zuerst einen historischen `fx-rates`-Snapshot an und speichert danach in fester Reihenfolge Krypto-Miete, Mining-Tool-Transfer, Wallet-Snapshot und Mining-Messpunkt. Wiederholte Ausfuehrung des Assistenten verwendet den FX-Snapshot mit identischem Zeitpunkt erneut. Die fachlichen Buchungen selbst duerfen nach einer erfolgreichen Ausfuehrung nicht erneut gestartet werden, damit keine doppelten Miner oder Transfers entstehen.
|
||||||
|
|
||||||
|
Kursnotierung und Speicherung werden getrennt behandelt: Die Eingabe `DOGE/USD` ist USD pro DOGE. Ein `fx-rates`-Snapshot mit Basis USD speichert dagegen DOGE pro USD, also den Kehrwert. Der Nachtrag-Assistent erledigt diese Umrechnung vor dem Speichern. `Nur historischen FX/Miner korrigieren` repariert ausschliesslich diesen Snapshot und die feste Bewertung eines bereits angelegten passenden Miners; Transfers und Messpunkte werden dabei nicht erneut angelegt.
|
||||||
|
|
||||||
## Break-even
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -854,11 +854,16 @@ final class Router
|
|||||||
$measuredAt = $this->requiredDateTime($input['measured_at'] ?? null, 'measured_at', $this->projectTimezone($projectKey));
|
$measuredAt = $this->requiredDateTime($input['measured_at'] ?? null, 'measured_at', $this->projectTimezone($projectKey));
|
||||||
$dogeUsd = $this->requiredDecimal($input['doge_usd'] ?? null, 'doge_usd');
|
$dogeUsd = $this->requiredDecimal($input['doge_usd'] ?? null, 'doge_usd');
|
||||||
$usdEur = $this->requiredDecimal($input['usd_eur'] ?? null, 'usd_eur');
|
$usdEur = $this->requiredDecimal($input['usd_eur'] ?? null, 'usd_eur');
|
||||||
|
// fx-rates stores target units per base unit. DOGE/USD is supplied as USD per DOGE.
|
||||||
|
$rates = ['DOGE' => 1 / $dogeUsd, 'EUR' => $usdEur];
|
||||||
$existing = $fxRepository->findFetchByBaseAndFetchedAt('USD', $measuredAt);
|
$existing = $fxRepository->findFetchByBaseAndFetchedAt('USD', $measuredAt);
|
||||||
|
|
||||||
if (is_array($existing) && is_numeric($existing['id'] ?? null)) {
|
if (is_array($existing) && is_numeric($existing['id'] ?? null)) {
|
||||||
|
$fetchId = (int) $existing['id'];
|
||||||
|
$fxRepository->replaceFetchRates($fetchId, $rates);
|
||||||
|
$this->repairHistoricalRecoveryMinerSettlement($projectKey, $measuredAt, $dogeUsd, $usdEur, $fetchId);
|
||||||
return [
|
return [
|
||||||
'fetch_id' => (int) $existing['id'],
|
'fetch_id' => $fetchId,
|
||||||
'reused' => true,
|
'reused' => true,
|
||||||
'fetched_at' => $measuredAt,
|
'fetched_at' => $measuredAt,
|
||||||
];
|
];
|
||||||
@@ -868,19 +873,39 @@ final class Router
|
|||||||
'USD',
|
'USD',
|
||||||
'coingecko+valutafx',
|
'coingecko+valutafx',
|
||||||
substr($measuredAt, 0, 10),
|
substr($measuredAt, 0, 10),
|
||||||
['DOGE' => $dogeUsd, 'EUR' => $usdEur],
|
$rates,
|
||||||
$measuredAt,
|
$measuredAt,
|
||||||
'migration'
|
'migration'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$fetchId = is_numeric($saved['fetch']['id'] ?? null) ? (int) $saved['fetch']['id'] : null;
|
||||||
|
if ($fetchId !== null) {
|
||||||
|
$this->repairHistoricalRecoveryMinerSettlement($projectKey, $measuredAt, $dogeUsd, $usdEur, $fetchId);
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'fetch_id' => is_numeric($saved['fetch']['id'] ?? null) ? (int) $saved['fetch']['id'] : null,
|
'fetch_id' => $fetchId,
|
||||||
'reused' => false,
|
'reused' => false,
|
||||||
'fetched_at' => $measuredAt,
|
'fetched_at' => $measuredAt,
|
||||||
'provider' => 'coingecko+valutafx',
|
'provider' => 'coingecko+valutafx',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function repairHistoricalRecoveryMinerSettlement(string $projectKey, string $purchasedAt, float $dogeUsd, float $usdEur, int $fxFetchId): void
|
||||||
|
{
|
||||||
|
foreach ($this->repository()->listPurchasedMiners($projectKey) as $miner) {
|
||||||
|
if ((string) ($miner['purchased_at'] ?? '') !== $purchasedAt || strtoupper((string) ($miner['currency'] ?? '')) !== 'DOGE') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$spent = is_numeric($miner['total_cost_amount'] ?? null) ? (float) $miner['total_cost_amount'] : null;
|
||||||
|
$minerId = is_numeric($miner['id'] ?? null) ? (int) $miner['id'] : 0;
|
||||||
|
if ($spent === null || $spent <= 0 || $minerId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$this->repository()->updatePurchasedMinerSettlement($projectKey, $minerId, $spent * $dogeUsd * $usdEur, 'EUR', $fxFetchId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function offerBasisHistory(string $projectKey): array
|
private function offerBasisHistory(string $projectKey): array
|
||||||
{
|
{
|
||||||
return $this->repository()->listOfferBasisHistory($projectKey, 50);
|
return $this->repository()->listOfferBasisHistory($projectKey, 50);
|
||||||
|
|||||||
@@ -461,15 +461,11 @@ final class AnalyticsService
|
|||||||
// Reinvestments are spent coins, not a current crypto holding. Their historical
|
// Reinvestments are spent coins, not a current crypto holding. Their historical
|
||||||
// settlement value is therefore the only valid break-even display basis.
|
// settlement value is therefore the only valid break-even display basis.
|
||||||
$reinvestedCapital = $settledCryptoSpendValue;
|
$reinvestedCapital = $settledCryptoSpendValue;
|
||||||
$settledCryptoSpendCurrentAsset = $latestAsset !== ''
|
// Income is only the current value of coins still held in the miner or wallet.
|
||||||
? $this->settledCryptoSpendValue($purchasedMiners, $latestAsset, $latest)
|
// A crypto-paid miner is an expense and must never be added as fixed income.
|
||||||
: null;
|
$settledCryptoSpendCurrentAsset = null;
|
||||||
$earnedCurrentAsset = $settledCryptoSpendCurrentAsset !== null
|
$earnedCurrentAsset = $holdingsCurrentAsset;
|
||||||
? $holdingsCurrentAsset + $settledCryptoSpendCurrentAsset
|
$earnedValue = $totalHoldingsValue;
|
||||||
: $holdingsCurrentAsset;
|
|
||||||
$earnedValue = ($totalHoldingsValue !== null || $settledCryptoSpendValue !== null)
|
|
||||||
? (float) ($totalHoldingsValue ?? 0.0) + (float) ($settledCryptoSpendValue ?? 0.0)
|
|
||||||
: null;
|
|
||||||
$currentDailyRevenue = is_numeric($latest['theoretical_daily_revenue'] ?? null) ? (float) $latest['theoretical_daily_revenue'] : null;
|
$currentDailyRevenue = is_numeric($latest['theoretical_daily_revenue'] ?? null) ? (float) $latest['theoretical_daily_revenue'] : null;
|
||||||
$totalInvestedCapital = ($cashInvestedCapital !== null || $reinvestedCapital !== null)
|
$totalInvestedCapital = ($cashInvestedCapital !== null || $reinvestedCapital !== null)
|
||||||
? (float) ($cashInvestedCapital ?? 0.0) + (float) ($reinvestedCapital ?? 0.0)
|
? (float) ($cashInvestedCapital ?? 0.0) + (float) ($reinvestedCapital ?? 0.0)
|
||||||
|
|||||||
@@ -1144,6 +1144,25 @@ final class MiningRepository
|
|||||||
return $this->normalizeRow($fetch->fetch() ?: []);
|
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatePurchasedMinerSettlement(string $projectKey, int $minerId, float $amount, string $currency, int $fxFetchId): ?array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'UPDATE ' . $this->table('purchased_miners') . '
|
||||||
|
SET settled_value_amount = :amount, settled_value_currency = :currency, settled_fx_fetch_id = :fx_fetch_id
|
||||||
|
WHERE project_key = :project_key AND owner_sub = :owner_sub AND id = :id'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'amount' => $amount,
|
||||||
|
'currency' => strtoupper(trim($currency)),
|
||||||
|
'fx_fetch_id' => $fxFetchId,
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'id' => $minerId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->getPurchasedMiner($projectKey, $minerId);
|
||||||
|
}
|
||||||
|
|
||||||
public function deletePurchasedMiner(string $projectKey, int $minerId): void
|
public function deletePurchasedMiner(string $projectKey, int $minerId): void
|
||||||
{
|
{
|
||||||
if ($minerId <= 0) {
|
if ($minerId <= 0) {
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ Aus [custom/apps/README.md](/home/lars/Schreibtisch/Projekte/desktop.kusche.berl
|
|||||||
- `Mining-Checker` ist eine Desktop-App aus `custom/apps/mining-checker/`
|
- `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.
|
- 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.
|
||||||
- Der Bereich `Nachtragen` im Mining-Checker speichert zusammenhaengende historische Miner-, Transfer-, Wallet- und Mining-Daten in chronologischer Reihenfolge und legt den belegten FX-Snapshot davor an.
|
- Der Bereich `Nachtragen` im Mining-Checker speichert zusammenhaengende historische Miner-, Transfer-, Wallet- und Mining-Daten in chronologischer Reihenfolge und legt den belegten FX-Snapshot davor an.
|
||||||
|
- `Bisher verdient` besteht ausschliesslich aus variablen Coin-Bestaenden; fixe Krypto-Mieten sind historische Ausgaben und gehoeren nicht in den Ertrag.
|
||||||
- `Waehrungs-Checker` ist eine Desktop-App aus `custom/apps/fx-rates/`
|
- `Waehrungs-Checker` ist eine Desktop-App aus `custom/apps/fx-rates/`
|
||||||
- die Desktop-Shell kann Moduldefinitionen zentral erkennen und als `App` bereitstellen
|
- die Desktop-Shell kann Moduldefinitionen zentral erkennen und als `App` bereitstellen
|
||||||
- wiederverwendbare Modul-Helfer liegen im globalen Kern, die Fachlogik bleibt im Modul
|
- wiederverwendbare Modul-Helfer liegen im globalen Kern, die Fachlogik bleibt im Modul
|
||||||
|
|||||||
Reference in New Issue
Block a user