reports page
This commit is contained in:
22
frontend/pweb/lib/utils/report/amount_parts.dart
Normal file
22
frontend/pweb/lib/utils/report/amount_parts.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
AmountParts splitAmount(String value) {
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty || trimmed == '-') {
|
||||
return const AmountParts(amount: '-', currency: '');
|
||||
}
|
||||
final parts = trimmed.split(' ');
|
||||
if (parts.length < 2) {
|
||||
return AmountParts(amount: trimmed, currency: '');
|
||||
}
|
||||
final currency = parts.removeLast();
|
||||
return AmountParts(amount: parts.join(' '), currency: currency);
|
||||
}
|
||||
|
||||
class AmountParts {
|
||||
final String amount;
|
||||
final String currency;
|
||||
|
||||
const AmountParts({
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
});
|
||||
}
|
||||
36
frontend/pweb/lib/utils/report/download_act.dart
Normal file
36
frontend/pweb/lib/utils/report/download_act.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/service/payment/documents.dart';
|
||||
|
||||
import 'package:pweb/utils/download.dart';
|
||||
import 'package:pweb/utils/error/snackbar.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
Future<void> downloadPaymentAct(BuildContext context, String paymentRef) async {
|
||||
final organizations = context.read<OrganizationsProvider>();
|
||||
if (!organizations.isOrganizationSet) {
|
||||
return;
|
||||
}
|
||||
final trimmed = paymentRef.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
await executeActionWithNotification(
|
||||
context: context,
|
||||
action: () async {
|
||||
final file = await PaymentDocumentsService.getAct(
|
||||
organizations.current.id,
|
||||
trimmed,
|
||||
);
|
||||
await downloadFile(file);
|
||||
},
|
||||
errorMessage: loc.downloadActError,
|
||||
);
|
||||
}
|
||||
42
frontend/pweb/lib/utils/report/format.dart
Normal file
42
frontend/pweb/lib/utils/report/format.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/localization.dart';
|
||||
|
||||
|
||||
String formatMoney(Money? money, {String fallback = '-'}) {
|
||||
if (money == null) return fallback;
|
||||
final amount = money.amount.trim();
|
||||
if (amount.isEmpty) return fallback;
|
||||
final symbol = currencySymbolFromCode(money.currency);
|
||||
final suffix = symbol ?? money.currency;
|
||||
if (suffix.trim().isEmpty) return amount;
|
||||
return '$amount $suffix';
|
||||
}
|
||||
|
||||
String formatAmount(double amount, String currency, {String fallback = '-'}) {
|
||||
final trimmed = currency.trim();
|
||||
if (trimmed.isEmpty) return amountToString(amount);
|
||||
final symbol = currencySymbolFromCode(trimmed);
|
||||
final suffix = symbol ?? trimmed;
|
||||
return '${amountToString(amount)} $suffix';
|
||||
}
|
||||
|
||||
String formatDateLabel(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
return dateTimeToLocalFormat(context, date.toLocal());
|
||||
}
|
||||
|
||||
String formatLongDate(BuildContext context, DateTime? date, {String fallback = '-'}) {
|
||||
if (date == null || date.millisecondsSinceEpoch == 0) return fallback;
|
||||
final locale = Localizations.localeOf(context).toString();
|
||||
final formatter = DateFormat('d MMMM y', locale);
|
||||
return formatter.format(date.toLocal());
|
||||
}
|
||||
|
||||
String collapseWhitespace(String value) {
|
||||
return value.replaceAll(RegExp(r'\s+'), ' ').trim();
|
||||
}
|
||||
23
frontend/pweb/lib/utils/report/operations.dart
Normal file
23
frontend/pweb/lib/utils/report/operations.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:pshared/models/payment/operation.dart';
|
||||
|
||||
|
||||
List<OperationItem> sortOperations(List<OperationItem> operations) {
|
||||
final sorted = List<OperationItem>.from(operations);
|
||||
sorted.sort((a, b) {
|
||||
final aTime = a.date.millisecondsSinceEpoch;
|
||||
final bTime = b.date.millisecondsSinceEpoch;
|
||||
final aUnknown = isUnknownDate(a.date);
|
||||
final bUnknown = isUnknownDate(b.date);
|
||||
|
||||
if (aUnknown != bUnknown) {
|
||||
return aUnknown ? 1 : -1;
|
||||
}
|
||||
if (aTime != bTime) {
|
||||
return bTime.compareTo(aTime);
|
||||
}
|
||||
return a.payId.compareTo(b.payId);
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
bool isUnknownDate(DateTime date) => date.millisecondsSinceEpoch == 0;
|
||||
117
frontend/pweb/lib/utils/report/payment_mapper.dart
Normal file
117
frontend/pweb/lib/utils/report/payment_mapper.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'package:pshared/models/payment/operation.dart';
|
||||
import 'package:pshared/models/payment/payment.dart';
|
||||
import 'package:pshared/models/payment/status.dart';
|
||||
|
||||
|
||||
OperationItem mapPaymentToOperation(Payment payment) {
|
||||
final debit = payment.lastQuote?.debitAmount;
|
||||
final settlement = payment.lastQuote?.expectedSettlementAmount;
|
||||
final amountMoney = debit ?? settlement;
|
||||
|
||||
final amount = _parseAmount(amountMoney?.amount);
|
||||
final currency = amountMoney?.currency ?? '';
|
||||
final toAmount = settlement == null ? amount : _parseAmount(settlement.amount);
|
||||
final toCurrency = settlement?.currency ?? currency;
|
||||
|
||||
final payId = _firstNonEmpty([
|
||||
payment.paymentRef,
|
||||
payment.idempotencyKey,
|
||||
]) ??
|
||||
'-';
|
||||
final name = _firstNonEmpty([
|
||||
payment.lastQuote?.quoteRef,
|
||||
payment.paymentRef,
|
||||
payment.idempotencyKey,
|
||||
]) ??
|
||||
'-';
|
||||
final comment = _firstNonEmpty([
|
||||
payment.failureReason,
|
||||
payment.failureCode,
|
||||
payment.state,
|
||||
]) ??
|
||||
'';
|
||||
|
||||
return OperationItem(
|
||||
status: statusFromPayment(payment),
|
||||
fileName: _extractFileName(payment.metadata),
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
toAmount: toAmount,
|
||||
toCurrency: toCurrency,
|
||||
payId: payId,
|
||||
paymentRef: payment.paymentRef,
|
||||
cardNumber: null,
|
||||
name: name,
|
||||
date: resolvePaymentDate(payment),
|
||||
comment: comment,
|
||||
);
|
||||
}
|
||||
|
||||
OperationStatus statusFromPayment(Payment payment) {
|
||||
final normalized = _normalizePaymentState(payment.state);
|
||||
switch (normalized) {
|
||||
case 'SUCCESS':
|
||||
return OperationStatus.success;
|
||||
case 'FAILED':
|
||||
case 'CANCELLED':
|
||||
return OperationStatus.error;
|
||||
default:
|
||||
return OperationStatus.processing;
|
||||
}
|
||||
}
|
||||
|
||||
DateTime resolvePaymentDate(Payment payment) {
|
||||
final createdAt = payment.createdAt;
|
||||
if (createdAt != null) return createdAt.toLocal();
|
||||
|
||||
final expiresAt = payment.lastQuote?.fxQuote?.expiresAtUnixMs;
|
||||
if (expiresAt != null && expiresAt > 0) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(expiresAt, isUtc: true).toLocal();
|
||||
}
|
||||
|
||||
return DateTime.fromMillisecondsSinceEpoch(0);
|
||||
}
|
||||
|
||||
String? paymentIdFromOperation(OperationItem operation) {
|
||||
final candidates = [
|
||||
operation.paymentRef,
|
||||
operation.payId,
|
||||
];
|
||||
for (final candidate in candidates) {
|
||||
final trimmed = candidate?.trim();
|
||||
if (trimmed != null && trimmed.isNotEmpty && trimmed != '-') {
|
||||
return trimmed;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _extractFileName(Map<String, String>? metadata) {
|
||||
if (metadata == null || metadata.isEmpty) return null;
|
||||
return _firstNonEmpty([
|
||||
metadata['upload_filename'],
|
||||
metadata['upload_file_name'],
|
||||
metadata['filename'],
|
||||
]);
|
||||
}
|
||||
|
||||
String? _firstNonEmpty(List<String?> values) {
|
||||
for (final value in values) {
|
||||
final trimmed = value?.trim();
|
||||
if (trimmed != null && trimmed.isNotEmpty) return trimmed;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _normalizePaymentState(String? raw) {
|
||||
final trimmed = (raw ?? '').trim().toUpperCase();
|
||||
if (trimmed.startsWith('PAYMENT_STATE_')) {
|
||||
return trimmed.substring('PAYMENT_STATE_'.length);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
double _parseAmount(String? amount) {
|
||||
if (amount == null || amount.trim().isEmpty) return 0;
|
||||
return double.tryParse(amount) ?? 0;
|
||||
}
|
||||
Reference in New Issue
Block a user