From 0091191d97c6f6bbe2e24668db833eb2e6e67ff0 Mon Sep 17 00:00:00 2001 From: Arseni Date: Fri, 13 Mar 2026 03:17:29 +0300 Subject: [PATCH] refactor of money utils with new money2 package --- frontend/pshared/lib/data/mapper/money.dart | 21 +-- .../lib/data/mapper/wallet/chain_asset.dart | 18 --- .../pshared/lib/data/mapper/wallet/ui.dart | 5 +- .../lib/data/mapper/wallet/wallet.dart | 12 +- frontend/pshared/lib/models/asset.dart | 18 --- frontend/pshared/lib/models/currency.dart | 2 +- .../pshared/lib/models/ledger/balance.dart | 2 +- frontend/pshared/lib/models/money.dart | 9 -- .../models/payment/execution_operation.dart | 2 +- .../pshared/lib/models/payment/fees/line.dart | 2 +- .../pshared/lib/models/payment/fx/quote.dart | 3 +- .../pshared/lib/models/payment/intent.dart | 4 +- .../lib/models/payment/quote/amounts.dart | 3 +- .../pshared/lib/models/payment/wallet.dart | 2 +- frontend/pshared/lib/models/wallet/asset.dart | 10 +- .../pshared/lib/models/wallet/balance.dart | 2 +- .../lib/models/wallet/chain_asset.dart | 12 -- .../pshared/lib/models/wallet/wallet.dart | 6 +- frontend/pshared/lib/provider/ledger.dart | 3 +- .../payment/quotation/intent_builder.dart | 89 ++++++----- .../provider/payment/quotation/quotation.dart | 22 +-- .../pshared/lib/provider/payment/wallets.dart | 10 +- frontend/pshared/lib/service/ledger.dart | 2 +- .../pshared/lib/service/payment/wallets.dart | 15 +- frontend/pshared/lib/service/wallet.dart | 17 ++- frontend/pshared/lib/utils/currency.dart | 140 ++++++++---------- frontend/pshared/lib/utils/money.dart | 60 ++++---- .../pshared/lib/utils/payment/fx_helpers.dart | 13 +- .../payment/quotation_currency_resolver.dart | 2 +- .../lib/utils/payment/quote_helpers.dart | 103 +++++-------- frontend/pshared/pubspec.yaml | 1 + .../operations/wallet_transactions.dart | 102 ------------- .../controllers/payments/amount_field.dart | 19 ++- .../controllers/payments/recent_payments.dart | 1 - .../controllers/payouts/multiple_payouts.dart | 4 +- frontend/pweb/lib/main.dart | 15 -- .../lib/models/wallet/wallet_transaction.dart | 4 +- .../buttons/balance/add/constants.dart | 4 +- .../dashboard/buttons/balance/add/dialog.dart | 13 +- .../dashboard/buttons/balance/add/form.dart | 8 +- .../balance/add/ledger/currency_item.dart | 2 +- .../buttons/balance/add/ledger/fields.dart | 6 +- .../balance/add/managed_wallet_fields.dart | 6 +- .../dashboard/buttons/balance/amount.dart | 9 +- .../pages/dashboard/payouts/amount/field.dart | 4 +- .../multiple/panels/source_quote/summary.dart | 19 +-- .../multiple/sections/history/widget.dart | 5 +- .../pages/dashboard/payouts/summary/fee.dart | 3 +- .../payouts/summary/recipient_receives.dart | 3 +- .../pages/dashboard/payouts/summary/row.dart | 10 +- .../dashboard/payouts/summary/total.dart | 7 +- .../dashboard/payouts/summary/widget.dart | 15 +- .../lib/pages/payout_page/wallet/card.dart | 13 +- .../wallet/currency_symbol_avatar.dart | 26 ++++ .../edit/fields/ledger/balance_formatter.dart | 10 +- .../payout_page/wallet/history/filters.dart | 96 ------------ .../pweb/lib/pages/report/cards/items.dart | 13 +- .../lib/pages/report/details/sections.dart | 1 - .../lib/pages/report/details/sections/fx.dart | 40 +++-- .../report/details/summary_card/widget.dart | 4 +- .../lib/pages/report/operations/actions.dart | 8 +- frontend/pweb/lib/pages/report/table/row.dart | 16 +- .../pweb/lib/providers/multiple_payouts.dart | 37 +++-- .../lib/providers/wallet_transactions.dart | 48 ------ .../lib/services/wallet_transactions.dart | 109 -------------- frontend/pweb/lib/utils/money_display.dart | 91 ++---------- .../utils/payment/multiple/csv_parser.dart | 6 +- .../payment/multiple/intent_builder.dart | 9 +- frontend/pweb/lib/utils/report/format.dart | 7 +- .../pweb/lib/utils/report/payment_mapper.dart | 9 +- .../balance_formatter.dart | 21 ++- frontend/pweb/pubspec.yaml | 2 +- 72 files changed, 453 insertions(+), 982 deletions(-) delete mode 100644 frontend/pshared/lib/data/mapper/wallet/chain_asset.dart delete mode 100644 frontend/pshared/lib/models/asset.dart delete mode 100644 frontend/pshared/lib/models/money.dart delete mode 100644 frontend/pshared/lib/models/wallet/chain_asset.dart delete mode 100644 frontend/pweb/lib/controllers/operations/wallet_transactions.dart create mode 100644 frontend/pweb/lib/pages/payout_page/wallet/currency_symbol_avatar.dart delete mode 100644 frontend/pweb/lib/pages/payout_page/wallet/history/filters.dart delete mode 100644 frontend/pweb/lib/providers/wallet_transactions.dart delete mode 100644 frontend/pweb/lib/services/wallet_transactions.dart diff --git a/frontend/pshared/lib/data/mapper/money.dart b/frontend/pshared/lib/data/mapper/money.dart index a2d8d9b3..6f8a83d9 100644 --- a/frontend/pshared/lib/data/mapper/money.dart +++ b/frontend/pshared/lib/data/mapper/money.dart @@ -1,17 +1,20 @@ +import 'package:money2/money2.dart'; + import 'package:pshared/data/dto/money.dart'; -import 'package:pshared/models/money.dart'; +import 'package:pshared/utils/money.dart'; extension MoneyMapper on Money { - MoneyDTO toDTO() => MoneyDTO( - amount: amount, - currency: currency, - ); + MoneyDTO toDTO() => + MoneyDTO(amount: toDecimal().toString(), currency: currency.isoCode); } extension MoneyDTOMapper on MoneyDTO { - Money toDomain() => Money( - amount: amount, - currency: currency, - ); + Money toDomain() { + final parsed = parseMoneyWithCurrencyCode(amount, currency); + if (parsed == null) { + throw FormatException('Invalid money dto: $currency $amount'); + } + return parsed; + } } diff --git a/frontend/pshared/lib/data/mapper/wallet/chain_asset.dart b/frontend/pshared/lib/data/mapper/wallet/chain_asset.dart deleted file mode 100644 index 5a8e3528..00000000 --- a/frontend/pshared/lib/data/mapper/wallet/chain_asset.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:pshared/data/dto/wallet/chain_asset.dart'; -import 'package:pshared/data/mapper/payment/enums.dart'; -import 'package:pshared/models/wallet/chain_asset.dart'; - - -extension ChainAssetDTOMapper on ChainAssetDTO { - ChainAsset toDomain() => ChainAsset( - chain: chainNetworkFromValue(chain), - tokenSymbol: tokenSymbol, - ); -} - -extension ChainAssetMapper on ChainAsset { - ChainAssetDTO toDTO() => ChainAssetDTO( - chain: chainNetworkToValue(chain), - tokenSymbol: tokenSymbol, - ); -} diff --git a/frontend/pshared/lib/data/mapper/wallet/ui.dart b/frontend/pshared/lib/data/mapper/wallet/ui.dart index e1e27aec..c73d6189 100644 --- a/frontend/pshared/lib/data/mapper/wallet/ui.dart +++ b/frontend/pshared/lib/data/mapper/wallet/ui.dart @@ -1,16 +1,13 @@ 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: parseMoneyAmount( - availableMoney?.amount ?? balance?.available?.amount, - ), + balance: availableMoney?.toDouble() ?? balance?.available?.toDouble() ?? 0, currency: currencyStringToCode(asset.tokenSymbol), calculatedAt: balance?.calculatedAt ?? DateTime.now(), depositAddress: depositAddress, diff --git a/frontend/pshared/lib/data/mapper/wallet/wallet.dart b/frontend/pshared/lib/data/mapper/wallet/wallet.dart index 687e2541..73b5df62 100644 --- a/frontend/pshared/lib/data/mapper/wallet/wallet.dart +++ b/frontend/pshared/lib/data/mapper/wallet/wallet.dart @@ -16,15 +16,19 @@ extension WalletDTOMapper on WalletDTO { depositAddress: depositAddress, status: status, metadata: metadata, - createdAt: (createdAt == null || createdAt!.isEmpty) ? null : DateTime.tryParse(createdAt!), - updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!), + createdAt: (createdAt == null || createdAt!.isEmpty) + ? null + : DateTime.tryParse(createdAt!), + updatedAt: (updatedAt == null || updatedAt!.isEmpty) + ? null + : DateTime.tryParse(updatedAt!), balance: balance?.toDomain(), availableMoney: balance?.available?.toDomain(), describable: newDescribable( name: name.isNotEmpty ? name : (metadata?['name']?.toString() ?? ''), description: (description != null && description!.isNotEmpty) - ? description - : metadata?['description'], + ? description + : metadata?['description'], ), ); } diff --git a/frontend/pshared/lib/models/asset.dart b/frontend/pshared/lib/models/asset.dart deleted file mode 100644 index 19db8496..00000000 --- a/frontend/pshared/lib/models/asset.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:pshared/models/currency.dart'; -import 'package:pshared/utils/currency.dart'; - - -class Asset { - final Currency currency; - final double amount; - - const Asset({ - required this.currency, - required this.amount, - }); -} - -Asset createAsset(String currencyCode, String amount) => Asset( - currency: currencyStringToCode(currencyCode), - amount: double.parse(amount), -); \ No newline at end of file diff --git a/frontend/pshared/lib/models/currency.dart b/frontend/pshared/lib/models/currency.dart index da336dbc..8b5e0b0e 100644 --- a/frontend/pshared/lib/models/currency.dart +++ b/frontend/pshared/lib/models/currency.dart @@ -1 +1 @@ -enum Currency {usd, eur, rub, usdt, usdc} \ No newline at end of file +enum CurrencyCode {usd, eur, rub, usdt, usdc} \ No newline at end of file diff --git a/frontend/pshared/lib/models/ledger/balance.dart b/frontend/pshared/lib/models/ledger/balance.dart index c4b407bd..5e52b9e5 100644 --- a/frontend/pshared/lib/models/ledger/balance.dart +++ b/frontend/pshared/lib/models/ledger/balance.dart @@ -1,4 +1,4 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; class LedgerBalance { diff --git a/frontend/pshared/lib/models/money.dart b/frontend/pshared/lib/models/money.dart deleted file mode 100644 index e971b5d9..00000000 --- a/frontend/pshared/lib/models/money.dart +++ /dev/null @@ -1,9 +0,0 @@ -class Money { - final String amount; - final String currency; - - const Money({ - required this.amount, - required this.currency, - }); -} diff --git a/frontend/pshared/lib/models/payment/execution_operation.dart b/frontend/pshared/lib/models/payment/execution_operation.dart index 8ab82ea3..1ee99028 100644 --- a/frontend/pshared/lib/models/payment/execution_operation.dart +++ b/frontend/pshared/lib/models/payment/execution_operation.dart @@ -1,4 +1,4 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; class PaymentExecutionOperation { diff --git a/frontend/pshared/lib/models/payment/fees/line.dart b/frontend/pshared/lib/models/payment/fees/line.dart index 9c8c22c2..b5d3949b 100644 --- a/frontend/pshared/lib/models/payment/fees/line.dart +++ b/frontend/pshared/lib/models/payment/fees/line.dart @@ -1,4 +1,4 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; class FeeLine { diff --git a/frontend/pshared/lib/models/payment/fx/quote.dart b/frontend/pshared/lib/models/payment/fx/quote.dart index 1e62b9de..1092300b 100644 --- a/frontend/pshared/lib/models/payment/fx/quote.dart +++ b/frontend/pshared/lib/models/payment/fx/quote.dart @@ -1,4 +1,5 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; + class FxQuote { final String? quoteRef; diff --git a/frontend/pshared/lib/models/payment/intent.dart b/frontend/pshared/lib/models/payment/intent.dart index 5fe55a6c..7934e2f4 100644 --- a/frontend/pshared/lib/models/payment/intent.dart +++ b/frontend/pshared/lib/models/payment/intent.dart @@ -1,11 +1,13 @@ +import 'package:money2/money2.dart'; + import 'package:pshared/models/payment/fees/treatment.dart'; import 'package:pshared/models/payment/fx/intent.dart'; import 'package:pshared/models/payment/kind.dart'; import 'package:pshared/models/payment/customer.dart'; import 'package:pshared/models/payment/methods/data.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/settlement_mode.dart'; + class PaymentIntent { final PaymentKind kind; final String? sourceRef; diff --git a/frontend/pshared/lib/models/payment/quote/amounts.dart b/frontend/pshared/lib/models/payment/quote/amounts.dart index e5fc9ebe..3fed78c4 100644 --- a/frontend/pshared/lib/models/payment/quote/amounts.dart +++ b/frontend/pshared/lib/models/payment/quote/amounts.dart @@ -1,4 +1,5 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; + class QuoteAmounts { final Money? sourcePrincipal; diff --git a/frontend/pshared/lib/models/payment/wallet.dart b/frontend/pshared/lib/models/payment/wallet.dart index 899143a9..d340b502 100644 --- a/frontend/pshared/lib/models/payment/wallet.dart +++ b/frontend/pshared/lib/models/payment/wallet.dart @@ -7,7 +7,7 @@ class Wallet implements Describable { final String id; final String walletUserID; // ID or number that we show the user final double balance; - final Currency currency; + final CurrencyCode currency; final DateTime calculatedAt; final String? depositAddress; final ChainNetwork? network; diff --git a/frontend/pshared/lib/models/wallet/asset.dart b/frontend/pshared/lib/models/wallet/asset.dart index 08fb274e..9cdb8ef4 100644 --- a/frontend/pshared/lib/models/wallet/asset.dart +++ b/frontend/pshared/lib/models/wallet/asset.dart @@ -1,12 +1,14 @@ -import 'package:pshared/models/wallet/chain_asset.dart'; +import 'package:pshared/models/payment/chain_network.dart'; -class WalletAsset extends ChainAsset { +class WalletAsset { + final ChainNetwork chain; + final String tokenSymbol; final String contractAddress; const WalletAsset({ - required super.chain, - required super.tokenSymbol, + required this.chain, + required this.tokenSymbol, required this.contractAddress, }); } diff --git a/frontend/pshared/lib/models/wallet/balance.dart b/frontend/pshared/lib/models/wallet/balance.dart index ffde28b6..7367adfa 100644 --- a/frontend/pshared/lib/models/wallet/balance.dart +++ b/frontend/pshared/lib/models/wallet/balance.dart @@ -1,4 +1,4 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; class WalletBalance { diff --git a/frontend/pshared/lib/models/wallet/chain_asset.dart b/frontend/pshared/lib/models/wallet/chain_asset.dart deleted file mode 100644 index 65c05358..00000000 --- a/frontend/pshared/lib/models/wallet/chain_asset.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:pshared/models/payment/chain_network.dart'; - - -class ChainAsset { - final ChainNetwork chain; - final String tokenSymbol; - - const ChainAsset({ - required this.chain, - required this.tokenSymbol, - }); -} diff --git a/frontend/pshared/lib/models/wallet/wallet.dart b/frontend/pshared/lib/models/wallet/wallet.dart index d78c01dc..d72a2863 100644 --- a/frontend/pshared/lib/models/wallet/wallet.dart +++ b/frontend/pshared/lib/models/wallet/wallet.dart @@ -1,5 +1,5 @@ import 'package:pshared/models/describable.dart'; -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; import 'package:pshared/models/wallet/asset.dart'; import 'package:pshared/models/wallet/balance.dart'; @@ -39,9 +39,7 @@ class WalletModel implements Describable { required this.describable, }); - WalletModel copyWith({ - Describable? describable, - }) => WalletModel( + WalletModel copyWith({Describable? describable}) => WalletModel( walletRef: walletRef, organizationRef: organizationRef, ownerRef: ownerRef, diff --git a/frontend/pshared/lib/provider/ledger.dart b/frontend/pshared/lib/provider/ledger.dart index 02209421..e3db7532 100644 --- a/frontend/pshared/lib/provider/ledger.dart +++ b/frontend/pshared/lib/provider/ledger.dart @@ -15,6 +15,7 @@ import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/ledger.dart'; import 'package:pshared/utils/exception.dart'; + class LedgerAccountsProvider with ChangeNotifier { final LedgerService _service; OrganizationsProvider? _organizations; @@ -179,7 +180,7 @@ class LedgerAccountsProvider with ChangeNotifier { Future create({ required Describable describable, - required Currency currency, + required CurrencyCode currency, String? ownerRef, }) async { final org = _organizations; diff --git a/frontend/pshared/lib/provider/payment/quotation/intent_builder.dart b/frontend/pshared/lib/provider/payment/quotation/intent_builder.dart index 477a39ab..7016e554 100644 --- a/frontend/pshared/lib/provider/payment/quotation/intent_builder.dart +++ b/frontend/pshared/lib/provider/payment/quotation/intent_builder.dart @@ -1,3 +1,5 @@ +import 'package:money2/money2.dart'; + import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/models/payment/asset.dart'; import 'package:pshared/models/payment/chain_network.dart'; @@ -13,19 +15,18 @@ import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/ledger.dart'; import 'package:pshared/models/payment/methods/managed_wallet.dart'; import 'package:pshared/models/payment/methods/type.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/settlement_mode.dart'; import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/recipient/provider.dart'; +import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/payment/fx_helpers.dart'; class QuotationIntentBuilder { static const String _settlementCurrency = 'RUB'; - static const String _addressBookCustomerFallbackId = 'address_book_customer'; PaymentIntent? build({ required PaymentAmountProvider payment, @@ -38,10 +39,9 @@ class QuotationIntentBuilder { final paymentData = flow.selectedPaymentData; final selectedMethod = flow.selectedMethod; final amountValue = payment.amount; - if (sourceMethod == null || sourceCurrency == null || paymentData == null) { + if (sourceCurrency == null) { return null; } - if (amountValue == null) return null; final customer = _buildCustomer( recipient: recipients.currentObject, @@ -51,22 +51,22 @@ class QuotationIntentBuilder { final amountCurrency = payment.settlementMode == SettlementMode.fixReceived ? _settlementCurrency : sourceCurrency; - final amount = Money( - amount: amountValue.toString(), - currency: amountCurrency, - ); + final currency = money2CurrencyFromCode(amountCurrency); + if (currency == null) return null; + final amount = amountValue == null + ? null + : Money.fromNumWithCurrency(amountValue, currency); final isLedgerSource = source.selectedLedgerAccount != null; final isCryptoToCrypto = paymentData is CryptoAddressPaymentMethod && (paymentData.asset?.tokenSymbol ?? '').trim().toUpperCase() == - amount.currency; + sourceCurrency.trim().toUpperCase(); final fxIntent = _buildFxIntent( sourceCurrency: sourceCurrency, settlementMode: payment.settlementMode, isLedgerSource: isLedgerSource, enabled: !isCryptoToCrypto, ); - final comment = _resolveComment(payment.comment); return PaymentIntent( kind: PaymentKind.payout, amount: amount, @@ -77,7 +77,7 @@ class QuotationIntentBuilder { ? FeeTreatment.addToSource : FeeTreatment.deductFromDestination, settlementMode: payment.settlementMode, - comment: comment, + comment: payment.comment, customer: customer, ); } @@ -94,14 +94,9 @@ class QuotationIntentBuilder { // BFF maps only settlement currency + fx side, then quotation derives pair. // For ledger this preserves source debit in ledger currency (e.g. USDT). if (isLedgerSource && settlementMode == SettlementMode.fixReceived) { - final base = sourceCurrency.trim(); - final quote = _settlementCurrency; - if (base.isEmpty || base.toUpperCase() == quote.toUpperCase()) { - return null; - } return FxIntent( - pair: CurrencyPair(base: base, quote: quote), - side: FxSide.sellBaseBuyQuote, + pair: CurrencyPair(base: sourceCurrency, quote: _settlementCurrency), + side: FxSide.buyBaseSellQuote, ); } @@ -137,39 +132,59 @@ class QuotationIntentBuilder { required PaymentMethod? method, required PaymentMethodData? data, }) { - final name = recipient?.name.trim(); - if (name == null || name.isEmpty) return null; - final id = recipient?.id.trim(); - final customerId = id == null || id.isEmpty - ? _addressBookCustomerFallbackId - : id; + final customerId = recipient?.id.trim() ?? ''; + final card = _resolveCard(method: method, data: data); + final fromRecipient = _buildCustomerFromName( + customerId: customerId, + fullName: recipient?.name, + country: card?.country, + ); + if (fromRecipient != null) return fromRecipient; - final parts = name.split(RegExp(r'\s+')); + if (card != null) { + final firstName = _normalizedOrNull(card.firstName); + final lastName = _normalizedOrNull(card.lastName); + if (firstName == null && lastName == null) return null; + return Customer( + id: customerId, + firstName: firstName, + lastName: lastName, + country: card.country, + ); + } + return null; + } + + CardPaymentMethod? _resolveCard({ + required PaymentMethod? method, + required PaymentMethodData? data, + }) => method?.cardData ?? (data is CardPaymentMethod ? data : null); + + Customer? _buildCustomerFromName({ + required String customerId, + required String? fullName, + String? country, + }) { + final normalizedName = _normalizedOrNull(fullName); + if (normalizedName == null) return null; + final parts = normalizedName.split(RegExp(r'\s+')); final firstName = parts.isNotEmpty ? parts.first : null; final lastName = parts.length >= 2 ? parts.last : null; final middleName = parts.length > 2 ? parts.sublist(1, parts.length - 1).join(' ') : null; - return Customer( id: customerId, firstName: firstName, middleName: middleName, lastName: lastName, - country: _resolveCustomerCountry(method: method, data: data), + country: country, ); } - String? _resolveCustomerCountry({ - required PaymentMethod? method, - required PaymentMethodData? data, - }) { - final card = method?.cardData ?? (data is CardPaymentMethod ? data : null); - return card?.country; - } - - String? _resolveComment(String comment) { - final normalized = comment.trim(); + String? _normalizedOrNull(String? value) { + if (value == null) return null; + final normalized = value.trim(); return normalized.isEmpty ? null : normalized; } } diff --git a/frontend/pshared/lib/provider/payment/quotation/quotation.dart b/frontend/pshared/lib/provider/payment/quotation/quotation.dart index acb7a853..bd871e78 100644 --- a/frontend/pshared/lib/provider/payment/quotation/quotation.dart +++ b/frontend/pshared/lib/provider/payment/quotation/quotation.dart @@ -6,13 +6,13 @@ import 'package:logging/logging.dart'; import 'package:uuid/uuid.dart'; +import 'package:money2/money2.dart'; + import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart'; -import 'package:pshared/models/asset.dart'; import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/payment/quote/quote.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/auto_refresh_mode.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/payment/amount.dart'; @@ -79,20 +79,12 @@ class QuotationProvider extends ChangeNotifier { return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true); } - Asset? get fee => _assetFromMoney(quoteFeeTotal(quotation)); - Asset? get total => _assetFromMoney( - quoteSourceDebitTotal( - quotation, - preferredSourceCurrency: _sourceCurrencyCode, - ), + Money? get fee => quoteFeeTotal(quotation); + Money? get total => quoteSourceDebitTotal( + quotation, + preferredSourceCurrency: _sourceCurrencyCode, ); - Asset? get recipientGets => - _assetFromMoney(quotation?.amounts?.destinationSettlement); - - Asset? _assetFromMoney(Money? money) { - if (money == null) return null; - return createAsset(money.currency, money.amount); - } + Money? get recipientGets => quotation?.amounts?.destinationSettlement; void _setResource(Resource quotation) { _quotation = quotation; diff --git a/frontend/pshared/lib/provider/payment/wallets.dart b/frontend/pshared/lib/provider/payment/wallets.dart index 9c2ce2e2..7ea9d0bc 100644 --- a/frontend/pshared/lib/provider/payment/wallets.dart +++ b/frontend/pshared/lib/provider/payment/wallets.dart @@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart'; import 'package:collection/collection.dart'; +import 'package:pshared/models/currency.dart'; import 'package:pshared/models/describable.dart'; +import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/wallet.dart'; -import 'package:pshared/models/wallet/chain_asset.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/wallets.dart'; import 'package:pshared/utils/exception.dart'; + class WalletsProvider with ChangeNotifier { final WalletsService _service; OrganizationsProvider? _organizations; @@ -180,7 +182,8 @@ class WalletsProvider with ChangeNotifier { Future create({ required Describable describable, - required ChainAsset asset, + required ChainNetwork chain, + required CurrencyCode currency, required String? ownerRef, }) async { final org = _organizations; @@ -195,7 +198,8 @@ class WalletsProvider with ChangeNotifier { await _service.create( organizationRef: org.current.id, describable: describable, - asset: asset, + chain: chain, + currency: currency, ownerRef: ownerRef, ); await loadWalletsWithBalances(); diff --git a/frontend/pshared/lib/service/ledger.dart b/frontend/pshared/lib/service/ledger.dart index 36541419..028550a7 100644 --- a/frontend/pshared/lib/service/ledger.dart +++ b/frontend/pshared/lib/service/ledger.dart @@ -43,7 +43,7 @@ class LedgerService { required String organizationRef, required Describable describable, required String? ownerRef, - required Currency currency, + required CurrencyCode currency, }) async => AuthorizationService.getPOSTResponse( _objectType, '/$organizationRef', diff --git a/frontend/pshared/lib/service/payment/wallets.dart b/frontend/pshared/lib/service/payment/wallets.dart index f1a42853..733650ca 100644 --- a/frontend/pshared/lib/service/payment/wallets.dart +++ b/frontend/pshared/lib/service/payment/wallets.dart @@ -1,9 +1,9 @@ import 'package:pshared/data/mapper/wallet/ui.dart'; +import 'package:pshared/models/currency.dart'; import 'package:pshared/models/describable.dart'; +import 'package:pshared/models/payment/chain_network.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 { @@ -12,7 +12,8 @@ abstract class WalletsService { Future create({ required String organizationRef, required Describable describable, - required ChainAsset asset, + required ChainNetwork chain, + required CurrencyCode currency, required String? ownerRef, }); } @@ -30,19 +31,21 @@ class ApiWalletsService implements WalletsService { organizationRef: organizationRef, walletRef: walletRef, ); - return parseMoneyAmount(balance.available?.amount); + return balance.available?.toDouble() ?? 0; } @override Future create({ required String organizationRef, required Describable describable, - required ChainAsset asset, + required ChainNetwork chain, + required CurrencyCode currency, required String? ownerRef, }) => shared_wallet_service.WalletService.create( organizationRef: organizationRef, describable: describable, - asset: asset, + chain: chain, + currency: currency, ownerRef: ownerRef, ); } diff --git a/frontend/pshared/lib/service/wallet.dart b/frontend/pshared/lib/service/wallet.dart index 15d0b1e4..d5609c71 100644 --- a/frontend/pshared/lib/service/wallet.dart +++ b/frontend/pshared/lib/service/wallet.dart @@ -1,15 +1,18 @@ import 'package:pshared/api/requests/wallet/create.dart'; import 'package:pshared/api/responses/wallet_balance.dart'; import 'package:pshared/api/responses/wallets.dart'; +import 'package:pshared/data/dto/wallet/chain_asset.dart'; import 'package:pshared/data/mapper/describable.dart'; -import 'package:pshared/data/mapper/wallet/chain_asset.dart'; +import 'package:pshared/data/mapper/payment/enums.dart'; import 'package:pshared/data/mapper/wallet/response.dart'; +import 'package:pshared/models/currency.dart'; import 'package:pshared/models/describable.dart'; +import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/wallet/balance.dart'; -import 'package:pshared/models/wallet/chain_asset.dart'; import 'package:pshared/models/wallet/wallet.dart'; import 'package:pshared/service/authorization/service.dart'; import 'package:pshared/service/services.dart'; +import 'package:pshared/utils/currency.dart'; class WalletService { @@ -37,13 +40,19 @@ class WalletService { static Future create({ required String organizationRef, required Describable describable, - required ChainAsset asset, + required ChainNetwork chain, + required CurrencyCode currency, required String? ownerRef, }) async => AuthorizationService.getPOSTResponse( _objectType, '/$organizationRef', CreateWalletRequest( - asset: asset.toDTO(), + asset: ChainAssetDTO( + chain: chainNetworkToValue(chain), + tokenSymbol: + money2CurrencyFromCode(currencyCodeToString(currency))?.isoCode ?? + currencyCodeToString(currency), + ), describable: describable.toDTO(), ownerRef: ownerRef, ).toJson(), diff --git a/frontend/pshared/lib/utils/currency.dart b/frontend/pshared/lib/utils/currency.dart index f44580e0..30a509b5 100644 --- a/frontend/pshared/lib/utils/currency.dart +++ b/frontend/pshared/lib/utils/currency.dart @@ -1,102 +1,78 @@ -import 'package:flutter/material.dart'; +import 'package:money2/money2.dart'; -import 'package:pshared/models/asset.dart'; import 'package:pshared/models/currency.dart'; -const nonBreakingSpace = '\u00A0'; +final Currency _usdtCurrency = Currency.create( + 'USDT', + 6, + symbol: '₮', + isIso: false, + country: 'Digital', + unit: 'Tether', + name: 'Tether', +); -String withTrailingNonBreakingSpace(String value) { - return '$value$nonBreakingSpace'; -} +final Currency _usdcCurrency = Currency.create( + 'USDC', + 6, + symbol: r'($)', + isIso: false, + country: 'Digital', + unit: 'USD Coin', + name: 'USD Coin', +); -String joinWithNonBreakingSpace(String left, String right) { - return '$left$nonBreakingSpace$right'; -} +final Map _commonCurrenciesByCode = + { + for (final currency in CommonCurrencies().asList()) + currency.isoCode: currency, + _usdtCurrency.isoCode: _usdtCurrency, + _usdcCurrency.isoCode: _usdcCurrency, + }; -String currencyCodeToSymbol(Currency currencyCode) { - switch (currencyCode) { - case Currency.usd: - return '\$'; - case Currency.usdt: - return '₮'; - case Currency.usdc: - return '\$'; - case Currency.rub: - return '₽'; - case Currency.eur: - return '€'; +String currencyCodeToSymbol(CurrencyCode currencyCode) { + final symbol = currencySymbolFromCode(currencyCodeToString(currencyCode)); + if (symbol == null || symbol.trim().isEmpty) { + return currencyCodeToString(currencyCode); } + return symbol; } -String amountToString(double amount) { - return amount.toStringAsFixed(2); -} - -String currencyToString(Currency currencyCode, double amount) { - return joinWithNonBreakingSpace( - currencyCodeToSymbol(currencyCode), - amountToString(amount), - ); -} - -String assetToString(Asset asset) { - return currencyToString(asset.currency, asset.amount); -} - -Currency currencyStringToCode(String currencyCode) { - switch (currencyCode) { - case 'USD': - return Currency.usd; - case 'USDT': - return Currency.usdt; - case 'USDC': - return Currency.usdc; - case 'RUB': - return Currency.rub; - case 'EUR': - return Currency.eur; - default: - throw ArgumentError('Unknown currency code: $currencyCode'); +String currencyToString(CurrencyCode currencyCode, double amount) { + final code = currencyCodeToString(currencyCode); + final currency = money2CurrencyFromCode(code); + if (currency == null) { + return '$amount $code'; } + final money = Money.fromNumWithCurrency(amount, currency); + return money.toString(); } -String currencyCodeToString(Currency currencyCode) { - switch (currencyCode) { - case Currency.usd: - return 'USD'; - case Currency.usdt: - return 'USDT'; - case Currency.usdc: - return 'USDC'; - case Currency.rub: - return 'RUB'; - case Currency.eur: - return 'EUR'; +CurrencyCode currencyStringToCode(String currencyCode) { + final normalized = currencyCode.trim().toUpperCase(); + for (final value in CurrencyCode.values) { + if (currencyCodeToString(value) == normalized) { + return value; + } } + + throw ArgumentError('Unknown currency code: $currencyCode'); } -IconData iconForCurrencyType(Currency currencyCode) { - switch (currencyCode) { - case Currency.usd: - return Icons.currency_exchange; - case Currency.eur: - return Icons.currency_exchange; - case Currency.rub: - return Icons.currency_ruble; - case Currency.usdt: - return Icons.currency_exchange; - case Currency.usdc: - return Icons.money; - } +String currencyCodeToString(CurrencyCode currencyCode) { + return currencyCode.name.toUpperCase(); } String? currencySymbolFromCode(String? code) { - final normalized = code?.trim(); - if (normalized == null || normalized.isEmpty) return null; - try { - return currencyCodeToSymbol(currencyStringToCode(normalized.toUpperCase())); - } catch (_) { - return null; - } + final currency = money2CurrencyFromCode(code); + if (currency == null) return null; + final symbol = currency.symbol.trim(); + return symbol.isEmpty ? null : symbol; +} + +Currency? money2CurrencyFromCode(String? code) { + final normalized = code?.trim().toUpperCase(); + if (normalized == null || normalized.isEmpty) return null; + return _commonCurrenciesByCode[normalized] ?? Currencies().find(normalized); } diff --git a/frontend/pshared/lib/utils/money.dart b/frontend/pshared/lib/utils/money.dart index 328d187f..33c1e0ab 100644 --- a/frontend/pshared/lib/utils/money.dart +++ b/frontend/pshared/lib/utils/money.dart @@ -1,41 +1,35 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; import 'package:pshared/utils/currency.dart'; +const String _decimalMoneyPattern = '0.##################'; -double parseMoneyAmount(String? raw, {double fallback = 0}) { - final trimmed = raw?.trim(); - if (trimmed == null || trimmed.isEmpty) return fallback; - return double.tryParse(trimmed) ?? fallback; +Money? parseMoneyWithCurrency(String? amount, Currency? currency) { + if (currency == null) return null; + final value = _normalizeMoneyAmount(amount); + if (value == null || value.isEmpty) return null; + + try { + return Money.parseWithCurrency( + value, + currency, + pattern: _decimalMoneyPattern, + ); + } catch (_) { + return null; + } } -String formatMoneyDisplay( - Money? money, { - String fallback = '--', - String separator = ' ', - String invalidAmountFallback = '', -}) { - if (money == null) return fallback; - - final rawAmount = money.amount.trim(); - final rawCurrency = money.currency.trim(); - final parsedAmount = parseMoneyAmount(rawAmount, fallback: double.nan); - final amountToken = parsedAmount.isNaN - ? (rawAmount.isEmpty ? invalidAmountFallback : rawAmount) - : amountToString(parsedAmount); - - final symbol = currencySymbolFromCode(rawCurrency); - final normalizedSymbol = symbol?.trim() ?? ''; - final hasSymbol = normalizedSymbol.isNotEmpty; - final currencyToken = hasSymbol ? normalizedSymbol : rawCurrency; - final first = amountToken; - final second = currencyToken; - - if (first.isEmpty && second.isEmpty) return fallback; - if (first.isEmpty) return second; - if (second.isEmpty) return first; - return '$first$separator$second'; +Money? parseMoneyWithCurrencyCode(String? amount, String? currencyCode) { + return parseMoneyWithCurrency(amount, money2CurrencyFromCode(currencyCode)); } -extension MoneyAmountX on Money { - double get amountValue => parseMoneyAmount(amount); +String? _normalizeMoneyAmount(String? value) { + final normalized = value?.trim(); + if (normalized == null || normalized.isEmpty) return null; + + if (normalized.contains(',') && !normalized.contains('.')) { + return normalized.replaceAll(',', '.'); + } + + return normalized; } diff --git a/frontend/pshared/lib/utils/payment/fx_helpers.dart b/frontend/pshared/lib/utils/payment/fx_helpers.dart index df8c3aad..afca9389 100644 --- a/frontend/pshared/lib/utils/payment/fx_helpers.dart +++ b/frontend/pshared/lib/utils/payment/fx_helpers.dart @@ -1,7 +1,8 @@ +import 'package:money2/money2.dart'; + import 'package:pshared/models/payment/currency_pair.dart'; import 'package:pshared/models/payment/fx/intent.dart'; import 'package:pshared/models/payment/fx/side.dart'; -import 'package:pshared/models/money.dart'; class FxIntentHelper { @@ -37,11 +38,15 @@ class FxIntentHelper { case FxSide.unspecified: break; } - if (amount.currency == pair.base && pair.quote.isNotEmpty) return pair.quote; - if (amount.currency == pair.quote && pair.base.isNotEmpty) return pair.base; + if (amount.currency.isoCode == pair.base && pair.quote.isNotEmpty) { + return pair.quote; + } + if (amount.currency.isoCode == pair.quote && pair.base.isNotEmpty) { + return pair.base; + } if (pair.quote.isNotEmpty) return pair.quote; if (pair.base.isNotEmpty) return pair.base; } - return amount.currency; + return amount.currency.isoCode; } } diff --git a/frontend/pshared/lib/utils/payment/quotation_currency_resolver.dart b/frontend/pshared/lib/utils/payment/quotation_currency_resolver.dart index bc21f0b0..be148cea 100644 --- a/frontend/pshared/lib/utils/payment/quotation_currency_resolver.dart +++ b/frontend/pshared/lib/utils/payment/quotation_currency_resolver.dart @@ -12,7 +12,7 @@ class PaymentQuotationCurrencyResolver { PaymentMethodData? paymentData, }) { final quoteCurrency = _normalizeCurrency( - quote?.amounts?.destinationSettlement?.currency, + quote?.amounts?.destinationSettlement?.currency.isoCode, ); if (quoteCurrency != null) return quoteCurrency; diff --git a/frontend/pshared/lib/utils/payment/quote_helpers.dart b/frontend/pshared/lib/utils/payment/quote_helpers.dart index 3b782f75..db87c6f1 100644 --- a/frontend/pshared/lib/utils/payment/quote_helpers.dart +++ b/frontend/pshared/lib/utils/payment/quote_helpers.dart @@ -1,17 +1,16 @@ -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; + import 'package:pshared/models/payment/fees/line.dart'; import 'package:pshared/models/payment/quote/quote.dart'; import 'package:pshared/utils/currency.dart'; -import 'package:pshared/utils/money.dart'; Money? quoteFeeTotal(PaymentQuote? quote) { - final preferredCurrency = - quote?.amounts?.sourcePrincipal?.currency ?? - quote?.amounts?.sourceDebitTotal?.currency; return quoteFeeTotalFromLines( quote?.fees?.lines, - preferredCurrency: preferredCurrency, + preferredCurrency: + quote?.amounts?.sourcePrincipal?.currency.isoCode ?? + quote?.amounts?.sourceDebitTotal?.currency.isoCode, ); } @@ -21,7 +20,8 @@ Money? quoteSourceDebitTotal( }) { final sourceDebitTotal = quote?.amounts?.sourceDebitTotal; final preferredCurrency = _normalizeCurrency( - preferredSourceCurrency ?? quote?.amounts?.sourcePrincipal?.currency, + preferredSourceCurrency ?? + quote?.amounts?.sourcePrincipal?.currency.isoCode, ); if (sourceDebitTotal == null) { @@ -31,10 +31,9 @@ Money? quoteSourceDebitTotal( ); } - final debitCurrency = _normalizeCurrency(sourceDebitTotal.currency); if (preferredCurrency == null || - debitCurrency == null || - debitCurrency == preferredCurrency) { + _normalizeCurrency(sourceDebitTotal.currency.isoCode) == + preferredCurrency) { return sourceDebitTotal; } @@ -52,22 +51,18 @@ Money? quoteFeeTotalFromLines( if (lines == null || lines.isEmpty) return null; final normalizedPreferred = _normalizeCurrency(preferredCurrency); - final totalsByCurrency = {}; + final totalsByCurrency = {}; for (final line in lines) { - final money = line.amount; - if (money == null) continue; + final parsedAmount = line.amount; + if (parsedAmount == null) continue; - final currency = _normalizeCurrency(money.currency); - if (currency == null) continue; - - final amount = parseMoneyAmount(money.amount, fallback: double.nan); - if (amount.isNaN) continue; - - final sign = _lineSign(line.side); - final signedAmount = sign * amount.abs(); - totalsByCurrency[currency] = - (totalsByCurrency[currency] ?? 0) + signedAmount; + final currencyCode = parsedAmount.currency.isoCode; + final signedAmount = _isCreditLine(line.side) ? -parsedAmount : parsedAmount; + final current = totalsByCurrency[currencyCode]; + totalsByCurrency[currencyCode] = current == null + ? signedAmount + : current + signedAmount; } if (totalsByCurrency.isEmpty) return null; @@ -77,85 +72,59 @@ Money? quoteFeeTotalFromLines( totalsByCurrency.containsKey(normalizedPreferred) ? normalizedPreferred : totalsByCurrency.keys.first; - final total = totalsByCurrency[selectedCurrency]; - if (total == null) return null; - - return Money(amount: amountToString(total), currency: selectedCurrency); + return totalsByCurrency[selectedCurrency]; } List aggregateMoneyByCurrency(Iterable values) { - final totals = {}; + final totals = {}; for (final value in values) { if (value == null) continue; - - final currency = _normalizeCurrency(value.currency); - if (currency == null) continue; - - final amount = parseMoneyAmount(value.amount, fallback: double.nan); - if (amount.isNaN) continue; - - totals[currency] = (totals[currency] ?? 0) + amount; + final currency = value.currency.isoCode; + final current = totals[currency]; + totals[currency] = current == null ? value : current + value; } - return totals.entries - .map( - (entry) => - Money(amount: amountToString(entry.value), currency: entry.key), - ) - .toList(); + return totals.values.toList(); } Money? _rebuildSourceDebitTotal( PaymentQuote? quote, { String? preferredSourceCurrency, }) { - final sourcePrincipal = quote?.amounts?.sourcePrincipal; - if (sourcePrincipal == null) return null; + final principal = quote?.amounts?.sourcePrincipal; + if (principal == null) return null; - final principalCurrency = _normalizeCurrency(sourcePrincipal.currency); - if (principalCurrency == null) return null; + final principalCurrency = principal.currency.isoCode; if (preferredSourceCurrency != null && principalCurrency != preferredSourceCurrency) { return null; } - final principalAmount = parseMoneyAmount( - sourcePrincipal.amount, - fallback: double.nan, - ); - if (principalAmount.isNaN) return null; - - double totalAmount = principalAmount; + var totalAmount = principal; final fee = quoteFeeTotalFromLines( quote?.fees?.lines, preferredCurrency: principalCurrency, ); - if (fee != null && _normalizeCurrency(fee.currency) == principalCurrency) { - final feeAmount = parseMoneyAmount(fee.amount, fallback: double.nan); - if (!feeAmount.isNaN) { - totalAmount += feeAmount; - } + if (fee != null && fee.currency.isoCode == principalCurrency) { + totalAmount += fee; } - return Money( - amount: amountToString(totalAmount), - currency: principalCurrency, - ); + return totalAmount; } -double _lineSign(String? side) { +bool _isCreditLine(String? side) { final normalized = side?.trim().toLowerCase() ?? ''; switch (normalized) { case 'entry_side_credit': case 'credit': - return -1; + return true; default: - return 1; + return false; } } String? _normalizeCurrency(String? currency) { - final normalized = currency?.trim().toUpperCase(); + final normalized = currency?.trim(); if (normalized == null || normalized.isEmpty) return null; - return normalized; + return money2CurrencyFromCode(normalized)?.isoCode ?? normalized.toUpperCase(); } diff --git a/frontend/pshared/pubspec.yaml b/frontend/pshared/pubspec.yaml index 23578347..a6b8315f 100644 --- a/frontend/pshared/pubspec.yaml +++ b/frontend/pshared/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: uuid: ^4.5.1 image: ^4.5.4 shared_preferences: ^2.5.3 + money2: ^6.3.0 dev_dependencies: flutter_lints: ^6.0.0 diff --git a/frontend/pweb/lib/controllers/operations/wallet_transactions.dart b/frontend/pweb/lib/controllers/operations/wallet_transactions.dart deleted file mode 100644 index dac9006c..00000000 --- a/frontend/pweb/lib/controllers/operations/wallet_transactions.dart +++ /dev/null @@ -1,102 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pshared/models/payment/status.dart'; - -import 'package:pweb/models/wallet/wallet_transaction.dart'; -import 'package:pweb/providers/wallet_transactions.dart'; - - -class WalletTransactionsController extends ChangeNotifier { - List _filteredTransactions = []; - DateTimeRange? _dateRange; - final Set _selectedStatuses = {}; - final Set _selectedTypes = {}; - WalletTransactionsProvider? _provider; - - List get transactions => - _provider?.transactions ?? const []; - List get filteredTransactions => _filteredTransactions; - DateTimeRange? get dateRange => _dateRange; - Set get selectedStatuses => _selectedStatuses; - Set get selectedTypes => _selectedTypes; - bool get isLoading => _provider?.isLoading ?? false; - String? get error => _provider?.error; - bool get hasFilters => - _dateRange != null || - _selectedStatuses.isNotEmpty || - _selectedTypes.isNotEmpty; - - void update(WalletTransactionsProvider provider) { - if (identical(_provider, provider)) return; - _provider?.removeListener(_onProviderChanged); - _provider = provider; - _provider?.addListener(_onProviderChanged); - _rebuildFiltered(notify: false); - notifyListeners(); - } - - void setDateRange(DateTimeRange? range) { - _dateRange = range; - _rebuildFiltered(); - } - - void toggleStatus(OperationStatus status) { - if (_selectedStatuses.contains(status)) { - _selectedStatuses.remove(status); - } else { - _selectedStatuses.add(status); - } - _rebuildFiltered(); - } - - void toggleType(WalletTransactionType type) { - if (_selectedTypes.contains(type)) { - _selectedTypes.remove(type); - } else { - _selectedTypes.add(type); - } - _rebuildFiltered(); - } - - void resetFilters() { - _dateRange = null; - _selectedStatuses.clear(); - _selectedTypes.clear(); - _rebuildFiltered(); - } - - void _onProviderChanged() { - _rebuildFiltered(); - } - - void _rebuildFiltered({bool notify = true}) { - final source = _provider?.transactions ?? const []; - final activeWalletId = _provider?.walletId; - _filteredTransactions = source.where((tx) { - final walletMatch = - activeWalletId == null || tx.walletId == activeWalletId; - final statusMatch = - _selectedStatuses.isEmpty || _selectedStatuses.contains(tx.status); - final typeMatch = - _selectedTypes.isEmpty || _selectedTypes.contains(tx.type); - final dateMatch = - _dateRange == null || - (tx.date.isAfter( - _dateRange!.start.subtract(const Duration(seconds: 1)), - ) && - tx.date.isBefore( - _dateRange!.end.add(const Duration(seconds: 1)), - )); - - return walletMatch && statusMatch && typeMatch && dateMatch; - }).toList(); - - if (notify) notifyListeners(); - } - - @override - void dispose() { - _provider?.removeListener(_onProviderChanged); - super.dispose(); - } -} diff --git a/frontend/pweb/lib/controllers/payments/amount_field.dart b/frontend/pweb/lib/controllers/payments/amount_field.dart index 8f94958b..b670bc7f 100644 --- a/frontend/pweb/lib/controllers/payments/amount_field.dart +++ b/frontend/pweb/lib/controllers/payments/amount_field.dart @@ -1,10 +1,11 @@ +import 'package:money2/money2.dart'; + import 'package:flutter/material.dart'; import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/models/payment/settlement_mode.dart'; import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/utils/currency.dart'; -import 'package:pshared/utils/money.dart'; import 'package:pweb/models/payment/amount/mode.dart'; @@ -22,7 +23,7 @@ class PaymentAmountFieldController extends ChangeNotifier { PaymentAmountFieldController({required double? initialAmount}) : textController = TextEditingController( - text: initialAmount == null ? '' : amountToString(initialAmount), + text: initialAmount == null ? '' : initialAmount.toString(), ); PaymentAmountMode get mode => _mode; @@ -122,18 +123,14 @@ class PaymentAmountFieldController extends ChangeNotifier { }; double? _parseAmount(String value) { - final parsed = parseMoneyAmount( - value.replaceAll(',', '.'), - fallback: double.nan, - ); - return parsed.isNaN ? null : parsed; + return double.tryParse(value.replaceAll(',', '.').trim()); } void _syncTextWithAmount(double? amount) { final parsedText = _parseAmount(textController.text); if (parsedText == amount) return; - final nextText = amount == null ? '' : amountToString(amount); + final nextText = amount == null ? '' : _formatAmount(amount); _isSyncingText = true; textController.value = TextEditingValue( text: nextText, @@ -142,6 +139,12 @@ class PaymentAmountFieldController extends ChangeNotifier { _isSyncingText = false; } + String _formatAmount(double amount) { + final currency = money2CurrencyFromCode(activeCurrencyCode); + if (currency == null) return amount.toString(); + return Money.fromNumWithCurrency(amount, currency).toDecimal().toString(); + } + @override void dispose() { _provider?.removeListener(_handleProviderChanged); diff --git a/frontend/pweb/lib/controllers/payments/recent_payments.dart b/frontend/pweb/lib/controllers/payments/recent_payments.dart index e5b6770f..2d1fe32d 100644 --- a/frontend/pweb/lib/controllers/payments/recent_payments.dart +++ b/frontend/pweb/lib/controllers/payments/recent_payments.dart @@ -29,5 +29,4 @@ class RecentPaymentsController extends ChangeNotifier { _recent = sortOperations(operations).take(5).toList(); notifyListeners(); } - } diff --git a/frontend/pweb/lib/controllers/payouts/multiple_payouts.dart b/frontend/pweb/lib/controllers/payouts/multiple_payouts.dart index 034a00a4..85beee2f 100644 --- a/frontend/pweb/lib/controllers/payouts/multiple_payouts.dart +++ b/frontend/pweb/lib/controllers/payouts/multiple_payouts.dart @@ -2,8 +2,9 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:money2/money2.dart'; + import 'package:pshared/controllers/payment/source.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/asset.dart'; import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/methods/data.dart'; @@ -17,6 +18,7 @@ import 'package:pweb/models/payment/multiple_payouts/state.dart'; import 'package:pweb/providers/multiple_payouts.dart'; import 'package:pweb/services/payments/csv_input.dart'; + class MultiplePayoutsController extends ChangeNotifier { final CsvInputService _csvInput; MultiplePayoutsProvider? _provider; diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index 527e2c60..dbdbe97c 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -31,10 +31,7 @@ import 'package:pweb/app/app.dart'; import 'package:pweb/pages/invitations/widgets/list/view_model.dart'; import 'package:pweb/app/timeago.dart'; import 'package:pweb/providers/two_factor.dart'; -import 'package:pweb/controllers/operations/wallet_transactions.dart'; -import 'package:pweb/providers/wallet_transactions.dart'; import 'package:pweb/services/posthog.dart'; -import 'package:pweb/services/wallet_transactions.dart'; import 'package:pweb/providers/account.dart'; import 'package:pweb/providers/locale.dart'; @@ -142,18 +139,6 @@ void main() async { create: (_) => WalletsController(), update: (_, wallets, controller) => controller!..update(wallets), ), - ChangeNotifierProvider( - create: (_) => WalletTransactionsProvider( - MockWalletTransactionsService(), - ), - ), - ChangeNotifierProxyProvider< - WalletTransactionsProvider, - WalletTransactionsController - >( - create: (_) => WalletTransactionsController(), - update: (_, provider, controller) => controller!..update(provider), - ), ], child: const PayApp(), ), diff --git a/frontend/pweb/lib/models/wallet/wallet_transaction.dart b/frontend/pweb/lib/models/wallet/wallet_transaction.dart index 95a7296c..0b78bdd7 100644 --- a/frontend/pweb/lib/models/wallet/wallet_transaction.dart +++ b/frontend/pweb/lib/models/wallet/wallet_transaction.dart @@ -29,7 +29,7 @@ class WalletTransaction { final WalletTransactionType type; final OperationStatus status; final double amount; - final Currency currency; + final CurrencyCode currency; final DateTime date; final String description; final String? counterparty; @@ -56,7 +56,7 @@ class WalletTransaction { WalletTransactionType? type, OperationStatus? status, double? amount, - Currency? currency, + CurrencyCode? currency, DateTime? date, String? description, String? counterparty, diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/constants.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/constants.dart index 6be2eb2e..e45bef30 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/constants.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/constants.dart @@ -2,6 +2,6 @@ import 'package:pshared/models/currency.dart'; import 'package:pshared/models/payment/chain_network.dart'; -const Currency managedCurrencyDefault = Currency.usdt; -const Currency ledgerCurrencyDefault = Currency.rub; +const CurrencyCode managedCurrencyDefault = CurrencyCode.usdt; +const CurrencyCode ledgerCurrencyDefault = CurrencyCode.rub; const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet; diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/dialog.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/dialog.dart index ee1a6960..0dc7c697 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/dialog.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/dialog.dart @@ -8,11 +8,9 @@ import 'package:pshared/models/currency.dart'; import 'package:pshared/models/describable.dart'; import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/type.dart'; -import 'package:pshared/models/wallet/chain_asset.dart'; import 'package:pshared/provider/accounts/employees.dart'; import 'package:pshared/provider/ledger.dart'; import 'package:pshared/provider/payment/wallets.dart'; -import 'package:pshared/utils/currency.dart'; import 'package:pweb/pages/dashboard/buttons/balance/add/form.dart'; import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart'; @@ -42,9 +40,9 @@ class _AddBalanceDialogState extends State { PaymentType _assetType = PaymentType.managedWallet; String? _ownerRef; - Currency _managedCurrency = managedCurrencyDefault; + CurrencyCode _managedCurrency = managedCurrencyDefault; ChainNetwork _network = managedNetworkDefault; - Currency _ledgerCurrency = ledgerCurrencyDefault; + CurrencyCode _ledgerCurrency = ledgerCurrencyDefault; @override void dispose() { @@ -60,7 +58,7 @@ class _AddBalanceDialogState extends State { void _setOwnerRef(String? value) => setState(() => _ownerRef = value); - void _setManagedCurrency(Currency? value) { + void _setManagedCurrency(CurrencyCode? value) { if (value == null) return; setState(() => _managedCurrency = value); } @@ -70,7 +68,7 @@ class _AddBalanceDialogState extends State { setState(() => _network = value); } - void _setLedgerCurrency(Currency? value) { + void _setLedgerCurrency(CurrencyCode? value) { if (value == null) return; setState(() => _ledgerCurrency = value); } @@ -102,7 +100,8 @@ class _AddBalanceDialogState extends State { if (_assetType == PaymentType.managedWallet) { await context.read().create( describable: newDescribable(name: name, description: description), - asset: ChainAsset(chain: _network, tokenSymbol: currencyCodeToString(_managedCurrency)), + chain: _network, + currency: _managedCurrency, ownerRef: owner?.id, ); } else { diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/form.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/form.dart index dc5c0550..7363d464 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/form.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/form.dart @@ -23,12 +23,12 @@ class AddBalanceForm extends StatelessWidget { final ValueChanged onOwnerChanged; final TextEditingController nameController; final TextEditingController descriptionController; - final Currency managedCurrency; + final CurrencyCode managedCurrency; final ChainNetwork network; - final Currency ledgerCurrency; - final ValueChanged onManagedCurrencyChanged; + final CurrencyCode ledgerCurrency; + final ValueChanged onManagedCurrencyChanged; final ValueChanged onNetworkChanged; - final ValueChanged onLedgerCurrencyChanged; + final ValueChanged onLedgerCurrencyChanged; final bool showEmployeesLoading; const AddBalanceForm({ diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/currency_item.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/currency_item.dart index 2c203d97..83fea543 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/currency_item.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/currency_item.dart @@ -4,7 +4,7 @@ import 'package:pshared/models/currency.dart'; import 'package:pshared/utils/currency.dart'; -DropdownMenuItem currencyItem(Currency currency) => DropdownMenuItem( +DropdownMenuItem currencyItem(CurrencyCode currency) => DropdownMenuItem( value: currency, child: Text(currencyCodeToString(currency)), ); \ No newline at end of file diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/fields.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/fields.dart index 191da740..8262e296 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/fields.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/ledger/fields.dart @@ -10,8 +10,8 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class LedgerFields extends StatelessWidget { - final Currency currency; - final ValueChanged? onCurrencyChanged; + final CurrencyCode currency; + final ValueChanged? onCurrencyChanged; const LedgerFields({ super.key, @@ -20,7 +20,7 @@ class LedgerFields extends StatelessWidget { }); @override - Widget build(BuildContext context) => DropdownButtonFormField( + Widget build(BuildContext context) => DropdownButtonFormField( initialValue: currency, decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true), items: [ diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/managed_wallet_fields.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/managed_wallet_fields.dart index 7f16a800..005744b7 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/add/managed_wallet_fields.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/add/managed_wallet_fields.dart @@ -12,9 +12,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart'; class ManagedWalletFields extends StatelessWidget { - final Currency currency; + final CurrencyCode currency; final ChainNetwork network; - final ValueChanged? onCurrencyChanged; + final ValueChanged? onCurrencyChanged; final ValueChanged? onNetworkChanged; const ManagedWalletFields({ @@ -31,7 +31,7 @@ class ManagedWalletFields extends StatelessWidget { return Column( spacing: 12, children: [ - DropdownButtonFormField( + DropdownButtonFormField( initialValue: currency, decoration: getInputDecoration(context, l10n.currency, true), items: [ diff --git a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart index 0f32412c..ae7f4a67 100644 --- a/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart +++ b/frontend/pweb/lib/pages/dashboard/buttons/balance/amount.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:pshared/controllers/balance_mask/wallets.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/utils/currency.dart'; @@ -28,12 +27,10 @@ class BalanceAmount extends StatelessWidget { final textTheme = Theme.of(context).textTheme; final colorScheme = Theme.of(context).colorScheme; final currencyBalance = currencyCodeToSymbol(wallet.currency); - final formattedBalance = formatMoneyUi( + final formattedBalance = formatAmountUi( context, - Money( - amount: amountToString(wallet.balance), - currency: currencyCodeToString(wallet.currency), - ), + amount: wallet.balance, + currency: currencyCodeToString(wallet.currency), ); final wallets = context.watch(); final isMasked = wallets.isBalanceMasked(wallet.id); diff --git a/frontend/pweb/lib/pages/dashboard/payouts/amount/field.dart b/frontend/pweb/lib/pages/dashboard/payouts/amount/field.dart index aeb78d7c..02201e98 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/amount/field.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/amount/field.dart @@ -36,9 +36,7 @@ class PaymentAmountField extends StatelessWidget { decoration: InputDecoration( labelText: loc.amount, border: const OutlineInputBorder(), - prefixText: symbol == null - ? null - : withTrailingNonBreakingSpace(symbol), + prefixText: symbol == null ? null : '$symbol ', ), onChanged: ui.handleChanged, ), diff --git a/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/summary.dart b/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/summary.dart index 5ea64671..35d25598 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/summary.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/multiple/panels/source_quote/summary.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:pshared/utils/currency.dart'; - import 'package:pweb/controllers/payouts/multiple_payouts.dart'; import 'package:pweb/models/dashboard/summary_values.dart'; import 'package:pweb/pages/dashboard/payouts/summary/widget.dart'; @@ -28,21 +26,12 @@ class SourceQuoteSummary extends StatelessWidget { values: PaymentSummaryValues( fee: controller.aggregateFeeAmount == null ? l10n.noFee - : formatMoneyUiWithL10n( - l10n, - controller.aggregateFeeAmount, - separator: nonBreakingSpace, - ), - recipientReceives: formatMoneyUiWithL10n( - l10n, + : formatMoneyUi(context, controller.aggregateFeeAmount), + recipientReceives: formatMoneyUi( + context, controller.aggregateSettlementAmount, - separator: nonBreakingSpace, - ), - total: formatMoneyUiWithL10n( - l10n, - controller.aggregateDebitAmount, - separator: nonBreakingSpace, ), + total: formatMoneyUi(context, controller.aggregateDebitAmount), ), ); } diff --git a/frontend/pweb/lib/pages/dashboard/payouts/multiple/sections/history/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/multiple/sections/history/widget.dart index 1c421798..6736c9a3 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/multiple/sections/history/widget.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/multiple/sections/history/widget.dart @@ -19,7 +19,10 @@ class UploadHistorySection extends StatelessWidget { @override Widget build(BuildContext context) { - return ChangeNotifierProxyProvider( + return ChangeNotifierProxyProvider< + PaymentsProvider, + RecentPaymentsController + >( create: (_) => RecentPaymentsController(), update: (_, payments, controller) => controller!..update(payments), child: const _RecentPaymentsView(), diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart index 55e0a11c..0998cd69 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart @@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - class PaymentFeeRow extends StatelessWidget { const PaymentFeeRow({super.key}); @@ -19,7 +18,7 @@ class PaymentFeeRow extends StatelessWidget { final l10 = AppLocalizations.of(context)!; return PaymentSummaryRow( labelFactory: l10.fee, - asset: fee, + money: fee, value: fee == null ? l10.noFee : null, style: Theme.of(context).textTheme.titleMedium, ); diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart index e82c5aed..80457fb2 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/recipient_receives.dart @@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - class PaymentRecipientReceivesRow extends StatelessWidget { const PaymentRecipientReceivesRow({super.key}); @@ -16,7 +15,7 @@ class PaymentRecipientReceivesRow extends StatelessWidget { Widget build(BuildContext context) => Consumer( builder: (context, provider, _) => PaymentSummaryRow( labelFactory: AppLocalizations.of(context)!.recipientWillReceive, - asset: provider.recipientGets, + money: provider.recipientGets, style: Theme.of(context).textTheme.titleMedium, ), ); diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart index 7086b1a6..07214da2 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart @@ -1,27 +1,25 @@ import 'package:flutter/material.dart'; - -import 'package:pshared/models/asset.dart'; +import 'package:money2/money2.dart'; import 'package:pweb/utils/money_display.dart'; - class PaymentSummaryRow extends StatelessWidget { final String Function(String) labelFactory; - final Asset? asset; + final Money? money; final String? value; final TextStyle? style; const PaymentSummaryRow({ super.key, required this.labelFactory, - required this.asset, + required this.money, this.value, this.style, }); @override Widget build(BuildContext context) { - final formatted = value ?? formatAssetUi(context, asset); + final formatted = value ?? formatMoneyUi(context, money); return Text(labelFactory(formatted), style: style); } } diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart index 0e6aa7da..efcfff82 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart @@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - class PaymentTotalRow extends StatelessWidget { const PaymentTotalRow({super.key}); @@ -16,8 +15,10 @@ class PaymentTotalRow extends StatelessWidget { Widget build(BuildContext context) => Consumer( builder: (context, provider, _) => PaymentSummaryRow( labelFactory: AppLocalizations.of(context)!.total, - asset: provider.total, - style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600), + money: provider.total, + style: Theme.of( + context, + ).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600), ), ); } diff --git a/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart index 3a9c48be..1da074b2 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/summary/widget.dart @@ -8,16 +8,11 @@ import 'package:pweb/pages/dashboard/payouts/summary/total.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - class PaymentSummary extends StatelessWidget { final double spacing; final PaymentSummaryValues? values; - const PaymentSummary({ - super.key, - required this.spacing, - this.values, - }); + const PaymentSummary({super.key, required this.spacing, this.values}); @override Widget build(BuildContext context) { @@ -32,20 +27,20 @@ class PaymentSummary extends StatelessWidget { children: [ PaymentSummaryRow( labelFactory: loc.fee, - asset: null, + money: null, value: resolvedValues.fee, style: theme.textTheme.titleMedium, ), PaymentSummaryRow( labelFactory: loc.recipientWillReceive, - asset: null, + money: null, value: resolvedValues.recipientReceives, style: theme.textTheme.titleMedium, ), SizedBox(height: spacing), PaymentSummaryRow( labelFactory: loc.total, - asset: null, + money: null, value: resolvedValues.total, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, @@ -69,4 +64,4 @@ class PaymentSummary extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/payout_page/wallet/card.dart b/frontend/pweb/lib/pages/payout_page/wallet/card.dart index dfb20f57..30792fa1 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/card.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/card.dart @@ -3,11 +3,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:pshared/controllers/balance_mask/wallets.dart'; -import 'package:pshared/utils/currency.dart'; import 'package:pshared/models/payment/wallet.dart'; import 'package:pweb/models/state/visibility.dart'; import 'package:pweb/pages/dashboard/buttons/balance/amount.dart'; +import 'package:pweb/pages/payout_page/wallet/currency_symbol_avatar.dart'; import 'package:pweb/widgets/refresh_balance/wallet.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; @@ -22,7 +22,7 @@ class WalletCard extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - + return Card( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: theme.cardTheme.elevation ?? 4, @@ -35,10 +35,7 @@ class WalletCard extends StatelessWidget { child: Row( spacing: 3, children: [ - CircleAvatar( - radius: 24, - child: Icon(iconForCurrencyType(wallet.currency), size: 28), - ), + CurrencySymbolAvatar(wallet: wallet), const SizedBox(width: 16), Expanded( child: Column( @@ -51,7 +48,9 @@ class WalletCard extends StatelessWidget { BalanceAmount( wallet: wallet, onToggleMask: () { - context.read().toggleBalanceMask(wallet.id); + context.read().toggleBalanceMask( + wallet.id, + ); }, ), WalletBalanceRefreshButton( diff --git a/frontend/pweb/lib/pages/payout_page/wallet/currency_symbol_avatar.dart b/frontend/pweb/lib/pages/payout_page/wallet/currency_symbol_avatar.dart new file mode 100644 index 00000000..782ebfa1 --- /dev/null +++ b/frontend/pweb/lib/pages/payout_page/wallet/currency_symbol_avatar.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +import 'package:pshared/models/payment/wallet.dart'; +import 'package:pshared/utils/currency.dart'; + + +class CurrencySymbolAvatar extends StatelessWidget { + final Wallet wallet; + + const CurrencySymbolAvatar({super.key, required this.wallet}); + + @override + Widget build(BuildContext context) { + final code = currencyCodeToString(wallet.currency); + final symbol = currencySymbolFromCode(code) ?? code; + final textTheme = Theme.of(context).textTheme; + + return CircleAvatar( + radius: 24, + child: Text( + symbol, + style: textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700), + ), + ); + } +} diff --git a/frontend/pweb/lib/pages/payout_page/wallet/edit/fields/ledger/balance_formatter.dart b/frontend/pweb/lib/pages/payout_page/wallet/edit/fields/ledger/balance_formatter.dart index a6639e1c..cfd54039 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/edit/fields/ledger/balance_formatter.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/edit/fields/ledger/balance_formatter.dart @@ -17,14 +17,10 @@ class LedgerBalanceFormatter { final currency = account.currency.trim(); if (currency.isEmpty) return '••••'; - try { - final symbol = currencyCodeToSymbol(currencyStringToCode(currency)); - if (symbol.trim().isEmpty) { - return '•••• $currency'; - } - return '•••• $symbol'; - } catch (_) { + final symbol = currencySymbolFromCode(currency); + if (symbol == null || symbol.trim().isEmpty) { return '•••• $currency'; } + return '•••• $symbol'; } } diff --git a/frontend/pweb/lib/pages/payout_page/wallet/history/filters.dart b/frontend/pweb/lib/pages/payout_page/wallet/history/filters.dart deleted file mode 100644 index c689618b..00000000 --- a/frontend/pweb/lib/pages/payout_page/wallet/history/filters.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pshared/models/payment/status.dart'; -import 'package:pshared/utils/localization.dart'; - -import 'package:pweb/models/wallet/wallet_transaction.dart'; -import 'package:pweb/controllers/operations/wallet_transactions.dart'; - -import 'package:pweb/generated/i18n/app_localizations.dart'; - - -class WalletHistoryFilters extends StatelessWidget { - final WalletTransactionsController provider; - final VoidCallback onPickRange; - - const WalletHistoryFilters({ - super.key, - required this.provider, - required this.onPickRange, - }); - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final loc = AppLocalizations.of(context)!; - - return Card( - elevation: 2, - color: theme.colorScheme.onSecondary, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - loc.walletActivity, - style: theme.textTheme.titleMedium, - ), - if (provider.hasFilters) - TextButton( - onPressed: provider.resetFilters, - child: Text(loc.reset), - ), - ], - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: WalletTransactionType.values.map((type) { - final isSelected = provider.selectedTypes.contains(type); - return FilterChip( - label: Text(type.label(context)), - selected: isSelected, - onSelected: (_) => provider.toggleType(type), - pressElevation: 0, - ); - }).toList(), - ), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: OperationStatus.values.map((status) { - final isSelected = provider.selectedStatuses.contains(status); - return FilterChip( - label: Text(status.localized(context)), - selected: isSelected, - onSelected: (_) => provider.toggleStatus(status), - pressElevation: 0, - ); - }).toList(), - ), - const SizedBox(height: 12), - Align( - alignment: Alignment.centerRight, - child: OutlinedButton.icon( - onPressed: onPickRange, - icon: const Icon(Icons.date_range_outlined), - label: Text( - provider.dateRange == null - ? loc.selectPeriod - : '${dateToLocalFormat(context, provider.dateRange!.start)} – ${dateToLocalFormat(context, provider.dateRange!.end)}', - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/frontend/pweb/lib/pages/report/cards/items.dart b/frontend/pweb/lib/pages/report/cards/items.dart index 32347333..d061edbf 100644 --- a/frontend/pweb/lib/pages/report/cards/items.dart +++ b/frontend/pweb/lib/pages/report/cards/items.dart @@ -23,17 +23,12 @@ List buildOperationCardItems( if (items.isNotEmpty) { items.add(const SizedBox(height: 16)); } - items.add(_DateHeader( - label: _dateLabel(context, operation.date, loc), - )); + items.add(_DateHeader(label: _dateLabel(context, operation.date, loc))); items.add(const SizedBox(height: 8)); currentKey = dateKey; } - items.add(OperationCard( - operation: operation, - onTap: onTap, - )); + items.add(OperationCard(operation: operation, onTap: onTap)); items.add(const SizedBox(height: 12)); } @@ -66,9 +61,7 @@ class _DateHeader extends StatelessWidget { final theme = Theme.of(context); return Text( label, - style: theme.textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.w600, - ), + style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ); } } diff --git a/frontend/pweb/lib/pages/report/details/sections.dart b/frontend/pweb/lib/pages/report/details/sections.dart index 79f69eca..ef19c238 100644 --- a/frontend/pweb/lib/pages/report/details/sections.dart +++ b/frontend/pweb/lib/pages/report/details/sections.dart @@ -6,7 +6,6 @@ import 'package:pshared/models/payment/payment.dart'; import 'package:pweb/pages/report/details/sections/fx.dart'; import 'package:pweb/pages/report/details/sections/operations/section.dart'; - class PaymentDetailsSections extends StatelessWidget { final Payment payment; final bool Function(PaymentExecutionOperation operation)? diff --git a/frontend/pweb/lib/pages/report/details/sections/fx.dart b/frontend/pweb/lib/pages/report/details/sections/fx.dart index 50a8ea2c..2886e71c 100644 --- a/frontend/pweb/lib/pages/report/details/sections/fx.dart +++ b/frontend/pweb/lib/pages/report/details/sections/fx.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/models/payment/fx/quote.dart'; import 'package:pshared/utils/currency.dart'; @@ -33,39 +32,36 @@ class PaymentFxSection extends StatelessWidget { final price = fx.price?.trim(); if (price == null || price.isEmpty) return null; - final baseCurrency = _firstNonEmpty([ + final baseCurrency = _resolveCurrencyCode([ fx.baseCurrency, - fx.baseAmount?.currency, - currencySymbolFromCode(fx.baseCurrency), - currencySymbolFromCode(fx.baseAmount?.currency), + fx.baseAmount?.currency.isoCode, ]); - final quoteCurrency = _firstNonEmpty([ + final quoteCurrency = _resolveCurrencyCode([ fx.quoteCurrency, - fx.quoteAmount?.currency, - currencySymbolFromCode(fx.quoteCurrency), - currencySymbolFromCode(fx.quoteAmount?.currency), + fx.quoteAmount?.currency.isoCode, ]); if (baseCurrency == null || quoteCurrency == null) return price; - final baseDisplay = formatMoneyDisplay( - Money(amount: '1', currency: baseCurrency), - fallback: '1 $baseCurrency', - invalidAmountFallback: '1', - ); - final quoteDisplay = formatMoneyDisplay( - Money(amount: _normalizeAmount(price), currency: quoteCurrency), - fallback: '$price $quoteCurrency', - invalidAmountFallback: price, - ); + final baseDisplay = + parseMoneyWithCurrencyCode('1', baseCurrency)?.toString() ?? + '1 $baseCurrency'; + final quoteDisplay = + parseMoneyWithCurrencyCode( + _normalizeAmount(price), + quoteCurrency, + )?.toString() ?? + '$price $quoteCurrency'; return '$baseDisplay = $quoteDisplay'; } - String? _firstNonEmpty(List values) { + String? _resolveCurrencyCode(List values) { for (final value in values) { - final trimmed = value?.trim(); - if (trimmed != null && trimmed.isNotEmpty) return trimmed; + if (value == null || value.isEmpty) continue; + final resolved = money2CurrencyFromCode(value); + if (resolved != null) return resolved.isoCode; + return value; } return null; } diff --git a/frontend/pweb/lib/pages/report/details/summary_card/widget.dart b/frontend/pweb/lib/pages/report/details/summary_card/widget.dart index ee6ade98..b93d7377 100644 --- a/frontend/pweb/lib/pages/report/details/summary_card/widget.dart +++ b/frontend/pweb/lib/pages/report/details/summary_card/widget.dart @@ -9,13 +9,11 @@ import 'package:pweb/pages/report/details/summary_card/info_line.dart'; import 'package:pweb/pages/report/table/badge.dart'; import 'package:pweb/utils/report/amount_parts.dart'; import 'package:pweb/utils/report/format.dart'; -import 'package:pweb/utils/money_display.dart'; import 'package:pweb/utils/report/payment_mapper.dart'; import 'package:pweb/utils/clipboard.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - class PaymentSummaryCard extends StatelessWidget { final Payment payment; @@ -25,7 +23,7 @@ class PaymentSummaryCard extends StatelessWidget { Widget build(BuildContext context) { final loc = AppLocalizations.of(context)!; final theme = Theme.of(context); - final unavailableValue = unavailableMoneyValue(context); + final unavailableValue = loc.valueUnavailable; final status = statusFromPayment(payment); final dateLabel = formatDateLabel(context, resolvePaymentDate(payment)); diff --git a/frontend/pweb/lib/pages/report/operations/actions.dart b/frontend/pweb/lib/pages/report/operations/actions.dart index 6a9b7c9c..5857b2aa 100644 --- a/frontend/pweb/lib/pages/report/operations/actions.dart +++ b/frontend/pweb/lib/pages/report/operations/actions.dart @@ -12,11 +12,9 @@ Future pickOperationsRange( ReportOperationsController controller, ) async { final now = DateTime.now(); - final initial = controller.selectedRange ?? - DateTimeRange( - start: now.subtract(const Duration(days: 30)), - end: now, - ); + final initial = + controller.selectedRange ?? + DateTimeRange(start: now.subtract(const Duration(days: 30)), end: now); final picked = await showDateRangePicker( context: context, diff --git a/frontend/pweb/lib/pages/report/table/row.dart b/frontend/pweb/lib/pages/report/table/row.dart index 1c2eab6b..c5b6597f 100644 --- a/frontend/pweb/lib/pages/report/table/row.dart +++ b/frontend/pweb/lib/pages/report/table/row.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/operation.dart'; import 'package:pshared/models/payment/status.dart'; -import 'package:pshared/utils/currency.dart'; import 'package:pweb/pages/report/table/badge.dart'; import 'package:pweb/utils/money_display.dart'; @@ -38,13 +36,15 @@ class OperationRow { label: Text(loc.downloadAct), ) : Text(op.fileName ?? ''); - final amountLabel = formatMoneyUiWithL10n( - loc, - Money(amount: amountToString(op.amount), currency: op.currency), + final amountLabel = formatAmountUi( + context, + amount: op.amount, + currency: op.currency, ); - final toAmountLabel = formatMoneyUiWithL10n( - loc, - Money(amount: amountToString(op.toAmount), currency: op.toCurrency), + final toAmountLabel = formatAmountUi( + context, + amount: op.toAmount, + currency: op.toCurrency, ); return DataRow( diff --git a/frontend/pweb/lib/providers/multiple_payouts.dart b/frontend/pweb/lib/providers/multiple_payouts.dart index 2b620546..3d3e13ea 100644 --- a/frontend/pweb/lib/providers/multiple_payouts.dart +++ b/frontend/pweb/lib/providers/multiple_payouts.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; + import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/models/payment/quote/quote.dart'; import 'package:pshared/models/payment/quote/status_type.dart'; @@ -89,14 +90,16 @@ class MultiplePayoutsProvider extends ChangeNotifier { Money? get requestedSentAmount { if (_rows.isEmpty) return null; const currency = 'RUB'; + final rubCurrency = money2CurrencyFromCode(currency); + if (rubCurrency == null) return null; - double total = 0; + Money? total; for (final row in _rows) { - final value = parseMoneyAmount(row.amount, fallback: double.nan); - if (value.isNaN) return null; - total += value; + final value = parseMoneyWithCurrency(row.amount, rubCurrency); + if (value == null) return null; + total = total == null ? value : total + value; } - return Money(amount: amountToString(total), currency: currency); + return total; } Money? aggregateSettlementAmountForCurrency(String? sourceCurrencyCode) { @@ -118,11 +121,11 @@ class MultiplePayoutsProvider extends ChangeNotifier { final fee = aggregateFeeAmountForCurrency(sourceCurrencyCode); if (debit == null || fee == null) return null; - 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.isNaN || feeValue.isNaN || debitValue <= 0) return null; - return (feeValue / debitValue) * 100; + final debitValue = debit; + final feeValue = fee; + if (debitValue.currency.isoCode != feeValue.currency.isoCode) return null; + if (!debitValue.isPositive) return null; + return (feeValue.toDecimal() / debitValue.toDecimal()).toDouble() * 100; } Future quoteFromCsv({ @@ -279,8 +282,8 @@ class MultiplePayoutsProvider extends ChangeNotifier { final sentAmount = requestedSentAmount; if (sentAmount == null) return null; return { - UploadMetadataKeys.amount: sentAmount.amount, - UploadMetadataKeys.currency: sentAmount.currency, + UploadMetadataKeys.amount: sentAmount.toDecimal().toString(), + UploadMetadataKeys.currency: sentAmount.currency.isoCode, }; } @@ -290,10 +293,12 @@ class MultiplePayoutsProvider extends ChangeNotifier { ) { if (values == null || values.isEmpty) return null; - if (sourceCurrencyCode != null && sourceCurrencyCode.isNotEmpty) { - final sourceCurrency = sourceCurrencyCode.trim().toUpperCase(); + final sourceCurrency = + money2CurrencyFromCode(sourceCurrencyCode)?.isoCode ?? + sourceCurrencyCode; + if (sourceCurrency != null) { for (final value in values) { - if (value.currency.toUpperCase() == sourceCurrency) { + if (value.currency.isoCode == sourceCurrency) { return value; } } diff --git a/frontend/pweb/lib/providers/wallet_transactions.dart b/frontend/pweb/lib/providers/wallet_transactions.dart deleted file mode 100644 index 7d153dbd..00000000 --- a/frontend/pweb/lib/providers/wallet_transactions.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/foundation.dart'; - -import 'package:pweb/models/wallet/wallet_transaction.dart'; -import 'package:pweb/services/wallet_transactions.dart'; - - -class WalletTransactionsProvider extends ChangeNotifier { - final WalletTransactionsService _service; - - WalletTransactionsProvider(this._service); - - List _transactions = const []; - bool _isLoading = false; - String? _error; - String? _walletId; - int _loadSeq = 0; - - List get transactions => List.unmodifiable(_transactions); - bool get isLoading => _isLoading; - String? get error => _error; - String? get walletId => _walletId; - - Future load({String? walletId}) async { - final targetWalletId = walletId ?? _walletId; - final requestSeq = ++_loadSeq; - _walletId = targetWalletId; - _isLoading = true; - _error = null; - notifyListeners(); - - try { - final fetched = await _service.fetchHistory(walletId: targetWalletId); - if (requestSeq != _loadSeq) return; - - _transactions = targetWalletId == null - ? fetched - : fetched.where((tx) => tx.walletId == targetWalletId).toList(); - } catch (e) { - if (requestSeq != _loadSeq) return; - _error = e.toString(); - } finally { - if (requestSeq == _loadSeq) { - _isLoading = false; - notifyListeners(); - } - } - } -} diff --git a/frontend/pweb/lib/services/wallet_transactions.dart b/frontend/pweb/lib/services/wallet_transactions.dart deleted file mode 100644 index 84b7979b..00000000 --- a/frontend/pweb/lib/services/wallet_transactions.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'package:pshared/models/payment/status.dart'; -import 'package:pshared/models/currency.dart'; - -import 'package:pweb/models/wallet/wallet_transaction.dart'; - - -abstract class WalletTransactionsService { - Future> fetchHistory({String? walletId}); -} - -class MockWalletTransactionsService implements WalletTransactionsService { - final List _history = [ - WalletTransaction( - id: 'wt-001', - walletId: '1124', - type: WalletTransactionType.topUp, - status: OperationStatus.success, - amount: 150000, - currency: Currency.rub, - date: DateTime(2024, 9, 14, 10, 12), - counterparty: 'VISA ••0019', - description: 'Top up via corporate card', - balanceAfter: 10150000, - ), - WalletTransaction( - id: 'wt-002', - walletId: '1124', - type: WalletTransactionType.payout, - status: OperationStatus.processing, - amount: 2500, - currency: Currency.rub, - date: DateTime(2024, 9, 15, 12, 10), - counterparty: 'Bank transfer RU239', - description: 'Invoice #239 shipping', - balanceAfter: 10147500, - ), - WalletTransaction( - id: 'wt-003', - walletId: '1124', - type: WalletTransactionType.payout, - status: OperationStatus.error, - amount: 1200, - currency: Currency.rub, - date: DateTime(2024, 9, 13, 16, 40), - counterparty: '4000 **** 0077', - description: 'Payout to card declined', - balanceAfter: 10000000, - ), - WalletTransaction( - id: 'wt-004', - walletId: '2124', - type: WalletTransactionType.topUp, - status: OperationStatus.success, - amount: 1800, - currency: Currency.usd, - date: DateTime(2024, 9, 12, 9, 0), - counterparty: 'Wire payment 8831', - description: 'Top up via USD wire', - balanceAfter: 4300.5, - ), - WalletTransaction( - id: 'wt-005', - walletId: '2124', - type: WalletTransactionType.payout, - status: OperationStatus.success, - amount: 400, - currency: Currency.usd, - date: DateTime(2024, 9, 16, 14, 30), - counterparty: 'IBAN DE09••1122', - description: 'Payout to John Snow', - balanceAfter: 3900.5, - ), - WalletTransaction( - id: 'wt-006', - walletId: '1124', - type: WalletTransactionType.payout, - status: OperationStatus.success, - amount: 70000, - currency: Currency.rub, - date: DateTime(2024, 9, 17, 8, 45), - counterparty: 'Payroll batch', - description: 'Monthly reimbursements', - balanceAfter: 10080000, - ), - WalletTransaction( - id: 'wt-007', - walletId: '1124', - type: WalletTransactionType.topUp, - status: OperationStatus.processing, - amount: 200000, - currency: Currency.rub, - date: DateTime(2024, 9, 18, 9, 30), - counterparty: 'Bank wire RU511', - description: 'Top up pending confirmation', - balanceAfter: 10280000, - ), - ]; - - @override - Future> fetchHistory({String? walletId}) async { - await Future.delayed(const Duration(milliseconds: 350)); - - final source = walletId == null - ? _history - : _history.where((tx) => tx.walletId == walletId).toList(); - - return List.from(source); - } -} diff --git a/frontend/pweb/lib/utils/money_display.dart b/frontend/pweb/lib/utils/money_display.dart index 3d7010f6..fe61a984 100644 --- a/frontend/pweb/lib/utils/money_display.dart +++ b/frontend/pweb/lib/utils/money_display.dart @@ -1,96 +1,27 @@ import 'package:flutter/widgets.dart'; -import 'package:pshared/models/asset.dart'; -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; + import 'package:pshared/utils/currency.dart'; -import 'package:pshared/utils/money.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; -String unavailableMoneyValue(BuildContext context) { - return AppLocalizations.of(context)!.valueUnavailable; -} - -String unavailableMoneyValueFromL10n(AppLocalizations l10n) { - return l10n.valueUnavailable; -} - -String formatMoneyUi( - BuildContext context, - Money? money, { - String separator = ' ', -}) { - return formatMoneyUiWithL10n( - AppLocalizations.of(context)!, - money, - separator: separator, - ); -} - -String formatMoneyUiWithL10n( - AppLocalizations l10n, - Money? money, { - String separator = ' ', -}) { - final unavailableValue = unavailableMoneyValueFromL10n(l10n); - return formatMoneyDisplay( - money, - fallback: unavailableValue, - invalidAmountFallback: unavailableValue, - separator: separator, - ); +String formatMoneyUi(BuildContext context, Money? money) { + final l10n = AppLocalizations.of(context)!; + if (money == null) return l10n.valueUnavailable; + return money.toString(); } String formatAmountUi( BuildContext context, { required double amount, required String currency, - String separator = ' ', }) { - return formatAmountUiWithL10n( - AppLocalizations.of(context)!, - amount: amount, - currency: currency, - separator: separator, - ); -} + final l10n = AppLocalizations.of(context)!; + final moneyCurrency = money2CurrencyFromCode(currency); + if (moneyCurrency == null) return l10n.valueUnavailable; -String formatAmountUiWithL10n( - AppLocalizations l10n, { - required double amount, - required String currency, - String separator = ' ', -}) { - return formatMoneyUiWithL10n( - l10n, - Money(amount: amountToString(amount), currency: currency), - separator: separator, - ); -} - -String formatAssetUi( - BuildContext context, - Asset? asset, { - String separator = ' ', -}) { - return formatAssetUiWithL10n( - AppLocalizations.of(context)!, - asset, - separator: separator, - ); -} - -String formatAssetUiWithL10n( - AppLocalizations l10n, - Asset? asset, { - String separator = ' ', -}) { - if (asset == null) return unavailableMoneyValueFromL10n(l10n); - return formatAmountUiWithL10n( - l10n, - amount: asset.amount, - currency: currencyCodeToString(asset.currency), - separator: separator, - ); + final money = Money.fromNumWithCurrency(amount, moneyCurrency); + return money.toString(); } diff --git a/frontend/pweb/lib/utils/payment/multiple/csv_parser.dart b/frontend/pweb/lib/utils/payment/multiple/csv_parser.dart index 776e9da3..cc808b31 100644 --- a/frontend/pweb/lib/utils/payment/multiple/csv_parser.dart +++ b/frontend/pweb/lib/utils/payment/multiple/csv_parser.dart @@ -1,5 +1,3 @@ -import 'package:pshared/utils/money.dart'; - import 'package:pweb/models/payment/multiple_payouts/csv_row.dart'; @@ -82,8 +80,8 @@ class MultipleCsvParser { throw FormatException('CSV row ${i + 1}: amount is required'); } - final parsedAmount = parseMoneyAmount(amount, fallback: double.nan); - if (parsedAmount.isNaN || parsedAmount <= 0) { + final parsedAmount = double.tryParse(amount); + if (parsedAmount == null || parsedAmount <= 0) { throw FormatException( 'CSV row ${i + 1}: amount must be greater than 0', ); diff --git a/frontend/pweb/lib/utils/payment/multiple/intent_builder.dart b/frontend/pweb/lib/utils/payment/multiple/intent_builder.dart index c779b5aa..93b8ff01 100644 --- a/frontend/pweb/lib/utils/payment/multiple/intent_builder.dart +++ b/frontend/pweb/lib/utils/payment/multiple/intent_builder.dart @@ -1,4 +1,3 @@ -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/customer.dart'; import 'package:pshared/models/payment/fees/treatment.dart'; import 'package:pshared/models/payment/intent.dart'; @@ -6,6 +5,7 @@ import 'package:pshared/models/payment/kind.dart'; import 'package:pshared/models/payment/methods/card.dart'; import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/settlement_mode.dart'; +import 'package:pshared/utils/money.dart'; import 'package:pshared/utils/payment/fx_helpers.dart'; import 'package:pweb/models/payment/multiple_payouts/csv_row.dart'; @@ -30,7 +30,12 @@ class MultipleIntentBuilder { .map((entry) { final rowIndex = entry.key; final row = entry.value; - final amount = Money(amount: row.amount, currency: _currency); + final amount = parseMoneyWithCurrencyCode(row.amount, _currency); + if (amount == null) { + throw FormatException( + 'Invalid CSV amount at row ${rowIndex + 1}: ${row.amount}', + ); + } final destination = CardPaymentMethod( pan: row.pan, firstName: row.firstName, diff --git a/frontend/pweb/lib/utils/report/format.dart b/frontend/pweb/lib/utils/report/format.dart index 370ab889..8133b5f3 100644 --- a/frontend/pweb/lib/utils/report/format.dart +++ b/frontend/pweb/lib/utils/report/format.dart @@ -2,15 +2,14 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -import 'package:pshared/models/money.dart'; +import 'package:money2/money2.dart'; + import 'package:pshared/utils/localization.dart'; + import 'package:pweb/utils/money_display.dart'; String formatMoney(BuildContext context, Money? money) { - if (money == null || money.amount.trim().isEmpty) { - return unavailableMoneyValue(context); - } return formatMoneyUi(context, money); } diff --git a/frontend/pweb/lib/utils/report/payment_mapper.dart b/frontend/pweb/lib/utils/report/payment_mapper.dart index cae8bd6f..68025e70 100644 --- a/frontend/pweb/lib/utils/report/payment_mapper.dart +++ b/frontend/pweb/lib/utils/report/payment_mapper.dart @@ -3,7 +3,6 @@ import 'package:pshared/models/payment/operation.dart'; import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/models/payment/state.dart'; import 'package:pshared/models/payment/status.dart'; -import 'package:pshared/utils/money.dart'; import 'package:pweb/utils/payment/upload_metadata.dart'; import 'package:pweb/utils/report/operations/document_rule.dart'; @@ -14,12 +13,12 @@ OperationItem mapPaymentToOperation(Payment payment) { final settlement = payment.lastQuote?.amounts?.destinationSettlement; final amountMoney = debit ?? settlement; - final amount = parseMoneyAmount(amountMoney?.amount); - final currency = amountMoney?.currency ?? ''; + final amount = amountMoney?.toDouble() ?? 0; + final currency = amountMoney?.currency.isoCode ?? ''; final toAmount = settlement == null ? amount - : parseMoneyAmount(settlement.amount); - final toCurrency = settlement?.currency ?? currency; + : settlement.toDouble(); + final toCurrency = settlement?.currency.isoCode ?? currency; final payId = _firstNonEmpty([payment.paymentRef]) ?? '-'; final name = diff --git a/frontend/pweb/lib/widgets/payment/source_wallet_selector/balance_formatter.dart b/frontend/pweb/lib/widgets/payment/source_wallet_selector/balance_formatter.dart index 8fda011b..a11157a0 100644 --- a/frontend/pweb/lib/widgets/payment/source_wallet_selector/balance_formatter.dart +++ b/frontend/pweb/lib/widgets/payment/source_wallet_selector/balance_formatter.dart @@ -1,31 +1,28 @@ import 'package:flutter/widgets.dart'; import 'package:pshared/models/ledger/account.dart'; -import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/utils/currency.dart'; +import 'package:pshared/utils/money.dart'; import 'package:pweb/utils/money_display.dart'; String walletBalance(BuildContext context, Wallet wallet) { - return formatMoneyUi( + return formatAmountUi( context, - Money( - amount: amountToString(wallet.balance), - currency: currencyCodeToString(wallet.currency), - ), + amount: wallet.balance, + currency: currencyCodeToString(wallet.currency), ); } String ledgerBalance(BuildContext context, LedgerAccount account) { final money = account.balance?.balance; - final effectiveCurrency = (money?.currency.trim().isNotEmpty ?? false) - ? money!.currency + final effectiveCurrency = (money?.currency.isoCode.trim().isNotEmpty ?? false) + ? money!.currency.isoCode : account.currency; + final effectiveMoney = + money ?? parseMoneyWithCurrencyCode('0', effectiveCurrency); - return formatMoneyUi( - context, - Money(amount: money?.amount ?? '', currency: effectiveCurrency), - ); + return formatMoneyUi(context, effectiveMoney); } diff --git a/frontend/pweb/pubspec.yaml b/frontend/pweb/pubspec.yaml index e3495768..80294ca0 100644 --- a/frontend/pweb/pubspec.yaml +++ b/frontend/pweb/pubspec.yaml @@ -70,7 +70,7 @@ dependencies: qr_flutter: ^4.1.0 duration: ^4.0.3 universal_html: ^2.3.0 - + money2: ^6.3.0 dev_dependencies: