calculation
All checks were successful
Deploy / deploy-staging (push) Successful in 33s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-08-21 00:46:29 +02:00
parent 62fb88c147
commit 0a19621748
8 changed files with 175 additions and 30 deletions

View File

@@ -1088,6 +1088,7 @@
const value = String(getCookie('mining_checker_report_currency') || '').toUpperCase();
return /^[A-Z0-9]{3,10}$/.test(value) ? value : '';
});
const [nowTimestamp, setNowTimestamp] = useState(() => Date.now());
const [targetForm, setTargetForm] = useState({
label: '',
target_amount_fiat: '',
@@ -1359,17 +1360,24 @@
const preferredOfferRequiredFiatValue = Number.isFinite(preferredOfferRequiredCoins)
? convertCurrencyValue(preferredOfferRequiredCoins, currentMiningCurrency, reportCurrency)
: null;
const preferredOfferRemainingDays = preferredOfferRemainingCoins !== null
const preferredOfferRemainingDaysAtUpload = preferredOfferRemainingCoins !== null
&& Number(latest?.doge_per_day_interval) > 0
? (preferredOfferRemainingCoins / Number(latest.doge_per_day_interval))
: null;
const preferredOfferEta = preferredOfferRemainingDays !== null
? (
preferredOfferRemainingDays <= 0
? new Date().toISOString()
: new Date(Date.now() + (preferredOfferRemainingDays * 86400000)).toISOString()
)
const preferredOfferUploadTimestamp = latest?.measured_at ? Date.parse(latest.measured_at) : NaN;
const preferredOfferEtaTimestamp = preferredOfferRemainingDaysAtUpload !== null && Number.isFinite(preferredOfferUploadTimestamp)
? preferredOfferUploadTimestamp + (preferredOfferRemainingDaysAtUpload * 86400000)
: NaN;
const preferredOfferRemainingDays = Number.isFinite(preferredOfferEtaTimestamp)
? Math.max(0, (preferredOfferEtaTimestamp - nowTimestamp) / 86400000)
: null;
const preferredOfferEta = Number.isFinite(preferredOfferEtaTimestamp)
? new Date(preferredOfferEtaTimestamp).toISOString()
: null;
const remainingDaysUntil = (timestamp) => {
const etaTimestamp = timestamp ? new Date(timestamp).getTime() : NaN;
return Number.isFinite(etaTimestamp) ? Math.max(0, (etaTimestamp - nowTimestamp) / 86400000) : null;
};
const speedUnits = ['kH/s', 'MH/s'];
const fiatCurrencies = currencies.filter((currency) => !currency.is_crypto);
const cryptoCurrencies = currencies.filter((currency) => !!currency.is_crypto);
@@ -1520,6 +1528,11 @@
}
}, [reportCurrencyOverride]);
useEffect(() => {
const intervalId = window.setInterval(() => setNowTimestamp(Date.now()), 60000);
return () => window.clearInterval(intervalId);
}, []);
useEffect(() => {
if (selectedMinerScenarioId === null) {
return;
@@ -3176,6 +3189,8 @@
const reinvestedCapital = latest ? convertMeasurementMoney(latest, latest.reinvested_capital, reportCurrency) : null;
const walletValue = latest ? convertMeasurementMoney(latest, latest.wallet_value, reportCurrency) : null;
const totalHoldingsValue = latest ? convertMeasurementMoney(latest, latest.total_holdings_value, reportCurrency) : null;
const earnedValue = latest ? convertMeasurementMoney(latest, latest.earned_value, reportCurrency) : null;
const settledCryptoSpendValue = latest ? convertMeasurementMoney(latest, latest.settled_crypto_spend_value, reportCurrency) : null;
const totalEarnedOverall = latest && latest.growth_since_baseline !== null && latest.growth_since_baseline !== undefined
? Number(latest.growth_since_baseline)
: null;
@@ -3298,14 +3313,15 @@
}),
h(StatCard, {
key: 'value',
label: 'Aktueller Gegenwert',
value: totalHoldingsValue !== null ? fmtMoney(totalHoldingsValue, reportCurrency) : 'n/a',
label: 'Bisheriger Wert',
value: earnedValue !== null ? fmtMoney(earnedValue, reportCurrency) : 'n/a',
sub: [
holdingsCurrentAsset !== null && holdingsCurrentAsset !== undefined
? `Gesamtbestand ${fmtNumber(holdingsCurrentAsset, 6)} ${currentCoinCurrency}`
: null,
latestValue !== null ? `Miner ${fmtMoney(latestValue, reportCurrency)}` : null,
walletValue !== null ? `Wallet ${fmtMoney(walletValue, reportCurrency)}` : null,
settledCryptoSpendValue !== null ? `Ausgegeben ${fmtMoney(settledCryptoSpendValue, reportCurrency)}` : null,
latestPrice !== null
? `Kurs ${fmtNumber(latestPrice, 6)} ${reportCurrency}${latest && latest.price_is_fallback ? ' · Fallback aus letztem Kurs' : ''}`
: null,
@@ -3327,6 +3343,9 @@
preferredOfferRemainingCoins !== null
? `Rest ${fmtNumber(preferredOfferRemainingCoins, 4)} ${currentMiningCurrency}`
: null,
latest?.doge_per_day_interval !== null && latest?.doge_per_day_interval !== undefined
? `Basis ${fmtNumber(latest.doge_per_day_interval, 4)} ${currentMiningCurrency}/Tag ab Upload`
: null,
preferredOfferEta ? `ETA ${fmtDate(preferredOfferEta)}` : null,
].filter(Boolean).join(' · '),
}),
@@ -3368,7 +3387,7 @@
yLabel: `Kurs ${reportCurrency}`,
})),
]),
panel('Zielmonitor', `Rest-${currentCoinCurrency} und Resttage werden gegen den letzten verfuegbaren Kurs je Zielwaehrung berechnet.`,
panel('Zielmonitor', `Die ETA wird ab dem letzten Upload mit dessen durchschnittlicher Tagesrate berechnet; die Restzeit laeuft bis zum naechsten Upload weiter.`,
h('div', { className: 'mc-target-grid' },
currentTargets.map((target, index) => h('div', {
key: index,
@@ -3383,7 +3402,7 @@
h('div', { key: 'price' }, `Letzter Kurs: ${target.latest_price_for_currency ? fmtNumber(target.latest_price_for_currency, 6) + ' ' + target.currency : 'n/a'}`),
h('div', { key: 'doge' }, `Benoetigte ${currentCoinCurrency}: ${fmtNumber(target.required_doge, 6)}`),
h('div', { key: 'remaining' }, `Rest-${currentCoinCurrency}: ${fmtNumber(target.remaining_doge, 6)}`),
h('div', { key: 'days' }, `Resttage: ${fmtNumber(target.remaining_days, 4)}`),
h('div', { key: 'days' }, `Resttage: ${fmtNumber(remainingDaysUntil(target.target_eta_at), 4)}`),
]),
]))
)
@@ -3529,7 +3548,7 @@
]);
}))
: h('div', { className: 'mc-empty' }, 'Noch keine Wallet-Assets erkannt.')),
panel('Wallet-Transfers', 'Transfers zum hier verfolgten Wallet erhoehen den Wallet-Bestand und reduzieren den sichtbaren Miner-Bestand. Reinvestierte Krypto-Minerkaeufe werden davon automatisch wieder abgezogen.', [
panel('Wallet-Transfers', 'Auszahlungen aus dem Mining-Tool erhoehen den Wallet-Bestand automatisch. Ein Screenshot setzt den Bestand zum Screenshot-Zeitpunkt verbindlich. Krypto-Minerkaeufe ziehen Coins wieder ab und bleiben zum Kaufkurs festgeschrieben.', [
h('div', { key: 'actions', className: 'mc-inline-row' }, [
h('button', {
key: 'transfer-all',
@@ -4065,7 +4084,7 @@
h('div', { key: 'currency-main' }, target.effective_currency || target.currency),
target.linked_offer_label ? h('div', { key: 'currency-offer', className: 'mc-kicker' }, `Angebot ${target.linked_offer_label}`) : null,
]),
h('td', { key: 'days' }, target.remaining_days !== null && target.remaining_days !== undefined ? fmtNumber(target.remaining_days, 2) : 'n/a'),
h('td', { key: 'days' }, remainingDaysUntil(target.target_eta_at) !== null ? fmtNumber(remainingDaysUntil(target.target_eta_at), 2) : 'n/a'),
h('td', { key: 'eta' }, target.target_eta_at ? fmtDateTime(target.target_eta_at) : 'n/a'),
h('td', { key: 'sort' }, String(target.sort_order ?? 0)),
h('td', { key: 'active' }, target.is_active ? 'ja' : 'nein'),

View File

@@ -222,6 +222,8 @@ CREATE TABLE IF NOT EXISTS miningcheck_purchased_miners (
usd_reference_amount DECIMAL(20,8) NULL,
reference_price_amount DECIMAL(20,8) NULL,
reference_price_currency VARCHAR(10) NULL,
settled_value_amount DECIMAL(20,8) NULL,
settled_value_currency VARCHAR(10) NULL,
daily_cost_amount DECIMAL(20,10) NULL,
daily_cost_currency VARCHAR(10) NULL,
auto_renew TINYINT(1) NOT NULL DEFAULT 0,

View File

@@ -239,6 +239,8 @@ CREATE TABLE IF NOT EXISTS miningcheck_purchased_miners (
usd_reference_amount NUMERIC(20,8),
reference_price_amount NUMERIC(20,8),
reference_price_currency VARCHAR(10),
settled_value_amount NUMERIC(20,8),
settled_value_currency VARCHAR(10),
daily_cost_amount NUMERIC(20,10),
daily_cost_currency VARCHAR(10),
auto_renew BOOLEAN NOT NULL DEFAULT FALSE,

View File

@@ -222,6 +222,8 @@ CREATE TABLE IF NOT EXISTS miningcheck_purchased_miners (
usd_reference_amount DECIMAL(20,8) NULL,
reference_price_amount DECIMAL(20,8) NULL,
reference_price_currency VARCHAR(10) NULL,
settled_value_amount DECIMAL(20,8) NULL,
settled_value_currency VARCHAR(10) NULL,
daily_cost_amount DECIMAL(20,10) NULL,
daily_cost_currency VARCHAR(10) NULL,
auto_renew TINYINT(1) NOT NULL DEFAULT 0,

View File

@@ -2092,8 +2092,10 @@ final class Router
$resolvedBonusUnit = $resolvedBonusValue !== null ? $resolvedSpeedUnit : null;
$purchaseCurrency = $this->optionalCurrency($input['currency'] ?? null) ?? (string) ($offer['effective_price_currency'] ?? $offer['price_currency'] ?? $offer['base_price_currency'] ?? '');
if ($paymentType === 'crypto' || !$isAutoRenew) {
if ($paymentType === 'crypto') {
$purchaseCurrency = $cryptoCurrency;
} elseif ($purchaseCurrency === '' || $this->isCryptoCurrencyCode($purchaseCurrency)) {
$purchaseCurrency = $this->requiredCurrency($settings['report_currency'] ?? 'EUR', 'report_currency');
}
$purchaseCost = $this->optionalDecimal($input['total_cost_amount'] ?? null);
if ($purchaseCost === null) {
@@ -2112,6 +2114,21 @@ final class Router
$purchasedAt = array_key_exists('purchased_at', $input)
? $this->requiredDateTime($input['purchased_at'], 'purchased_at', $this->projectTimezone($projectKey))
: $this->currentTimestamp();
$settledValueAmount = null;
$settledValueCurrency = null;
if ($this->isCryptoCurrencyCode($purchaseCurrency)) {
$settledValueCurrency = $this->requiredCurrency($settings['report_currency'] ?? 'EUR', 'report_currency');
$settledValueAmount = $purchaseCurrency === $settledValueCurrency
? $purchaseCost
: $this->convertMiningAssetAmount((float) $purchaseCost, $purchaseCurrency, $settledValueCurrency, $purchasedAt);
if ($settledValueAmount === null) {
throw new ApiException(
'Der aktuelle Kurs fuer die Krypto-Zahlung konnte nicht ermittelt werden. Der Miner wird nicht ohne feste Bewertung gespeichert.',
422,
['currency' => $purchaseCurrency, 'target_currency' => $settledValueCurrency]
);
}
}
return $this->repository()->purchaseMiner($projectKey, null, [
'purchased_at' => $purchasedAt,
@@ -2126,6 +2143,8 @@ final class Router
'usd_reference_amount' => $offer['usd_reference_amount'] ?? null,
'reference_price_amount' => $referencePriceAmount,
'reference_price_currency' => $referencePriceCurrency,
'settled_value_amount' => $settledValueAmount,
'settled_value_currency' => $settledValueCurrency,
'auto_renew' => $isAutoRenew ? 1 : 0,
'note' => $this->optionalString($input['note'] ?? ($offer['note'] ?? null), 1000),
'is_active' => 1,

View File

@@ -355,26 +355,32 @@ final class AnalyticsService
$price = $latestPriceByCurrency[$currency] ?? $this->convertLatestPrice($latestPriceByCurrency, $currency, $latest);
$requiredDoge = ($price && $targetAmount !== null) ? $targetAmount / $price : null;
$remainingDoge = $requiredDoge !== null ? $requiredDoge - (float) ($latest['coins_total_effective'] ?? $latest['coins_total']) : null;
$remainingDays = (
$remainingDoge !== null &&
$remainingDogeAtUpload = $requiredDoge !== null ? $requiredDoge - (float) ($latest['coins_total_effective'] ?? $latest['coins_total']) : null;
$remainingDaysAtUpload = (
$remainingDogeAtUpload !== null &&
$latest['doge_per_day_interval'] !== null &&
(float) $latest['doge_per_day_interval'] > 0
) ? $remainingDoge / (float) $latest['doge_per_day_interval'] : null;
) ? $remainingDogeAtUpload / (float) $latest['doge_per_day_interval'] : null;
$targetEtaAt = null;
if ($remainingDays !== null) {
if ($remainingDays <= 0) {
if ($remainingDaysAtUpload !== null) {
if ($remainingDaysAtUpload <= 0) {
$targetEtaAt = (string) ($latest['measured_at'] ?? '');
} elseif (!empty($latest['measured_at'])) {
try {
$targetEtaAt = $this->formatUtcTimestamp(
$this->utcTimestamp((string) $latest['measured_at']) + (int) round($remainingDays * 86400)
$this->utcTimestamp((string) $latest['measured_at']) + (int) round($remainingDaysAtUpload * 86400)
);
} catch (\Throwable) {
$targetEtaAt = null;
}
}
}
$remainingDays = $targetEtaAt !== null
? max(0.0, ($this->utcTimestamp($targetEtaAt) - time()) / 86400)
: null;
$remainingDoge = ($remainingDays !== null && $latest['doge_per_day_interval'] !== null)
? max(0.0, $remainingDays * (float) $latest['doge_per_day_interval'])
: $remainingDogeAtUpload;
$targetSummary[] = array_merge($target, [
'effective_target_amount_fiat' => $this->roundOrNull($targetAmount, 2),
@@ -384,9 +390,11 @@ final class AnalyticsService
'latest_price_for_currency' => $price,
'required_doge' => $this->roundOrNull($requiredDoge, 6),
'remaining_doge' => $this->roundOrNull($remainingDoge, 6),
'remaining_doge_at_upload' => $this->roundOrNull($remainingDogeAtUpload, 6),
'remaining_days_at_upload' => $this->roundOrNull($remainingDaysAtUpload, 4),
'remaining_days' => $this->roundOrNull($remainingDays, 4),
'target_eta_at' => $targetEtaAt,
'status' => $remainingDoge !== null && $remainingDoge <= 0 ? 'reached' : 'open',
'status' => $remainingDays !== null && $remainingDays <= 0 ? 'reached' : 'open',
]);
}
@@ -452,6 +460,12 @@ final class AnalyticsService
$totalHoldingsValue = ($walletValue !== null || $currentVisibleValue !== null)
? (float) ($walletValue ?? 0.0) + (float) ($currentVisibleValue ?? 0.0)
: null;
$settledCryptoSpendValue = $latestCurrency !== ''
? $this->settledCryptoSpendValue($purchasedMiners, $latestCurrency, $latest)
: null;
$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;
$breakEvenRemainingAmount = ($cashInvestedCapital !== null && $totalHoldingsValue !== null)
? max(0.0, $cashInvestedCapital - $totalHoldingsValue)
@@ -493,6 +507,8 @@ final class AnalyticsService
'wallet_value' => $this->roundOrNull($walletValue, 8),
'wallet_snapshot_measured_at' => $useWalletSnapshot ? (string) ($latestWalletSnapshot['measured_at'] ?? '') : null,
'total_holdings_value' => $this->roundOrNull($totalHoldingsValue, 8),
'settled_crypto_spend_value' => $this->roundOrNull($settledCryptoSpendValue, 8),
'earned_value' => $this->roundOrNull($earnedValue, 8),
'break_even_remaining_amount' => $this->roundOrNull($breakEvenRemainingAmount, 8),
'break_even_days_overall' => $this->roundOrNull($breakEvenDaysOverall, 4),
'break_even_eta_at' => $breakEvenProjection['eta'] ?? null,
@@ -1646,6 +1662,33 @@ final class AnalyticsService
return array_map(fn (float $value): float => round($value, 8), $balances);
}
private function settledCryptoSpendValue(array $purchasedMiners, string $targetCurrency, ?array $fxContext = null): ?float
{
$total = 0.0;
$matched = false;
foreach ($purchasedMiners as $miner) {
if ($this->entryFundingSource($miner) !== 'reinvest') {
continue;
}
$amount = is_numeric($miner['settled_value_amount'] ?? null) ? (float) $miner['settled_value_amount'] : null;
$currency = strtoupper(trim((string) ($miner['settled_value_currency'] ?? '')));
if ($amount === null || $currency === '') {
continue;
}
$converted = $this->convertAmount($amount, $currency, $targetCurrency, $fxContext);
if ($converted === null) {
continue;
}
$matched = true;
$total += $converted;
}
return $matched ? $total : null;
}
private function mergeWalletBalances(array $baseBalances, array $deltaBalances): array
{
$merged = $baseBalances;
@@ -1765,6 +1808,13 @@ final class AnalyticsService
$balances[$normalizedCode] = round((float) $balance, 8);
}
// A wallet screenshot's primary balance is authoritative even when OCR did not
// expose a complete per-asset balance list.
$walletCurrency = strtoupper(trim((string) ($snapshot['wallet_currency'] ?? '')));
if ($walletCurrency !== '' && is_numeric($snapshot['wallet_balance'] ?? null)) {
$balances[$walletCurrency] = round((float) $snapshot['wallet_balance'], 8);
}
ksort($balances);
return $balances;
}

View File

@@ -1005,12 +1005,12 @@ final class MiningRepository
'INSERT INTO ' . $this->table('purchased_miners') . ' (
project_key, owner_sub, miner_offer_id, purchased_at, label, runtime_months,
mining_speed_value, mining_speed_unit, bonus_speed_value, bonus_speed_unit,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency, settled_value_amount, settled_value_currency,
daily_cost_amount, daily_cost_currency, auto_renew, note, is_active
) VALUES (
:project_key, :owner_sub, :miner_offer_id, :purchased_at, :label, :runtime_months,
:mining_speed_value, :mining_speed_unit, :bonus_speed_value, :bonus_speed_unit,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency, :settled_value_amount, :settled_value_currency,
:daily_cost_amount, :daily_cost_currency, :auto_renew, :note, :is_active
)
RETURNING *'
@@ -1023,12 +1023,12 @@ final class MiningRepository
'INSERT INTO ' . $this->table('purchased_miners') . ' (
project_key, owner_sub, miner_offer_id, purchased_at, label, runtime_months,
mining_speed_value, mining_speed_unit, bonus_speed_value, bonus_speed_unit,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency, settled_value_amount, settled_value_currency,
daily_cost_amount, daily_cost_currency, auto_renew, note, is_active
) VALUES (
:project_key, :owner_sub, :miner_offer_id, :purchased_at, :label, :runtime_months,
:mining_speed_value, :mining_speed_unit, :bonus_speed_value, :bonus_speed_unit,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency, :settled_value_amount, :settled_value_currency,
:daily_cost_amount, :daily_cost_currency, :auto_renew, :note, :is_active
)'
);
@@ -1057,6 +1057,8 @@ final class MiningRepository
'usd_reference_amount' => $payload['usd_reference_amount'] ?? null,
'reference_price_amount' => $payload['reference_price_amount'] ?? null,
'reference_price_currency' => $payload['reference_price_currency'] ?? null,
'settled_value_amount' => $payload['settled_value_amount'] ?? null,
'settled_value_currency' => $payload['settled_value_currency'] ?? null,
'daily_cost_amount' => $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null),
'daily_cost_currency' => $payload['daily_cost_currency'] ?? ($payload['currency'] ?? null),
'auto_renew' => $payload['auto_renew'] ?? 0,
@@ -1069,12 +1071,12 @@ final class MiningRepository
'INSERT INTO ' . $this->table('purchased_miners') . ' (
project_key, owner_sub, miner_offer_id, purchased_at, label, runtime_months,
mining_speed_value, mining_speed_unit, bonus_speed_value, bonus_speed_unit,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency, settled_value_amount, settled_value_currency,
daily_cost_amount, daily_cost_currency, auto_renew, note, is_active
) VALUES (
:project_key, :owner_sub, :miner_offer_id, :purchased_at, :label, :runtime_months,
:mining_speed_value, :mining_speed_unit, :bonus_speed_value, :bonus_speed_unit,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency, :settled_value_amount, :settled_value_currency,
:daily_cost_amount, :daily_cost_currency, :auto_renew, :note, :is_active
)
RETURNING *'
@@ -1087,12 +1089,12 @@ final class MiningRepository
'INSERT INTO ' . $this->table('purchased_miners') . ' (
project_key, owner_sub, miner_offer_id, purchased_at, label, runtime_months,
mining_speed_value, mining_speed_unit, bonus_speed_value, bonus_speed_unit,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency,
total_cost_amount, currency, usd_reference_amount, reference_price_amount, reference_price_currency, settled_value_amount, settled_value_currency,
daily_cost_amount, daily_cost_currency, auto_renew, note, is_active
) VALUES (
:project_key, :owner_sub, :miner_offer_id, :purchased_at, :label, :runtime_months,
:mining_speed_value, :mining_speed_unit, :bonus_speed_value, :bonus_speed_unit,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency,
:total_cost_amount, :currency, :usd_reference_amount, :reference_price_amount, :reference_price_currency, :settled_value_amount, :settled_value_currency,
:daily_cost_amount, :daily_cost_currency, :auto_renew, :note, :is_active
)'
);
@@ -1646,6 +1648,8 @@ final class MiningRepository
'usd_reference_amount' => $payload['usd_reference_amount'],
'reference_price_amount' => $payload['reference_price_amount'] ?? null,
'reference_price_currency' => $payload['reference_price_currency'] ?? null,
'settled_value_amount' => $payload['settled_value_amount'] ?? null,
'settled_value_currency' => $payload['settled_value_currency'] ?? null,
'daily_cost_amount' => $dailyCostAmount,
'daily_cost_currency' => $dailyCostCurrency,
'auto_renew' => $payload['auto_renew'] ?? 0,

View File

@@ -259,6 +259,13 @@ final class SchemaManager
$this->upgradePurchasedMinerReferenceColumns();
$applied[] = 'purchased_miner_reference_columns';
}
if ($this->tableExists($this->prefix . 'purchased_miners') && (
!$this->columnExists($this->prefix . 'purchased_miners', 'settled_value_amount') ||
!$this->columnExists($this->prefix . 'purchased_miners', 'settled_value_currency')
)) {
$this->upgradePurchasedMinerSettlementColumns();
$applied[] = 'purchased_miner_settlement_columns';
}
if ($this->tableExists($this->prefix . 'targets') && $this->tableExists($this->prefix . 'miner_offers')) {
$this->ensureTargetOfferForeignKey();
$applied[] = 'target_offer_foreign_key';
@@ -401,6 +408,13 @@ final class SchemaManager
$this->upgradePurchasedMinerReferenceColumns();
$applied[] = 'purchased_miner_reference_columns';
}
if ($this->tableExists($this->prefix . 'purchased_miners') && (
!$this->columnExists($this->prefix . 'purchased_miners', 'settled_value_amount') ||
!$this->columnExists($this->prefix . 'purchased_miners', 'settled_value_currency')
)) {
$this->upgradePurchasedMinerSettlementColumns();
$applied[] = 'purchased_miner_settlement_columns';
}
if ($this->tableExists($this->prefix . 'targets') && $this->tableExists($this->prefix . 'miner_offers')) {
$this->ensureTargetOfferForeignKey();
$applied[] = 'target_offer_foreign_key';
@@ -754,6 +768,12 @@ final class SchemaManager
if (!$this->tableExists($this->prefix . 'purchased_miners')) {
$upgrades[] = 'purchased_miners_table';
}
if ($this->tableExists($this->prefix . 'purchased_miners') && (
!$this->columnExists($this->prefix . 'purchased_miners', 'settled_value_amount') ||
!$this->columnExists($this->prefix . 'purchased_miners', 'settled_value_currency')
)) {
$upgrades[] = 'purchased_miner_settlement_columns';
}
if ($this->tableExists($this->prefix . 'targets') && !$this->columnExists($this->prefix . 'targets', 'miner_offer_id')) {
$upgrades[] = 'target_offer_column';
}
@@ -1502,6 +1522,33 @@ final class SchemaManager
}
}
private function upgradePurchasedMinerSettlementColumns(): void
{
$table = $this->prefix . 'purchased_miners';
$statements = $this->driver === 'pgsql'
? [
'ALTER TABLE ' . $table . ' ADD COLUMN IF NOT EXISTS settled_value_amount NUMERIC(20,8)',
'ALTER TABLE ' . $table . ' ADD COLUMN IF NOT EXISTS settled_value_currency VARCHAR(10)',
"UPDATE " . $table . " SET settled_value_amount = CASE WHEN reference_price_amount IS NOT NULL AND NULLIF(BTRIM(reference_price_currency), '') IS NOT NULL AND UPPER(reference_price_currency) <> UPPER(currency) THEN reference_price_amount ELSE total_cost_amount * 0.07 END, settled_value_currency = CASE WHEN reference_price_amount IS NOT NULL AND NULLIF(BTRIM(reference_price_currency), '') IS NOT NULL AND UPPER(reference_price_currency) <> UPPER(currency) THEN reference_price_currency ELSE 'EUR' END WHERE UPPER(currency) IN ('ADA','ARB','BNB','BTC','DAI','DOGE','DOT','ETH','LINK','LTC','SOL','USDC','USDT','XRP') AND settled_value_amount IS NULL",
]
: [
'ALTER TABLE `' . $table . '` ADD COLUMN settled_value_amount DECIMAL(20,8) NULL',
'ALTER TABLE `' . $table . '` ADD COLUMN settled_value_currency VARCHAR(10) NULL',
"UPDATE `" . $table . "` SET settled_value_amount = CASE WHEN reference_price_amount IS NOT NULL AND NULLIF(TRIM(reference_price_currency), '') IS NOT NULL AND UPPER(reference_price_currency) <> UPPER(currency) THEN reference_price_amount ELSE total_cost_amount * 0.07 END, settled_value_currency = CASE WHEN reference_price_amount IS NOT NULL AND NULLIF(TRIM(reference_price_currency), '') IS NOT NULL AND UPPER(reference_price_currency) <> UPPER(currency) THEN reference_price_currency ELSE 'EUR' END WHERE UPPER(currency) IN ('ADA','ARB','BNB','BTC','DAI','DOGE','DOT','ETH','LINK','LTC','SOL','USDC','USDT','XRP') AND settled_value_amount IS NULL",
];
foreach ($statements as $statement) {
try {
$this->executeUpgradeStatements([$statement], 'Schema-Upgrade fuer die feste Krypto-Bewertung fehlgeschlagen.');
} catch (\Throwable $exception) {
if ($this->driver === 'mysql' && str_contains(strtolower($exception->getMessage()), 'duplicate column')) {
continue;
}
throw $exception;
}
}
}
private function upgradeServerDailyCostColumns(): void
{
foreach ([$this->prefix . 'cost_plans', $this->prefix . 'purchased_miners'] as $table) {