SEND063
This commit is contained in:
23
frontend/pweb/lib/utils/payment/operation_code.dart
Normal file
23
frontend/pweb/lib/utils/payment/operation_code.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
class OperationCodePair {
|
||||
final String operation;
|
||||
final String action;
|
||||
|
||||
const OperationCodePair({required this.operation, required this.action});
|
||||
}
|
||||
|
||||
OperationCodePair? parseOperationCodePair(String? code) {
|
||||
final normalized = code?.trim().toLowerCase();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
|
||||
final parts = normalized.split('.').where((part) => part.isNotEmpty).toList();
|
||||
if (parts.length >= 4 && (parts.first == 'hop' || parts.first == 'edge')) {
|
||||
return OperationCodePair(operation: parts[2], action: parts[3]);
|
||||
}
|
||||
if (parts.length >= 2) {
|
||||
return OperationCodePair(
|
||||
operation: parts[parts.length - 2],
|
||||
action: parts.last,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -7,50 +7,150 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
class StatusView {
|
||||
final String label;
|
||||
final Color color;
|
||||
final Color backgroundColor;
|
||||
final Color foregroundColor;
|
||||
|
||||
const StatusView(this.label, this.color);
|
||||
}
|
||||
StatusView({
|
||||
required this.label,
|
||||
required this.backgroundColor,
|
||||
Color? foregroundColor,
|
||||
}) : foregroundColor =
|
||||
foregroundColor ??
|
||||
(backgroundColor.computeLuminance() > 0.5
|
||||
? Colors.black
|
||||
: Colors.white);
|
||||
|
||||
StatusView statusView(AppLocalizations l10n, String? raw) {
|
||||
final trimmed = (raw ?? '').trim();
|
||||
final upper = trimmed.toUpperCase();
|
||||
final normalized = upper.startsWith('PAYMENT_STATE_')
|
||||
? upper.substring('PAYMENT_STATE_'.length)
|
||||
: upper;
|
||||
|
||||
switch (normalized) {
|
||||
case 'SETTLED':
|
||||
return StatusView(l10n.paymentStatusPending, Colors.orange);
|
||||
case 'SUCCESS':
|
||||
return StatusView(l10n.paymentStatusSuccessful, Colors.green);
|
||||
case 'FUNDS_RESERVED':
|
||||
return StatusView(l10n.paymentStatusReserved, Colors.blue);
|
||||
case 'ACCEPTED':
|
||||
return StatusView(l10n.paymentStatusProcessing, Colors.orange);
|
||||
case 'SUBMITTED':
|
||||
return StatusView(l10n.paymentStatusProcessing, Colors.blue);
|
||||
case 'FAILED':
|
||||
return StatusView(l10n.paymentStatusFailed, Colors.red);
|
||||
case 'CANCELLED':
|
||||
return StatusView(l10n.paymentStatusCancelled, Colors.grey);
|
||||
case 'UNSPECIFIED':
|
||||
case '':
|
||||
default:
|
||||
return StatusView(l10n.paymentStatusPending, Colors.grey);
|
||||
}
|
||||
Color get color => backgroundColor;
|
||||
}
|
||||
|
||||
StatusView operationStatusView(
|
||||
AppLocalizations l10n,
|
||||
ColorScheme scheme,
|
||||
OperationStatus status,
|
||||
) {
|
||||
switch (status) {
|
||||
case OperationStatus.success:
|
||||
return statusView(l10n, 'SUCCESS');
|
||||
case OperationStatus.error:
|
||||
return statusView(l10n, 'FAILED');
|
||||
case OperationStatus.processing:
|
||||
return statusView(l10n, 'ACCEPTED');
|
||||
return operationStatusViewFromToken(
|
||||
l10n,
|
||||
scheme,
|
||||
operationStatusTokenFromEnum(status),
|
||||
);
|
||||
}
|
||||
|
||||
StatusView operationStatusViewFromToken(
|
||||
AppLocalizations l10n,
|
||||
ColorScheme scheme,
|
||||
String? rawState, {
|
||||
String? fallbackLabel,
|
||||
}) {
|
||||
final token = normalizeOperationStatusToken(rawState);
|
||||
switch (token) {
|
||||
case 'success':
|
||||
case 'succeeded':
|
||||
case 'completed':
|
||||
case 'confirmed':
|
||||
case 'settled':
|
||||
return StatusView(
|
||||
label: l10n.operationStatusSuccessful,
|
||||
backgroundColor: scheme.tertiaryContainer,
|
||||
foregroundColor: scheme.onTertiaryContainer,
|
||||
);
|
||||
case 'skipped':
|
||||
return StatusView(
|
||||
label: l10n.operationStepStateSkipped,
|
||||
backgroundColor: scheme.secondaryContainer,
|
||||
foregroundColor: scheme.onSecondaryContainer,
|
||||
);
|
||||
case 'error':
|
||||
case 'failed':
|
||||
case 'rejected':
|
||||
case 'aborted':
|
||||
return StatusView(
|
||||
label: l10n.operationStatusUnsuccessful,
|
||||
backgroundColor: scheme.errorContainer,
|
||||
foregroundColor: scheme.onErrorContainer,
|
||||
);
|
||||
case 'cancelled':
|
||||
case 'canceled':
|
||||
return StatusView(
|
||||
label: l10n.paymentStatusCancelled,
|
||||
backgroundColor: scheme.surfaceContainerHighest,
|
||||
foregroundColor: scheme.onSurfaceVariant,
|
||||
);
|
||||
case 'processing':
|
||||
case 'running':
|
||||
case 'executing':
|
||||
case 'in_progress':
|
||||
case 'started':
|
||||
return StatusView(
|
||||
label: l10n.paymentStatusProcessing,
|
||||
backgroundColor: scheme.primaryContainer,
|
||||
foregroundColor: scheme.onPrimaryContainer,
|
||||
);
|
||||
case 'pending':
|
||||
case 'queued':
|
||||
case 'waiting':
|
||||
case 'created':
|
||||
case 'scheduled':
|
||||
return StatusView(
|
||||
label: l10n.operationStatusPending,
|
||||
backgroundColor: scheme.secondary,
|
||||
foregroundColor: scheme.onSecondary,
|
||||
);
|
||||
case 'needs_attention':
|
||||
return StatusView(
|
||||
label: l10n.operationStepStateNeedsAttention,
|
||||
backgroundColor: scheme.tertiary,
|
||||
foregroundColor: scheme.onTertiary,
|
||||
);
|
||||
case 'retrying':
|
||||
return StatusView(
|
||||
label: l10n.operationStepStateRetrying,
|
||||
backgroundColor: scheme.primary,
|
||||
foregroundColor: scheme.onPrimary,
|
||||
);
|
||||
default:
|
||||
return StatusView(
|
||||
label: fallbackLabel ?? humanizeOperationStatusToken(token),
|
||||
backgroundColor: scheme.surfaceContainerHighest,
|
||||
foregroundColor: scheme.onSurfaceVariant,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String operationStatusTokenFromEnum(OperationStatus status) {
|
||||
switch (status) {
|
||||
case OperationStatus.pending:
|
||||
return 'pending';
|
||||
case OperationStatus.processing:
|
||||
return 'processing';
|
||||
case OperationStatus.retrying:
|
||||
return 'retrying';
|
||||
case OperationStatus.success:
|
||||
return 'success';
|
||||
case OperationStatus.skipped:
|
||||
return 'skipped';
|
||||
case OperationStatus.cancelled:
|
||||
return 'cancelled';
|
||||
case OperationStatus.needsAttention:
|
||||
return 'needs_attention';
|
||||
case OperationStatus.error:
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
|
||||
String normalizeOperationStatusToken(String? state) {
|
||||
final normalized = (state ?? '').trim().toLowerCase();
|
||||
if (normalized.isEmpty) return 'pending';
|
||||
return normalized
|
||||
.replaceFirst(RegExp(r'^step_execution_state_'), '')
|
||||
.replaceFirst(RegExp(r'^orchestration_state_'), '');
|
||||
}
|
||||
|
||||
String humanizeOperationStatusToken(String token) {
|
||||
final parts = token.split('_').where((part) => part.isNotEmpty).toList();
|
||||
if (parts.isEmpty) return token;
|
||||
return parts
|
||||
.map(
|
||||
(part) => '${part[0].toUpperCase()}${part.substring(1).toLowerCase()}',
|
||||
)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import 'package:pweb/utils/error/snackbar.dart';
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
Future<void> downloadPaymentAct(BuildContext context, String paymentRef) async {
|
||||
Future<void> downloadPaymentAct(
|
||||
BuildContext context,
|
||||
String paymentRef, {
|
||||
String? operationRef,
|
||||
}) async {
|
||||
final organizations = context.read<OrganizationsProvider>();
|
||||
if (!organizations.isOrganizationSet) {
|
||||
return;
|
||||
@@ -28,6 +32,7 @@ Future<void> downloadPaymentAct(BuildContext context, String paymentRef) async {
|
||||
final file = await PaymentDocumentsService.getAct(
|
||||
organizations.current.id,
|
||||
trimmed,
|
||||
operationRef: operationRef,
|
||||
);
|
||||
await downloadFile(file);
|
||||
},
|
||||
|
||||
12
frontend/pweb/lib/utils/report/operations/state_mapper.dart
Normal file
12
frontend/pweb/lib/utils/report/operations/state_mapper.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/utils/payment/status_view.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
StatusView resolveStepStateView(BuildContext context, String? rawState) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return operationStatusViewFromToken(loc, scheme, rawState);
|
||||
}
|
||||
15
frontend/pweb/lib/utils/report/operations/time_format.dart
Normal file
15
frontend/pweb/lib/utils/report/operations/time_format.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:pweb/utils/report/format.dart';
|
||||
|
||||
|
||||
String formatCompletedAt(BuildContext context, DateTime? completedAt) {
|
||||
final value = meaningfulDate(completedAt);
|
||||
return formatDateLabel(context, value);
|
||||
}
|
||||
|
||||
DateTime? meaningfulDate(DateTime? value) {
|
||||
if (value == null) return null;
|
||||
if (value.year <= 1) return null;
|
||||
return value;
|
||||
}
|
||||
59
frontend/pweb/lib/utils/report/operations/title_mapper.dart
Normal file
59
frontend/pweb/lib/utils/report/operations/title_mapper.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:pweb/utils/payment/operation_code.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
String resolveOperationTitle(AppLocalizations loc, String? code) {
|
||||
final pair = parseOperationCodePair(code);
|
||||
if (pair == null) return '-';
|
||||
|
||||
final operation = _localizedOperation(loc, pair.operation);
|
||||
final action = _localizedAction(loc, pair.action);
|
||||
return loc.paymentOperationPair(operation, action);
|
||||
}
|
||||
|
||||
String _localizedOperation(AppLocalizations loc, String operation) {
|
||||
switch (operation) {
|
||||
case 'card_payout':
|
||||
return loc.paymentOperationCardPayout;
|
||||
case 'crypto':
|
||||
return loc.paymentOperationCrypto;
|
||||
case 'settlement':
|
||||
return loc.paymentOperationSettlement;
|
||||
case 'ledger':
|
||||
return loc.paymentOperationLedger;
|
||||
default:
|
||||
return _humanizeToken(operation);
|
||||
}
|
||||
}
|
||||
|
||||
String _localizedAction(AppLocalizations loc, String action) {
|
||||
switch (action) {
|
||||
case 'send':
|
||||
return loc.paymentOperationActionSend;
|
||||
case 'observe':
|
||||
return loc.paymentOperationActionObserve;
|
||||
case 'fx_convert':
|
||||
return loc.paymentOperationActionFxConvert;
|
||||
case 'credit':
|
||||
return loc.paymentOperationActionCredit;
|
||||
case 'block':
|
||||
return loc.paymentOperationActionBlock;
|
||||
case 'debit':
|
||||
return loc.paymentOperationActionDebit;
|
||||
case 'release':
|
||||
return loc.paymentOperationActionRelease;
|
||||
case 'move':
|
||||
return loc.paymentOperationActionMove;
|
||||
default:
|
||||
return _humanizeToken(action);
|
||||
}
|
||||
}
|
||||
|
||||
String _humanizeToken(String token) {
|
||||
final parts = token.split('_').where((part) => part.isNotEmpty).toList();
|
||||
if (parts.isEmpty) return token;
|
||||
return parts
|
||||
.map((part) => '${part[0].toUpperCase()}${part.substring(1)}')
|
||||
.join(' ');
|
||||
}
|
||||
@@ -56,11 +56,14 @@ OperationStatus statusFromPayment(Payment payment) {
|
||||
return OperationStatus.error;
|
||||
case PaymentOrchestrationState.settled:
|
||||
return OperationStatus.success;
|
||||
case PaymentOrchestrationState.created:
|
||||
case PaymentOrchestrationState.executing:
|
||||
case PaymentOrchestrationState.needsAttention:
|
||||
case PaymentOrchestrationState.unspecified:
|
||||
return OperationStatus.needsAttention;
|
||||
case PaymentOrchestrationState.created:
|
||||
return OperationStatus.pending;
|
||||
case PaymentOrchestrationState.executing:
|
||||
return OperationStatus.processing;
|
||||
case PaymentOrchestrationState.unspecified:
|
||||
return OperationStatus.pending;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user