import 'package:flutter/foundation.dart'; import 'package:pshared/controllers/balance_mask/wallets.dart'; import 'package:pshared/models/money.dart'; import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/provider/payment/multiple/provider.dart'; import 'package:pshared/provider/payment/multiple/quotation.dart'; import 'package:pshared/utils/currency.dart'; import 'package:pweb/models/multiple_payouts/csv_row.dart'; import 'package:pweb/models/multiple_payouts/state.dart'; import 'package:pweb/services/payments/csv_input.dart'; import 'package:pweb/utils/payment/multiple_csv_parser.dart'; import 'package:pweb/utils/payment/multiple_intent_builder.dart'; class MultiplePayoutsController extends ChangeNotifier { final CsvInputService _csvInput; final MultipleCsvParser _csvParser; final MultipleIntentBuilder _intentBuilder; WalletsController? _wallets; MultiQuotationProvider? _quotation; MultiPaymentProvider? _payment; MultiplePayoutsState _state = MultiplePayoutsState.idle; String? _selectedFileName; List _rows = const []; int _sentCount = 0; Exception? _error; MultiplePayoutsController({ required CsvInputService csvInput, MultipleCsvParser? csvParser, MultipleIntentBuilder? intentBuilder, }) : _csvInput = csvInput, _csvParser = csvParser ?? MultipleCsvParser(), _intentBuilder = intentBuilder ?? MultipleIntentBuilder(); void update( WalletsController wallets, MultiQuotationProvider quotation, MultiPaymentProvider payment, ) { _wallets = wallets; _quotation = quotation; _payment = payment; } MultiplePayoutsState get state => _state; String? get selectedFileName => _selectedFileName; List get rows => List.unmodifiable(_rows); int get sentCount => _sentCount; Exception? get error => _error; bool get isQuoting => _state == MultiplePayoutsState.quoting; bool get isSending => _state == MultiplePayoutsState.sending; bool get isBusy => isQuoting || isSending; bool get canSend { if (isBusy || _rows.isEmpty) return false; final quoteRef = _quotation?.quotation?.quoteRef; return quoteRef != null && quoteRef.isNotEmpty; } Money? get aggregateDebitAmount { if (_rows.isEmpty) return null; return _moneyForSourceCurrency( _quotation?.quotation?.aggregate?.debitAmounts, ); } Money? get requestedSentAmount { if (_rows.isEmpty) return null; const currency = 'RUB'; double total = 0; for (final row in _rows) { final value = double.tryParse(row.amount); if (value == null) return null; total += value; } return Money(amount: amountToString(total), currency: currency); } Money? get aggregateSettlementAmount { if (_rows.isEmpty) return null; return _moneyForSourceCurrency( _quotation?.quotation?.aggregate?.expectedSettlementAmounts, ); } Money? get aggregateFeeAmount { if (_rows.isEmpty) return null; return _moneyForSourceCurrency( _quotation?.quotation?.aggregate?.expectedFeeTotals, ); } double? get aggregateFeePercent { final debit = aggregateDebitAmount; final fee = aggregateFeeAmount; if (debit == null || fee == null) return null; final debitValue = double.tryParse(debit.amount); final feeValue = double.tryParse(fee.amount); if (debit.currency.toUpperCase() != fee.currency.toUpperCase()) return null; if (debitValue == null || feeValue == null || debitValue <= 0) return null; return (feeValue / debitValue) * 100; } Future pickAndQuote() async { if (isBusy) return; final wallets = _wallets; final quotation = _quotation; if (wallets == null || quotation == null) { _setErrorObject( StateError('Multiple payouts dependencies are not ready'), ); return; } try { _setState(MultiplePayoutsState.quoting); _error = null; _sentCount = 0; final picked = await _csvInput.pickCsv(); if (picked == null) { return; } final rows = _csvParser.parseRows(picked.content); final intents = _intentBuilder.buildIntents(wallets, rows); _selectedFileName = picked.name; _rows = rows; await quotation.quotePayments( intents, metadata: { 'upload_filename': picked.name, 'upload_rows': rows.length.toString(), ...?_uploadAmountMetadata(), }, ); if (quotation.error != null) { _setErrorObject(quotation.error!); } } catch (e) { _setErrorObject(e); } finally { _setState(MultiplePayoutsState.idle); } } Future> send() async { if (isBusy) return const []; final payment = _payment; if (payment == null) { _setErrorObject( StateError('Multiple payouts payment provider is not ready'), ); return const []; } if (!canSend) { _setErrorObject( StateError('Upload CSV and wait for quote before sending'), ); return const []; } try { _setState(MultiplePayoutsState.sending); _error = null; final result = await payment.pay( metadata: { ...?_selectedFileName == null ? null : {'upload_filename': _selectedFileName!}, 'upload_rows': _rows.length.toString(), ...?_uploadAmountMetadata(), }, ); _sentCount = result.length; return result; } catch (e) { _setErrorObject(e); return const []; } finally { _setState(MultiplePayoutsState.idle); } } void removeUploadedFile() { if (isBusy) return; _selectedFileName = null; _rows = const []; _sentCount = 0; _error = null; notifyListeners(); } void _setState(MultiplePayoutsState value) { _state = value; notifyListeners(); } void _setErrorObject(Object error) { _error = error is Exception ? error : Exception(error.toString()); notifyListeners(); } Map? _uploadAmountMetadata() { final sentAmount = requestedSentAmount; if (sentAmount == null) return null; return { 'upload_amount': sentAmount.amount, 'upload_currency': sentAmount.currency, }; } Money? _moneyForSourceCurrency(List? values) { if (values == null || values.isEmpty) return null; final selectedWallet = _wallets?.selectedWallet; if (selectedWallet != null) { final sourceCurrency = currencyCodeToString(selectedWallet.currency); for (final value in values) { if (value.currency.toUpperCase() == sourceCurrency.toUpperCase()) { return value; } } } return values.first; } }