adasd
This commit is contained in:
@@ -198,18 +198,43 @@
|
||||
}).format(Number(value));
|
||||
}
|
||||
|
||||
function deriveDailyCostAmount(totalCostAmount, runtimeMonths) {
|
||||
const amount = Number(totalCostAmount);
|
||||
function calendarRuntimeEnd(startAt, runtimeMonths) {
|
||||
const start = parseStoredUtcDate(startAt);
|
||||
const months = Number(runtimeMonths);
|
||||
if (!Number.isFinite(amount) || !Number.isFinite(months) || months <= 0) {
|
||||
if (!start || !Number.isInteger(months) || months <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const runtimeDays = months * 30.4375;
|
||||
if (!Number.isFinite(runtimeDays) || runtimeDays <= 0) {
|
||||
const targetMonthIndex = (start.getUTCFullYear() * 12) + start.getUTCMonth() + months;
|
||||
const targetYear = Math.floor(targetMonthIndex / 12);
|
||||
const targetMonth = targetMonthIndex % 12;
|
||||
const lastDayOfTargetMonth = new Date(Date.UTC(targetYear, targetMonth + 1, 0)).getUTCDate();
|
||||
return new Date(Date.UTC(
|
||||
targetYear,
|
||||
targetMonth,
|
||||
Math.min(start.getUTCDate(), lastDayOfTargetMonth),
|
||||
start.getUTCHours(),
|
||||
start.getUTCMinutes(),
|
||||
start.getUTCSeconds(),
|
||||
start.getUTCMilliseconds()
|
||||
));
|
||||
}
|
||||
|
||||
function runtimeDaysForEntry(startAt, runtimeMonths) {
|
||||
const start = parseStoredUtcDate(startAt);
|
||||
const end = calendarRuntimeEnd(startAt, runtimeMonths);
|
||||
if (!start || !end) {
|
||||
return null;
|
||||
}
|
||||
return (end.getTime() - start.getTime()) / 86400000;
|
||||
}
|
||||
|
||||
function deriveDailyCostAmount(totalCostAmount, runtimeMonths, startsAt) {
|
||||
const amount = Number(totalCostAmount);
|
||||
const runtimeDays = runtimeDaysForEntry(startsAt, runtimeMonths);
|
||||
if (!Number.isFinite(amount) || !Number.isFinite(runtimeDays) || runtimeDays <= 0) {
|
||||
return null;
|
||||
}
|
||||
return amount / runtimeDays;
|
||||
}
|
||||
|
||||
@@ -356,8 +381,10 @@
|
||||
return { endAt: null, isCovered: true };
|
||||
}
|
||||
|
||||
const runtimeMs = normalizedRuntime * 30.4375 * 86400 * 1000;
|
||||
const endAtDate = new Date(startDate.getTime() + runtimeMs);
|
||||
const endAtDate = calendarRuntimeEnd(startAt, normalizedRuntime);
|
||||
if (!endAtDate) {
|
||||
return { endAt: null, isCovered: true };
|
||||
}
|
||||
return {
|
||||
endAt: endAtDate.toISOString(),
|
||||
isCovered: !!autoRenew || Date.now() <= endAtDate.getTime(),
|
||||
@@ -582,6 +609,7 @@
|
||||
current_effective_coins: null,
|
||||
wallet_balances: {},
|
||||
wallet_balance_current_asset: null,
|
||||
wallet_balance_current_asset_direct: null,
|
||||
holdings_current_asset: null,
|
||||
},
|
||||
current_hashrate_mh: null,
|
||||
@@ -1438,10 +1466,7 @@
|
||||
baseAmount = fallbackUsdReference;
|
||||
baseCurrency = 'USD';
|
||||
}
|
||||
const storedDailyAmount = Number(miner.daily_cost_amount);
|
||||
const dailyAmount = Number.isFinite(storedDailyAmount) && storedDailyAmount > 0
|
||||
? storedDailyAmount
|
||||
: deriveDailyCostAmount(miner.total_cost_amount, miner.runtime_months);
|
||||
const dailyAmount = deriveDailyCostAmount(miner.total_cost_amount, miner.runtime_months, miner.purchased_at);
|
||||
const dailyCurrency = String(miner.daily_cost_currency || miner.currency || '').toUpperCase();
|
||||
const dailyReportAmount = Number.isFinite(dailyAmount) && dailyCurrency
|
||||
? (dailyCurrency === reportCurrency ? dailyAmount : convertCurrencyValue(dailyAmount, dailyCurrency, reportCurrency))
|
||||
@@ -1450,7 +1475,7 @@
|
||||
const settledAmount = Number(miner.settled_value_amount);
|
||||
const settledCurrency = String(miner.settled_value_currency || '').toUpperCase();
|
||||
const hasHistoricalSettlement = Number.isFinite(settledAmount) && settledAmount > 0 && settledCurrency;
|
||||
const runtimeDays = Number(miner.runtime_months) * 30.4375;
|
||||
const runtimeDays = runtimeDaysForEntry(miner.purchased_at, miner.runtime_months);
|
||||
const costPerKhAmount = totalHashrateKh > 0 && Number.isFinite(runtimeDays) && runtimeDays > 0
|
||||
? ((paymentType === 'crypto' && hasHistoricalSettlement ? settledAmount : Number(miner.total_cost_amount)) / totalHashrateKh / runtimeDays)
|
||||
: null;
|
||||
@@ -1489,16 +1514,13 @@
|
||||
};
|
||||
}).concat(currentCostPlans.map((plan) => {
|
||||
const coverage = entryCoverageMeta(plan.starts_at, plan.runtime_months, plan.auto_renew);
|
||||
const storedDailyAmount = Number(plan.daily_cost_amount);
|
||||
const dailyAmount = Number.isFinite(storedDailyAmount) && storedDailyAmount > 0
|
||||
? storedDailyAmount
|
||||
: deriveDailyCostAmount(plan.total_cost_amount, plan.runtime_months);
|
||||
const dailyAmount = deriveDailyCostAmount(plan.total_cost_amount, plan.runtime_months, plan.starts_at);
|
||||
const dailyCurrency = String(plan.daily_cost_currency || plan.currency || '').toUpperCase();
|
||||
const dailyReportAmount = Number.isFinite(dailyAmount) && dailyCurrency
|
||||
? (dailyCurrency === reportCurrency ? dailyAmount : convertCurrencyValue(dailyAmount, dailyCurrency, reportCurrency))
|
||||
: null;
|
||||
const totalHashrateKh = toKhPerSecond(plan.mining_speed_value, plan.mining_speed_unit) + toKhPerSecond(plan.bonus_speed_value, plan.bonus_speed_unit);
|
||||
const runtimeDays = Number(plan.runtime_months) * 30.4375;
|
||||
const runtimeDays = runtimeDaysForEntry(plan.starts_at, plan.runtime_months);
|
||||
const costPerKhAmount = totalHashrateKh > 0 && Number.isFinite(runtimeDays) && runtimeDays > 0
|
||||
? Number(plan.total_cost_amount) / totalHashrateKh / runtimeDays
|
||||
: null;
|
||||
@@ -3206,12 +3228,11 @@
|
||||
const perDayLabel = `${currentCoinCurrency} pro Tag`;
|
||||
|
||||
if (activeTab === 'overview') {
|
||||
const latestValue = latest ? convertMeasurementMoney(latest, latest.current_value, reportCurrency) : null;
|
||||
const latestPriceSource = latest && latest.effective_price_per_coin !== null && latest.effective_price_per_coin !== undefined
|
||||
? latest.effective_price_per_coin
|
||||
: (latest ? latest.price_per_coin : null);
|
||||
const latestPrice = latest && latestPriceSource !== null && latestPriceSource !== undefined
|
||||
? convertMeasurementMoney(latest, latestPriceSource, reportCurrency)
|
||||
const latestCoinPriceUsd = latest && latestPriceSource !== null && latestPriceSource !== undefined
|
||||
? convertMeasurementMoney(latest, latestPriceSource, 'USD')
|
||||
: null;
|
||||
const dailyRevenue = latest ? convertMeasurementMoney(latest, latest.theoretical_daily_revenue, reportCurrency) : null;
|
||||
const dailyProfit = latest ? convertMeasurementMoney(latest, latest.theoretical_daily_profit, reportCurrency) : null;
|
||||
@@ -3225,7 +3246,6 @@
|
||||
: null;
|
||||
const investedCapital = latest ? convertMeasurementMoney(latest, latest.cash_invested_capital ?? latest.invested_capital, reportCurrency) : null;
|
||||
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;
|
||||
@@ -3238,10 +3258,14 @@
|
||||
: null;
|
||||
const breakEvenReached = breakEvenRemainingAmount !== null && breakEvenRemainingAmount <= 0;
|
||||
const breakEvenEta = latest && latest.break_even_eta_at ? fmtDate(latest.break_even_eta_at) : null;
|
||||
const walletBalanceCurrentAsset = payload?.summary?.payouts?.wallet_balance_current_asset;
|
||||
const walletBalanceCurrentAssetDirect = payload?.summary?.payouts?.wallet_balance_current_asset_direct;
|
||||
const holdingsCurrentAsset = payload?.summary?.payouts?.holdings_current_asset;
|
||||
const minerVisibleCoins = latest ? Number(latest.coins_total_visible ?? latest.coins_total) : null;
|
||||
const walletCurrentAssetCoins = walletBalanceCurrentAssetDirect !== null && walletBalanceCurrentAssetDirect !== undefined
|
||||
? Number(walletBalanceCurrentAssetDirect)
|
||||
: null;
|
||||
const totalCurrentAssetCoins = Number.isFinite(walletCurrentAssetCoins) && Number.isFinite(minerVisibleCoins)
|
||||
? walletCurrentAssetCoins + minerVisibleCoins
|
||||
: null;
|
||||
const minerVisibleValue = latest && latest.current_value !== null && latest.current_value !== undefined
|
||||
? Number(latest.current_value)
|
||||
: (
|
||||
@@ -3322,20 +3346,21 @@
|
||||
: 'Noch kein Transfer vor dem letzten Upload',
|
||||
}),
|
||||
h(StatCard, {
|
||||
key: 'value',
|
||||
label: 'Bisheriger Wert',
|
||||
value: earnedValue !== null ? fmtMoney(earnedValue, reportCurrency) : 'n/a',
|
||||
key: 'current-asset-balance',
|
||||
label: `${currentCoinCurrency} Bestand`,
|
||||
value: totalCurrentAssetCoins !== null ? `${fmtNumber(totalCurrentAssetCoins, 6)} ${currentCoinCurrency}` : 'n/a',
|
||||
sub: [
|
||||
holdingsCurrentAsset !== null && holdingsCurrentAsset !== undefined
|
||||
? `Variabel ${fmtNumber(holdingsCurrentAsset, 6)} ${currentCoinCurrency}`
|
||||
walletCurrentAssetCoins !== null && Number.isFinite(walletCurrentAssetCoins)
|
||||
? `Wallet ${fmtNumber(walletCurrentAssetCoins, 6)} ${currentCoinCurrency}`
|
||||
: null,
|
||||
latestValue !== null ? `Miner ${fmtMoney(latestValue, reportCurrency)}` : null,
|
||||
walletValue !== null ? `Wallet ${fmtMoney(walletValue, reportCurrency)}` : null,
|
||||
settledCryptoSpendValue !== null ? `Fix ausgegeben ${fmtMoney(settledCryptoSpendValue, reportCurrency)}` : null,
|
||||
latestPrice !== null
|
||||
? `Kurs ${fmtNumber(latestPrice, 6)} ${reportCurrency}${latest && latest.price_is_fallback ? ' · Fallback aus letztem Kurs' : ''}`
|
||||
Number.isFinite(minerVisibleCoins)
|
||||
? `Miner ${fmtNumber(minerVisibleCoins, 6)} ${currentCoinCurrency}`
|
||||
: null,
|
||||
].filter(Boolean).join(' · ') || 'Kein umrechenbarer Kurs am letzten Punkt',
|
||||
totalCurrentAssetCoins !== null ? `Gesamt ${fmtNumber(totalCurrentAssetCoins, 6)} ${currentCoinCurrency}` : null,
|
||||
latestCoinPriceUsd !== null
|
||||
? `Letzter ${currentCoinCurrency}-Kurs ${fmtNumber(latestCoinPriceUsd, 6)} USD${latest && latest.price_is_fallback ? ' · Fallback aus letztem Kurs' : ''}`
|
||||
: null,
|
||||
].filter(Boolean).join(' · ') || 'Kein Wallet- oder Mining-Bestand vorhanden',
|
||||
}),
|
||||
h(StatCard, {
|
||||
key: 'preferred-offer-target',
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
Das Modul erfasst Mining-Messpunkte der aktuell konfigurierten Kryptowaehrung, analysiert OCR-Vorschlaege aus Screenshots, speichert Messreihen projektbezogen und berechnet Performance-, Kurs- und Zielmetriken. `DOGE` ist lediglich die aktuelle Konfiguration, nicht eine fest verdrahtete Fachregel.
|
||||
|
||||
Im Startseiten-Ueberblick zeigt die Kachel `DOGE Bestand` den direkten Wallet-Bestand, den nach Transfers bereinigten Miner-Bestand, deren Summe sowie den letzten Coin/USD-Kurs. Bei einer geaenderten Mining-Waehrung werden Beschriftung und Werte entsprechend dieser Waehrung gefuehrt.
|
||||
|
||||
## Ordnerstruktur
|
||||
|
||||
```text
|
||||
@@ -139,8 +141,8 @@ Diese Regeln sind bei jeder Erweiterung der Wallet-, Miner- oder Uebersichtslogi
|
||||
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.
|
||||
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.
|
||||
10. `Kosten/kH/s/Tag` ist ein laufzeitbereinigter Vergleichswert: feste historische Gesamtkosten geteilt durch die gesamte Hashrate einschliesslich Bonus und die Laufzeit in Tagen (`Monate x 30,4375`). 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.
|
||||
11. Die Startseitenkennzahl `Tageskosten` verteilt jede aktive Miete auf ihre Laufzeit (`Gesamtkosten / Laufzeittage`) und beruecksichtigt keine bereits abgelaufenen Miner. Bei Krypto-Mieten verwendet sie den festen Mietwert und dessen zugeordnete historische `fx-rates.fetch_id`, nicht den aktuellen Coin-Kurs.
|
||||
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.
|
||||
11. Die Startseitenkennzahl `Tageskosten` verteilt jede aktive Miete auf ihre exakte Kalenderlaufzeit (`Gesamtkosten / Kalendertage`) und beruecksichtigt keine bereits abgelaufenen Miner. Bei Krypto-Mieten verwendet sie den festen Mietwert und dessen zugeordnete historische `fx-rates.fetch_id`, nicht den aktuellen Coin-Kurs. Ein Tageswert fuer ein Angebot ohne Mietdatum wird nicht angezeigt, weil seine exakte Kalenderlaufzeit erst bei der Anmietung feststeht.
|
||||
|
||||
### Historische Krypto-Miner
|
||||
|
||||
|
||||
@@ -700,7 +700,7 @@ final class AnalyticsService
|
||||
continue;
|
||||
}
|
||||
|
||||
$convertedDailyCost = $this->entryDailyCostInCurrency($entry, $runtimeMonths, $currency, $fxContext);
|
||||
$convertedDailyCost = $this->entryDailyCostInCurrency($entry, $runtimeMonths, $currency, $fxContext, $startField);
|
||||
if ($convertedDailyCost === null) {
|
||||
continue;
|
||||
}
|
||||
@@ -712,9 +712,9 @@ final class AnalyticsService
|
||||
return $matched ? $dailyTotal : null;
|
||||
}
|
||||
|
||||
private function entryDailyCostInCurrency(array $entry, int $runtimeMonths, string $targetCurrency, ?array $fxContext = null): ?float
|
||||
private function entryDailyCostInCurrency(array $entry, int $runtimeMonths, string $targetCurrency, ?array $fxContext = null, string $startField = 'starts_at'): ?float
|
||||
{
|
||||
$runtimeDays = $runtimeMonths * 30.4375;
|
||||
$runtimeDays = $this->entryRuntimeDays($entry, $startField);
|
||||
if ($runtimeDays <= 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -762,20 +762,16 @@ final class AnalyticsService
|
||||
return null;
|
||||
}
|
||||
|
||||
$storedDailyCost = is_numeric($entry['daily_cost_amount'] ?? null) ? (float) $entry['daily_cost_amount'] : null;
|
||||
$entryCurrency = strtoupper(trim((string) ($entry['daily_cost_currency'] ?? $entry['currency'] ?? '')));
|
||||
if ($entryCurrency === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dailyAmount = $storedDailyCost;
|
||||
if ($dailyAmount === null || $dailyAmount <= 0) {
|
||||
$amount = is_numeric($entry['total_cost_amount'] ?? null) ? (float) $entry['total_cost_amount'] : null;
|
||||
if ($amount === null || $amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
$dailyAmount = $amount / $runtimeDays;
|
||||
$amount = is_numeric($entry['total_cost_amount'] ?? null) ? (float) $entry['total_cost_amount'] : null;
|
||||
if ($amount === null || $amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
$dailyAmount = $amount / $runtimeDays;
|
||||
|
||||
return $this->convertAmount($dailyAmount, $entryCurrency, $targetCurrency, $fxContext);
|
||||
}
|
||||
@@ -1094,8 +1090,10 @@ final class AnalyticsService
|
||||
return false;
|
||||
}
|
||||
|
||||
$runtimeDays = $runtimeMonths * 30.4375;
|
||||
$endTs = (int) round($startTs + ($runtimeDays * 86400));
|
||||
$endTs = $this->entryCoverageEndTimestamp($entry, $startField);
|
||||
if ($endTs === null) {
|
||||
return true;
|
||||
}
|
||||
return $this->entryAutoRenewEnabled($entry) || $checkTs <= $endTs;
|
||||
}
|
||||
|
||||
@@ -1586,7 +1584,10 @@ final class AnalyticsService
|
||||
|
||||
$runtimeMonths = (int) ($plan['runtime_months'] ?? 0);
|
||||
if ($runtimeMonths > 0 && is_numeric($plan['total_cost_amount'] ?? null)) {
|
||||
$runtimeDays = $runtimeMonths * 30.4375;
|
||||
$runtimeDays = $this->entryRuntimeDays($plan);
|
||||
if ($runtimeDays <= 0) {
|
||||
continue;
|
||||
}
|
||||
$dailyCost = $this->convertAmount((float) $plan['total_cost_amount'] / $runtimeDays, (string) ($plan['currency'] ?? ''), $currency, $latest);
|
||||
if ($dailyCost !== null) {
|
||||
$cost += $dailyCost * $coveredDays;
|
||||
@@ -1610,7 +1611,10 @@ final class AnalyticsService
|
||||
|
||||
$runtimeMonths = (int) ($miner['runtime_months'] ?? 0);
|
||||
if ($runtimeMonths > 0 && is_numeric($miner['total_cost_amount'] ?? null)) {
|
||||
$runtimeDays = $runtimeMonths * 30.4375;
|
||||
$runtimeDays = $this->entryRuntimeDays($miner, 'purchased_at');
|
||||
if ($runtimeDays <= 0) {
|
||||
continue;
|
||||
}
|
||||
$dailyCost = $this->convertAmount((float) $miner['total_cost_amount'] / $runtimeDays, (string) ($miner['currency'] ?? ''), $currency, $latest);
|
||||
if ($dailyCost !== null) {
|
||||
$cost += $dailyCost * $coveredDays;
|
||||
@@ -1649,8 +1653,10 @@ final class AnalyticsService
|
||||
return $days - $startIndex;
|
||||
}
|
||||
|
||||
$runtimeDays = $runtimeMonths * 30.4375;
|
||||
$endTs = (int) round($startTs + ($runtimeDays * 86400));
|
||||
$endTs = $this->entryCoverageEndTimestamp($entry, $startField);
|
||||
if ($endTs === null) {
|
||||
return $days - $startIndex;
|
||||
}
|
||||
$endIndex = (int) floor(($endTs - $baseTs) / 86400);
|
||||
$endIndex = min($days - 1, $endIndex);
|
||||
if ($endIndex < $startIndex) {
|
||||
@@ -2178,8 +2184,31 @@ final class AnalyticsService
|
||||
return null;
|
||||
}
|
||||
|
||||
$runtimeDays = $runtimeMonths * 30.4375;
|
||||
return (int) round($startTs + ($runtimeDays * 86400));
|
||||
return $this->calendarMonthEndTimestamp($startTs, $runtimeMonths);
|
||||
}
|
||||
|
||||
private function entryRuntimeDays(array $entry, string $startField = 'starts_at'): float
|
||||
{
|
||||
$startTs = $this->utcTimestamp((string) ($entry[$startField] ?? ''));
|
||||
$runtimeMonths = (int) ($entry['runtime_months'] ?? 0);
|
||||
if ($startTs <= 0 || $runtimeMonths <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$endTs = $this->calendarMonthEndTimestamp($startTs, $runtimeMonths);
|
||||
return $endTs > $startTs ? ($endTs - $startTs) / 86400 : 0.0;
|
||||
}
|
||||
|
||||
private function calendarMonthEndTimestamp(int $startTs, int $runtimeMonths): int
|
||||
{
|
||||
$utc = new \DateTimeZone('UTC');
|
||||
$start = (new \DateTimeImmutable('@' . $startTs))->setTimezone($utc);
|
||||
$monthIndex = (((int) $start->format('Y')) * 12) + ((int) $start->format('n')) - 1 + $runtimeMonths;
|
||||
$year = intdiv($monthIndex, 12);
|
||||
$month = ($monthIndex % 12) + 1;
|
||||
$day = min((int) $start->format('j'), cal_days_in_month(CAL_GREGORIAN, $month, $year));
|
||||
|
||||
return $start->setDate($year, $month, $day)->getTimestamp();
|
||||
}
|
||||
|
||||
private function utcTimestamp(?string $value): int
|
||||
|
||||
@@ -1060,7 +1060,7 @@ final class MiningRepository
|
||||
'settled_value_amount' => $payload['settled_value_amount'] ?? null,
|
||||
'settled_value_currency' => $payload['settled_value_currency'] ?? null,
|
||||
'settled_fx_fetch_id' => $payload['settled_fx_fetch_id'] ?? null,
|
||||
'daily_cost_amount' => $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null),
|
||||
'daily_cost_amount' => $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null, $payload['purchased_at'] ?? null),
|
||||
'daily_cost_currency' => $payload['daily_cost_currency'] ?? ($payload['currency'] ?? null),
|
||||
'auto_renew' => $payload['auto_renew'] ?? 0,
|
||||
'note' => $payload['note'] ?? null,
|
||||
@@ -1615,7 +1615,7 @@ final class MiningRepository
|
||||
|
||||
private function normalizeInsertPayload(string $projectKey, array $payload): array
|
||||
{
|
||||
$dailyCostAmount = $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null);
|
||||
$dailyCostAmount = $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null, $payload['starts_at'] ?? null);
|
||||
$dailyCostCurrency = $payload['daily_cost_currency'] ?? ($payload['currency'] ?? null);
|
||||
|
||||
return [
|
||||
@@ -1661,7 +1661,7 @@ final class MiningRepository
|
||||
|
||||
private function normalizePurchasedPayload(string $projectKey, ?int $offerId, array $payload): array
|
||||
{
|
||||
$dailyCostAmount = $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null);
|
||||
$dailyCostAmount = $payload['daily_cost_amount'] ?? $this->deriveDailyCostAmount($payload['total_cost_amount'] ?? null, $payload['runtime_months'] ?? null, $payload['purchased_at'] ?? null);
|
||||
$dailyCostCurrency = $payload['daily_cost_currency'] ?? ($payload['currency'] ?? null);
|
||||
|
||||
return [
|
||||
@@ -1691,18 +1691,29 @@ final class MiningRepository
|
||||
];
|
||||
}
|
||||
|
||||
private function deriveDailyCostAmount(mixed $totalCostAmount, mixed $runtimeMonths): ?float
|
||||
private function deriveDailyCostAmount(mixed $totalCostAmount, mixed $runtimeMonths, mixed $startsAt): ?float
|
||||
{
|
||||
if (!is_numeric($totalCostAmount) || !is_numeric($runtimeMonths)) {
|
||||
if (!is_numeric($totalCostAmount) || !is_numeric($runtimeMonths) || !is_string($startsAt) || trim($startsAt) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$months = (float) $runtimeMonths;
|
||||
$months = (int) $runtimeMonths;
|
||||
if ($months <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$runtimeDays = $months * 30.4375;
|
||||
try {
|
||||
$utc = new \DateTimeZone('UTC');
|
||||
$start = new \DateTimeImmutable($startsAt, $utc);
|
||||
$monthIndex = (((int) $start->format('Y')) * 12) + ((int) $start->format('n')) - 1 + $months;
|
||||
$year = intdiv($monthIndex, 12);
|
||||
$month = ($monthIndex % 12) + 1;
|
||||
$day = min((int) $start->format('j'), cal_days_in_month(CAL_GREGORIAN, $month, $year));
|
||||
$end = $start->setDate($year, $month, $day);
|
||||
$runtimeDays = ($end->getTimestamp() - $start->getTimestamp()) / 86400;
|
||||
} catch (\Throwable) {
|
||||
return null;
|
||||
}
|
||||
if ($runtimeDays <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1590,13 +1590,11 @@ final class SchemaManager
|
||||
? [
|
||||
'ALTER TABLE ' . $table . ' ADD COLUMN IF NOT EXISTS daily_cost_amount NUMERIC(20,10)',
|
||||
'ALTER TABLE ' . $table . ' ADD COLUMN IF NOT EXISTS daily_cost_currency VARCHAR(10)',
|
||||
'UPDATE ' . $table . ' SET daily_cost_amount = ROUND((total_cost_amount / NULLIF(runtime_months * 30.4375, 0))::numeric, 10) WHERE daily_cost_amount IS NULL AND total_cost_amount IS NOT NULL AND runtime_months IS NOT NULL AND runtime_months > 0',
|
||||
'UPDATE ' . $table . ' SET daily_cost_currency = COALESCE(daily_cost_currency, currency) WHERE currency IS NOT NULL',
|
||||
]
|
||||
: [
|
||||
'ALTER TABLE `' . $table . '` ADD COLUMN daily_cost_amount DECIMAL(20,10) NULL',
|
||||
'ALTER TABLE `' . $table . '` ADD COLUMN daily_cost_currency VARCHAR(10) NULL',
|
||||
'UPDATE `' . $table . '` SET daily_cost_amount = ROUND(total_cost_amount / NULLIF(runtime_months * 30.4375, 0), 10) WHERE daily_cost_amount IS NULL AND total_cost_amount IS NOT NULL AND runtime_months IS NOT NULL AND runtime_months > 0',
|
||||
'UPDATE `' . $table . '` SET daily_cost_currency = COALESCE(daily_cost_currency, currency) WHERE currency IS NOT NULL',
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user