reports page #518
@@ -1,13 +1,16 @@
|
||||
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
extension WalletUiMapper on domain.WalletModel {
|
||||
Wallet toUi() => Wallet(
|
||||
id: walletRef,
|
||||
walletUserID: walletRef,
|
||||
balance: double.tryParse(availableMoney?.amount ?? balance?.available?.amount ?? '0') ?? 0,
|
||||
balance: parseMoneyAmount(
|
||||
availableMoney?.amount ?? balance?.available?.amount,
|
||||
),
|
||||
currency: currencyStringToCode(asset.tokenSymbol),
|
||||
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
||||
depositAddress: depositAddress,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/models/wallet/chain_asset.dart';
|
||||
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
abstract class WalletsService {
|
||||
@@ -29,8 +30,7 @@ class ApiWalletsService implements WalletsService {
|
||||
organizationRef: organizationRef,
|
||||
walletRef: walletRef,
|
||||
);
|
||||
final amount = balance.available?.amount;
|
||||
return amount == null ? 0 : double.tryParse(amount) ?? 0;
|
||||
return parseMoneyAmount(balance.available?.amount);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
12
frontend/pshared/lib/utils/money.dart
Normal file
12
frontend/pshared/lib/utils/money.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
|
||||
|
||||
double parseMoneyAmount(String? raw, {double fallback = 0}) {
|
||||
final trimmed = raw?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) return fallback;
|
||||
return double.tryParse(trimmed) ?? fallback;
|
||||
}
|
||||
|
||||
extension MoneyAmountX on Money {
|
||||
double get amountValue => parseMoneyAmount(amount);
|
||||
}
|
||||
@@ -17,17 +17,11 @@ class RecentPaymentsController extends ChangeNotifier {
|
||||
|
||||
void update(PaymentsProvider provider) {
|
||||
if (!identical(_payments, provider)) {
|
||||
_payments?.removeListener(_onPaymentsChanged);
|
||||
_payments = provider;
|
||||
|
|
||||
_payments?.addListener(_onPaymentsChanged);
|
||||
}
|
||||
_rebuild();
|
||||
}
|
||||
|
||||
void _onPaymentsChanged() {
|
||||
_rebuild();
|
||||
}
|
||||
|
||||
void _rebuild() {
|
||||
final operations = (_payments?.payments ?? const [])
|
||||
.map(mapPaymentToOperation)
|
||||
@@ -36,9 +30,4 @@ class RecentPaymentsController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_payments?.removeListener(_onPaymentsChanged);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,7 @@ class ReportOperationsController extends ChangeNotifier {
|
||||
|
||||
void update(PaymentsProvider provider) {
|
||||
if (!identical(_payments, provider)) {
|
||||
_payments?.removeListener(_onPaymentsChanged);
|
||||
_payments = provider;
|
||||
|
tech
commented
то же самое то же самое
|
||||
_payments?.addListener(_onPaymentsChanged);
|
||||
}
|
||||
_rebuildOperations();
|
||||
}
|
||||
@@ -61,10 +59,6 @@ class ReportOperationsController extends ChangeNotifier {
|
||||
await _payments?.refresh();
|
||||
}
|
||||
|
||||
void _onPaymentsChanged() {
|
||||
_rebuildOperations();
|
||||
}
|
||||
|
||||
void _rebuildOperations() {
|
||||
final items = _payments?.payments ?? const [];
|
||||
_operations = items.map(mapPaymentToOperation).toList();
|
||||
@@ -107,9 +101,4 @@ class ReportOperationsController extends ChangeNotifier {
|
||||
left.end.isAtSameMomentAs(right.end);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_payments?.removeListener(_onPaymentsChanged);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
27
frontend/pweb/lib/models/payment_state.dart
Normal file
27
frontend/pweb/lib/models/payment_state.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
enum PaymentState {
|
||||
success,
|
||||
failed,
|
||||
cancelled,
|
||||
processing,
|
||||
unknown,
|
||||
}
|
||||
|
||||
PaymentState paymentStateFromRaw(String? raw) {
|
||||
final trimmed = (raw ?? '').trim().toUpperCase();
|
||||
final normalized = trimmed.startsWith('PAYMENT_STATE_')
|
||||
? trimmed.substring('PAYMENT_STATE_'.length)
|
||||
: trimmed;
|
||||
|
||||
switch (normalized) {
|
||||
case 'SUCCESS':
|
||||
return PaymentState.success;
|
||||
case 'FAILED':
|
||||
return PaymentState.failed;
|
||||
case 'CANCELLED':
|
||||
return PaymentState.cancelled;
|
||||
case 'PROCESSING':
|
||||
return PaymentState.processing;
|
||||
default:
|
||||
return PaymentState.unknown;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
||||
import 'package:pshared/controllers/balance_mask/ledger_accounts.dart';
|
||||
import 'package:pshared/models/ledger/account.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/config.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/header.dart';
|
||||
@@ -25,8 +26,8 @@ class LedgerAccountCard extends StatelessWidget {
|
||||
final money = account.balance?.balance;
|
||||
if (money == null) return '--';
|
||||
|
||||
final amount = double.tryParse(money.amount);
|
||||
if (amount == null) {
|
||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||
if (amount.isNaN) {
|
||||
return '${money.amount} ${money.currency}';
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
@@ -32,7 +33,13 @@ class _PaymentAmountWidgetState extends State<PaymentAmountWidget> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double? _parseAmount(String value) => double.tryParse(value.replaceAll(',', '.'));
|
||||
double? _parseAmount(String value) {
|
||||
final parsed = parseMoneyAmount(
|
||||
value.replaceAll(',', '.'),
|
||||
fallback: double.nan,
|
||||
);
|
||||
return parsed.isNaN ? null : parsed;
|
||||
}
|
||||
|
||||
void _syncTextWithAmount(double amount) {
|
||||
final parsedText = _parseAmount(_controller.text);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'package:pshared/models/asset.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/controllers/multiple_payouts.dart';
|
||||
|
||||
|
||||
String moneyLabel(Money? money) {
|
||||
if (money == null) return 'N/A';
|
||||
final amount = double.tryParse(money.amount);
|
||||
if (amount == null) return '${money.amount} ${money.currency}';
|
||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||
if (amount.isNaN) return '${money.amount} ${money.currency}';
|
||||
try {
|
||||
return assetToString(
|
||||
Asset(
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:pshared/provider/payment/multiple/provider.dart';
|
||||
import 'package:pshared/provider/payment/multiple/quotation.dart';
|
||||
import 'package:pshared/provider/payment/payments.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/models/multiple_payouts/csv_row.dart';
|
||||
import 'package:pweb/models/multiple_payouts/state.dart';
|
||||
@@ -93,8 +94,8 @@ class MultiplePayoutsProvider extends ChangeNotifier {
|
||||
|
||||
double total = 0;
|
||||
for (final row in _rows) {
|
||||
final value = double.tryParse(row.amount);
|
||||
if (value == null) return null;
|
||||
final value = parseMoneyAmount(row.amount, fallback: double.nan);
|
||||
if (value.isNaN) return null;
|
||||
total += value;
|
||||
}
|
||||
return Money(amount: amountToString(total), currency: currency);
|
||||
@@ -121,10 +122,10 @@ class MultiplePayoutsProvider extends ChangeNotifier {
|
||||
final fee = aggregateFeeAmountFor(sourceWallet);
|
||||
if (debit == null || fee == null) return null;
|
||||
|
||||
final debitValue = double.tryParse(debit.amount);
|
||||
final feeValue = double.tryParse(fee.amount);
|
||||
final debitValue = parseMoneyAmount(debit.amount, fallback: double.nan);
|
||||
final feeValue = parseMoneyAmount(fee.amount, fallback: double.nan);
|
||||
if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null;
|
||||
if (debitValue == null || feeValue == null || debitValue <= 0) return null;
|
||||
if (debitValue.isNaN || feeValue.isNaN || debitValue <= 0) return null;
|
||||
return (feeValue / debitValue) * 100;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/models/multiple_payouts/csv_row.dart';
|
||||
|
||||
|
||||
@@ -79,8 +81,8 @@ class MultipleCsvParser {
|
||||
throw FormatException('CSV row ${i + 1}: amount is required');
|
||||
}
|
||||
|
||||
final parsedAmount = double.tryParse(amount);
|
||||
if (parsedAmount == null || parsedAmount <= 0) {
|
||||
final parsedAmount = parseMoneyAmount(amount, fallback: double.nan);
|
||||
if (parsedAmount.isNaN || parsedAmount <= 0) {
|
||||
throw FormatException(
|
||||
'CSV row ${i + 1}: amount must be greater than 0',
|
||||
);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:pshared/models/payment/operation.dart';
|
||||
import 'package:pshared/models/payment/payment.dart';
|
||||
import 'package:pshared/models/payment/status.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/models/payment_state.dart';
|
||||
|
||||
|
||||
OperationItem mapPaymentToOperation(Payment payment) {
|
||||
@@ -8,9 +11,11 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
||||
final settlement = payment.lastQuote?.expectedSettlementAmount;
|
||||
final amountMoney = debit ?? settlement;
|
||||
|
||||
final amount = _parseAmount(amountMoney?.amount);
|
||||
final amount = parseMoneyAmount(amountMoney?.amount);
|
||||
final currency = amountMoney?.currency ?? '';
|
||||
final toAmount = settlement == null ? amount : _parseAmount(settlement.amount);
|
||||
final toAmount = settlement == null
|
||||
? amount
|
||||
: parseMoneyAmount(settlement.amount);
|
||||
final toCurrency = settlement?.currency ?? currency;
|
||||
|
||||
final payId = _firstNonEmpty([
|
||||
@@ -48,14 +53,15 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
||||
}
|
||||
|
||||
OperationStatus statusFromPayment(Payment payment) {
|
||||
final normalized = _normalizePaymentState(payment.state);
|
||||
switch (normalized) {
|
||||
case 'SUCCESS':
|
||||
final state = paymentStateFromRaw(payment.state);
|
||||
switch (state) {
|
||||
case PaymentState.success:
|
||||
return OperationStatus.success;
|
||||
case 'FAILED':
|
||||
case 'CANCELLED':
|
||||
case PaymentState.failed:
|
||||
case PaymentState.cancelled:
|
||||
return OperationStatus.error;
|
||||
default:
|
||||
case PaymentState.processing:
|
||||
case PaymentState.unknown:
|
||||
return OperationStatus.processing;
|
||||
}
|
||||
}
|
||||
@@ -102,16 +108,3 @@ String? _firstNonEmpty(List<String?> values) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _normalizePaymentState(String? raw) {
|
||||
final trimmed = (raw ?? '').trim().toUpperCase();
|
||||
if (trimmed.startsWith('PAYMENT_STATE_')) {
|
||||
return trimmed.substring('PAYMENT_STATE_'.length);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
double _parseAmount(String? amount) {
|
||||
if (amount == null || amount.trim().isEmpty) return 0;
|
||||
return double.tryParse(amount) ?? 0;
|
||||
}
|
||||
|
||||
37
frontend/pweb/lib/utils/report/utils/format.dart
Normal file
37
frontend/pweb/lib/utils/report/utils/format.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/localization.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
|
||||
String formatMoney(Money? money, {String fallback = '-'}) {
|
||||
final amount = money?.amount.trim();
|
||||
if (amount == null || amount.isEmpty) return fallback;
|
||||
return '$amount ${money!.currency}';
|
||||
}
|
||||
|
||||
String formatAmount(double amount, String currency, {String fallback = '-'}) {
|
||||
final trimmed = currency.trim();
|
||||
if (trimmed.isEmpty) return amountToString(amount);
|
||||
final symbol = currencySymbolFromCode(trimmed);
|
||||
final suffix = symbol ?? trimmed;
|
||||
return '${amountToString(amount)} $suffix';
|
||||
}
|
||||
|
||||
String formatDateLabel(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
return dateTimeToLocalFormat(context, date.toLocal());
|
||||
}
|
||||
|
||||
String formatLongDate(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final formatter = DateFormat('d MMMM y', locale);
|
||||
return formatter.format(date.toLocal());
|
||||
}
|
||||
|
||||
String collapseWhitespace(String value) {
|
||||
return value.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
}
|
||||
Reference in New Issue
Block a user
по идее должно работать без ручного управления подпиской. update должен вызываться при любом обновлении провайдера. Соответственно, можно просто из update делать rebuild. Так не работает?