sdsad
This commit is contained in:
@@ -299,6 +299,29 @@
|
|||||||
return `${map.year}-${map.month}-${map.day}`;
|
return `${map.year}-${map.month}-${map.day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCryptoCurrencyCode(code) {
|
||||||
|
const normalized = String(code || '').toUpperCase();
|
||||||
|
return [
|
||||||
|
'ADA', 'ARB', 'AVAX', 'BNB', 'BTC', 'CTC', 'DAI', 'DOGE', 'DOT', 'ETH',
|
||||||
|
'HSH', 'LINK', 'LTC', 'MATIC', 'SOL', 'TRX', 'USDC', 'USDT', 'XMR', 'XRP',
|
||||||
|
].includes(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
function entryCoverageMeta(startAt, runtimeMonths, autoRenew) {
|
||||||
|
const normalizedRuntime = Number(runtimeMonths);
|
||||||
|
const startDate = parseStoredUtcDate(startAt);
|
||||||
|
if (!startDate || !Number.isFinite(normalizedRuntime) || normalizedRuntime <= 0) {
|
||||||
|
return { endAt: null, isCovered: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const runtimeMs = normalizedRuntime * 30.4375 * 86400 * 1000;
|
||||||
|
const endAtDate = new Date(startDate.getTime() + runtimeMs);
|
||||||
|
return {
|
||||||
|
endAt: endAtDate.toISOString(),
|
||||||
|
isCovered: !!autoRenew || Date.now() <= endAtDate.getTime(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function currentSectionLabel(sectionId) {
|
function currentSectionLabel(sectionId) {
|
||||||
return sectionMap.get(String(sectionId || '').trim()) || 'Bereich';
|
return sectionMap.get(String(sectionId || '').trim()) || 'Bereich';
|
||||||
}
|
}
|
||||||
@@ -1106,45 +1129,54 @@
|
|||||||
const selectableFiatCurrencies = preferredSelectableFiatCurrencies.length ? preferredSelectableFiatCurrencies : fiatCurrencies;
|
const selectableFiatCurrencies = preferredSelectableFiatCurrencies.length ? preferredSelectableFiatCurrencies : fiatCurrencies;
|
||||||
const selectableCryptoCurrencies = preferredSelectableCryptoCurrencies.length ? preferredSelectableCryptoCurrencies : cryptoCurrencies;
|
const selectableCryptoCurrencies = preferredSelectableCryptoCurrencies.length ? preferredSelectableCryptoCurrencies : cryptoCurrencies;
|
||||||
const selectedMinerScenario = scenarioMinerOffers.find((offer) => String(offer.id) === String(selectedMinerScenarioId)) || null;
|
const selectedMinerScenario = scenarioMinerOffers.find((offer) => String(offer.id) === String(selectedMinerScenarioId)) || null;
|
||||||
const activeMinerRows = currentPurchasedMiners.map((miner) => ({
|
const activeMinerRows = currentPurchasedMiners.map((miner) => {
|
||||||
id: `purchase-${miner.id}`,
|
const coverage = entryCoverageMeta(miner.purchased_at, miner.runtime_months, miner.auto_renew);
|
||||||
source: 'miete',
|
const effectiveCurrency = String(miner.currency || '').toUpperCase();
|
||||||
starts_at: miner.purchased_at,
|
return {
|
||||||
label: miner.label,
|
id: `purchase-${miner.id}`,
|
||||||
runtime_months: miner.runtime_months,
|
source: 'miete',
|
||||||
auto_renew: !!miner.auto_renew,
|
starts_at: miner.purchased_at,
|
||||||
effective_amount: miner.total_cost_amount,
|
label: miner.label,
|
||||||
effective_currency: miner.currency,
|
runtime_months: miner.runtime_months,
|
||||||
base_amount: miner.reference_price_amount,
|
auto_renew: !!miner.auto_renew,
|
||||||
base_currency: miner.reference_price_currency,
|
effective_amount: miner.total_cost_amount,
|
||||||
miner_id: miner.id,
|
effective_currency: miner.currency,
|
||||||
miner_offer_id: miner.miner_offer_id,
|
base_amount: miner.reference_price_amount,
|
||||||
payment_type: miner.reference_price_currency
|
base_currency: miner.reference_price_currency,
|
||||||
&& miner.currency
|
miner_id: miner.id,
|
||||||
&& String(miner.reference_price_currency).toUpperCase() !== String(miner.currency).toUpperCase()
|
miner_offer_id: miner.miner_offer_id,
|
||||||
? 'crypto'
|
payment_type: isCryptoCurrencyCode(effectiveCurrency) ? 'crypto' : 'fiat',
|
||||||
: 'fiat',
|
is_active: miner.is_active !== false && coverage.isCovered,
|
||||||
is_active: miner.is_active !== false,
|
end_at: coverage.endAt,
|
||||||
can_toggle_auto_renew: Number(miner.runtime_months) > 0,
|
can_toggle_auto_renew: Number(miner.runtime_months) > 0,
|
||||||
hashrate_text: formatHashrateWithBonus(miner.mining_speed_value, miner.mining_speed_unit, miner.bonus_speed_value, miner.bonus_speed_unit),
|
toggle_resource: 'purchased-miners',
|
||||||
type_label: 'Aus Angebot gemietet',
|
toggle_id: miner.id,
|
||||||
})).concat(currentCostPlans.map((plan) => ({
|
hashrate_text: formatHashrateWithBonus(miner.mining_speed_value, miner.mining_speed_unit, miner.bonus_speed_value, miner.bonus_speed_unit),
|
||||||
id: `plan-${plan.id}`,
|
type_label: 'Aus Angebot gemietet',
|
||||||
source: 'manual',
|
};
|
||||||
starts_at: plan.starts_at,
|
}).concat(currentCostPlans.map((plan) => {
|
||||||
label: plan.label,
|
const coverage = entryCoverageMeta(plan.starts_at, plan.runtime_months, plan.auto_renew);
|
||||||
runtime_months: plan.runtime_months,
|
return {
|
||||||
auto_renew: !!plan.auto_renew,
|
id: `plan-${plan.id}`,
|
||||||
effective_amount: plan.total_cost_amount,
|
source: 'manual',
|
||||||
effective_currency: plan.currency,
|
starts_at: plan.starts_at,
|
||||||
base_amount: plan.base_price_amount,
|
label: plan.label,
|
||||||
base_currency: currentSettings.report_currency || 'EUR',
|
runtime_months: plan.runtime_months,
|
||||||
payment_type: plan.payment_type,
|
auto_renew: !!plan.auto_renew,
|
||||||
is_active: !!plan.is_active,
|
effective_amount: plan.total_cost_amount,
|
||||||
can_toggle_auto_renew: false,
|
effective_currency: plan.currency,
|
||||||
hashrate_text: formatHashrateWithBonus(plan.mining_speed_value, plan.mining_speed_unit, plan.bonus_speed_value, plan.bonus_speed_unit),
|
base_amount: plan.base_price_amount,
|
||||||
type_label: 'Manuell eingetragen',
|
base_currency: currentSettings.report_currency || 'EUR',
|
||||||
}))).sort((left, right) => String(right.starts_at || '').localeCompare(String(left.starts_at || '')));
|
payment_type: plan.payment_type,
|
||||||
|
is_active: !!plan.is_active && coverage.isCovered,
|
||||||
|
end_at: coverage.endAt,
|
||||||
|
can_toggle_auto_renew: Number(plan.runtime_months) > 0,
|
||||||
|
toggle_resource: 'cost-plans',
|
||||||
|
toggle_id: plan.id,
|
||||||
|
hashrate_text: formatHashrateWithBonus(plan.mining_speed_value, plan.mining_speed_unit, plan.bonus_speed_value, plan.bonus_speed_unit),
|
||||||
|
type_label: 'Manuell eingetragen',
|
||||||
|
};
|
||||||
|
})).sort((left, right) => String(right.starts_at || '').localeCompare(String(left.starts_at || '')));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reportCurrencyOverride) {
|
if (reportCurrencyOverride) {
|
||||||
@@ -2060,15 +2092,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function togglePurchasedMinerAutoRenew(row) {
|
async function toggleMinerAutoRenew(row) {
|
||||||
if (!row || !row.can_toggle_auto_renew || !row.miner_id) {
|
if (!row || !row.can_toggle_auto_renew || !row.toggle_resource || !row.toggle_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
setError('');
|
setError('');
|
||||||
try {
|
try {
|
||||||
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/purchased-miners/${encodeURIComponent(row.miner_id)}`, {
|
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/${encodeURIComponent(row.toggle_resource)}/${encodeURIComponent(row.toggle_id)}`, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ auto_renew: !row.auto_renew }),
|
body: JSON.stringify({ auto_renew: !row.auto_renew }),
|
||||||
@@ -2721,6 +2753,16 @@
|
|||||||
const reinvestedCapital = latest ? convertMeasurementMoney(latest, latest.reinvested_capital, reportCurrency) : null;
|
const reinvestedCapital = latest ? convertMeasurementMoney(latest, latest.reinvested_capital, reportCurrency) : null;
|
||||||
const walletValue = latest ? convertMeasurementMoney(latest, latest.wallet_value, reportCurrency) : null;
|
const walletValue = latest ? convertMeasurementMoney(latest, latest.wallet_value, reportCurrency) : null;
|
||||||
const totalHoldingsValue = latest ? convertMeasurementMoney(latest, latest.total_holdings_value, reportCurrency) : null;
|
const totalHoldingsValue = latest ? convertMeasurementMoney(latest, latest.total_holdings_value, reportCurrency) : null;
|
||||||
|
const totalEarnedOverall = latest && latest.growth_since_baseline !== null && latest.growth_since_baseline !== undefined
|
||||||
|
? Number(latest.growth_since_baseline)
|
||||||
|
: null;
|
||||||
|
const totalSpentOverallCurrentAsset = latest
|
||||||
|
? convertMeasurementMoney(
|
||||||
|
latest,
|
||||||
|
(Number(latest.cash_invested_capital) || 0) + (Number(latest.reinvested_capital) || 0),
|
||||||
|
currentCoinCurrency
|
||||||
|
)
|
||||||
|
: null;
|
||||||
const breakEvenReached = breakEvenRemainingAmount !== null && breakEvenRemainingAmount <= 0;
|
const breakEvenReached = breakEvenRemainingAmount !== null && breakEvenRemainingAmount <= 0;
|
||||||
const breakEvenEta = latest && latest.break_even_eta_at ? fmtDate(latest.break_even_eta_at) : null;
|
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 walletBalanceCurrentAsset = payload?.summary?.payouts?.wallet_balance_current_asset;
|
||||||
@@ -2759,6 +2801,20 @@
|
|||||||
? `Wallet-Gegenwert ${fmtNumber(walletBalanceCurrentAsset, 6)} ${currentCoinCurrency}${currentWalletPrimaryCurrency !== currentCoinCurrency ? ` · direkt ${fmtNumber(walletBalanceCurrentAssetDirect, 6)} ${currentCoinCurrency}` : ''} · Miner ${fmtNumber(latest?.coins_total_visible ?? latest?.coins_total, 6)} ${currentCoinCurrency}`
|
? `Wallet-Gegenwert ${fmtNumber(walletBalanceCurrentAsset, 6)} ${currentCoinCurrency}${currentWalletPrimaryCurrency !== currentCoinCurrency ? ` · direkt ${fmtNumber(walletBalanceCurrentAssetDirect, 6)} ${currentCoinCurrency}` : ''} · Miner ${fmtNumber(latest?.coins_total_visible ?? latest?.coins_total, 6)} ${currentCoinCurrency}`
|
||||||
: '',
|
: '',
|
||||||
}),
|
}),
|
||||||
|
h(StatCard, {
|
||||||
|
key: 'earned-overall',
|
||||||
|
label: `Bisher verdient gesamt ${currentCoinCurrency}`,
|
||||||
|
value: totalEarnedOverall !== null ? fmtNumber(totalEarnedOverall, 6) : 'n/a',
|
||||||
|
sub: currentSettings?.baseline_measured_at
|
||||||
|
? `Seit Baseline ${fmtDate(currentSettings.baseline_measured_at)}`
|
||||||
|
: 'Benötigt Baseline',
|
||||||
|
}),
|
||||||
|
h(StatCard, {
|
||||||
|
key: 'spent-overall',
|
||||||
|
label: `Bisher ausgegeben gesamt ${currentCoinCurrency}`,
|
||||||
|
value: totalSpentOverallCurrentAsset !== null ? fmtNumber(totalSpentOverallCurrentAsset, 6) : 'n/a',
|
||||||
|
sub: 'FIAT und Krypto auf die aktuelle Mining-Währung reduziert',
|
||||||
|
}),
|
||||||
h(StatCard, {
|
h(StatCard, {
|
||||||
key: 'perday',
|
key: 'perday',
|
||||||
label: perDayLabel,
|
label: perDayLabel,
|
||||||
@@ -3182,6 +3238,7 @@
|
|||||||
h('td', { key: 'runtime' }, [
|
h('td', { key: 'runtime' }, [
|
||||||
h('div', { key: 'months' }, `${row.runtime_months} Monate`),
|
h('div', { key: 'months' }, `${row.runtime_months} Monate`),
|
||||||
h('div', { key: 'hash', className: 'mc-kicker' }, row.hashrate_text),
|
h('div', { key: 'hash', className: 'mc-kicker' }, row.hashrate_text),
|
||||||
|
row.end_at ? h('div', { key: 'end', className: 'mc-kicker' }, `Ende ${fmtDate(row.end_at)}`) : null,
|
||||||
]),
|
]),
|
||||||
h('td', { key: 'renew' }, row.auto_renew ? 'ja' : 'nein'),
|
h('td', { key: 'renew' }, row.auto_renew ? 'ja' : 'nein'),
|
||||||
h('td', { key: 'cost' }, [
|
h('td', { key: 'cost' }, [
|
||||||
@@ -3200,7 +3257,7 @@
|
|||||||
? h('button', {
|
? h('button', {
|
||||||
type: 'button',
|
type: 'button',
|
||||||
className: 'mc-button mc-button--ghost',
|
className: 'mc-button mc-button--ghost',
|
||||||
onClick: () => togglePurchasedMinerAutoRenew(row),
|
onClick: () => toggleMinerAutoRenew(row),
|
||||||
disabled: saving,
|
disabled: saving,
|
||||||
}, row.auto_renew ? 'Verlaengerung aus' : 'Verlaengerung an')
|
}, row.auto_renew ? 'Verlaengerung aus' : 'Verlaengerung an')
|
||||||
: '—'
|
: '—'
|
||||||
|
|||||||
@@ -265,6 +265,10 @@ final class Router
|
|||||||
Http::json(['data' => $this->saveCostPlan($projectKey, Http::input())], 201);
|
Http::json(['data' => $this->saveCostPlan($projectKey, Http::input())], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preg_match('~^cost-plans/(\d+)$~', $resource, $matches) && $method === 'PATCH') {
|
||||||
|
Http::json(['data' => $this->updateCostPlan($projectKey, (int) $matches[1], Http::input())]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($resource === 'payouts' && $method === 'GET') {
|
if ($resource === 'payouts' && $method === 'GET') {
|
||||||
Http::json(['data' => $this->payouts($projectKey)]);
|
Http::json(['data' => $this->payouts($projectKey)]);
|
||||||
}
|
}
|
||||||
@@ -1912,6 +1916,24 @@ final class Router
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateCostPlan(string $projectKey, int $planId, array $input): array
|
||||||
|
{
|
||||||
|
$plan = $this->repository()->getCostPlan($projectKey, $planId);
|
||||||
|
if (!is_array($plan)) {
|
||||||
|
throw new ApiException('Manuell eingetragener Miner nicht gefunden.', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists('auto_renew', $input)) {
|
||||||
|
throw new ApiException('Es kann aktuell nur auto_renew geaendert werden.', 422, ['field' => 'auto_renew']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->repository()->updateCostPlanAutoRenew(
|
||||||
|
$projectKey,
|
||||||
|
$planId,
|
||||||
|
!empty($input['auto_renew'])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private function savePayout(string $projectKey, array $input): array
|
private function savePayout(string $projectKey, array $input): array
|
||||||
{
|
{
|
||||||
$transferMode = $this->enumValue($input['transfer_mode'] ?? 'manual', ['manual', 'full_latest'], 'transfer_mode');
|
$transferMode = $this->enumValue($input['transfer_mode'] ?? 'manual', ['manual', 'full_latest'], 'transfer_mode');
|
||||||
|
|||||||
@@ -267,6 +267,70 @@ final class MiningRepository
|
|||||||
return $this->normalizeRow($fetch->fetch() ?: []);
|
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCostPlan(string $projectKey, int $planId): ?array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM ' . $this->table('cost_plans') . ' WHERE project_key = :project_key AND owner_sub = :owner_sub AND id = :id LIMIT 1'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'id' => $planId,
|
||||||
|
]);
|
||||||
|
$row = $stmt->fetch();
|
||||||
|
if (is_array($row)) {
|
||||||
|
return $this->normalizeRow($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
$legacyStmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM ' . $this->table('cost_plans') . ' WHERE project_key = :project_key AND id = :id LIMIT 1'
|
||||||
|
);
|
||||||
|
$legacyStmt->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'id' => $planId,
|
||||||
|
]);
|
||||||
|
$legacyRow = $legacyStmt->fetch();
|
||||||
|
return is_array($legacyRow) ? $this->normalizeRow($legacyRow) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCostPlanAutoRenew(string $projectKey, int $planId, bool $autoRenew): array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare(
|
||||||
|
'UPDATE ' . $this->table('cost_plans') . '
|
||||||
|
SET auto_renew = :auto_renew
|
||||||
|
WHERE project_key = :project_key AND owner_sub = :owner_sub AND id = :id'
|
||||||
|
);
|
||||||
|
$stmt->execute([
|
||||||
|
'auto_renew' => $autoRenew ? 1 : 0,
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'id' => $planId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() === 0) {
|
||||||
|
$legacyUpdate = $this->pdo->prepare(
|
||||||
|
'UPDATE ' . $this->table('cost_plans') . '
|
||||||
|
SET auto_renew = :auto_renew
|
||||||
|
WHERE project_key = :project_key AND id = :id'
|
||||||
|
);
|
||||||
|
$legacyUpdate->execute([
|
||||||
|
'auto_renew' => $autoRenew ? 1 : 0,
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'id' => $planId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fetch = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM ' . $this->table('cost_plans') . ' WHERE project_key = :project_key AND owner_sub = :owner_sub AND id = :id LIMIT 1'
|
||||||
|
);
|
||||||
|
$fetch->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'owner_sub' => $this->ownerSub,
|
||||||
|
'id' => $planId,
|
||||||
|
]);
|
||||||
|
return $this->normalizeRow($fetch->fetch() ?: []);
|
||||||
|
}
|
||||||
|
|
||||||
public function listMeasurements(string $projectKey, int $limit = 200): array
|
public function listMeasurements(string $projectKey, int $limit = 200): array
|
||||||
{
|
{
|
||||||
$stmt = $this->pdo->prepare(
|
$stmt = $this->pdo->prepare(
|
||||||
@@ -915,7 +979,19 @@ final class MiningRepository
|
|||||||
'id' => $minerId,
|
'id' => $minerId,
|
||||||
]);
|
]);
|
||||||
$row = $stmt->fetch();
|
$row = $stmt->fetch();
|
||||||
return is_array($row) ? $this->normalizeRow($row) : null;
|
if (is_array($row)) {
|
||||||
|
return $this->normalizeRow($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
$legacyStmt = $this->pdo->prepare(
|
||||||
|
'SELECT * FROM ' . $this->table('purchased_miners') . ' WHERE project_key = :project_key AND id = :id LIMIT 1'
|
||||||
|
);
|
||||||
|
$legacyStmt->execute([
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'id' => $minerId,
|
||||||
|
]);
|
||||||
|
$legacyRow = $legacyStmt->fetch();
|
||||||
|
return is_array($legacyRow) ? $this->normalizeRow($legacyRow) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function purchaseMiner(string $projectKey, ?int $offerId, array $payload): array
|
public function purchaseMiner(string $projectKey, ?int $offerId, array $payload): array
|
||||||
@@ -1027,6 +1103,19 @@ final class MiningRepository
|
|||||||
'id' => $minerId,
|
'id' => $minerId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() === 0) {
|
||||||
|
$legacyUpdate = $this->pdo->prepare(
|
||||||
|
'UPDATE ' . $this->table('purchased_miners') . '
|
||||||
|
SET auto_renew = :auto_renew
|
||||||
|
WHERE project_key = :project_key AND id = :id'
|
||||||
|
);
|
||||||
|
$legacyUpdate->execute([
|
||||||
|
'auto_renew' => $autoRenew ? 1 : 0,
|
||||||
|
'project_key' => $projectKey,
|
||||||
|
'id' => $minerId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$fetch = $this->pdo->prepare(
|
$fetch = $this->pdo->prepare(
|
||||||
'SELECT * FROM ' . $this->table('purchased_miners') . ' WHERE project_key = :project_key AND owner_sub = :owner_sub AND id = :id LIMIT 1'
|
'SELECT * FROM ' . $this->table('purchased_miners') . ' WHERE project_key = :project_key AND owner_sub = :owner_sub AND id = :id LIMIT 1'
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user