diff --git a/frontend/pshared/lib/data/dto/payment/operation.dart b/frontend/pshared/lib/data/dto/payment/operation.dart index 8a2365c4..42b0a106 100644 --- a/frontend/pshared/lib/data/dto/payment/operation.dart +++ b/frontend/pshared/lib/data/dto/payment/operation.dart @@ -1,6 +1,7 @@ class PaymentOperationDTO { final String? stepRef; final String? operationRef; + final String? gateway; final String? code; final String? state; final String? label; @@ -12,6 +13,7 @@ class PaymentOperationDTO { const PaymentOperationDTO({ this.stepRef, this.operationRef, + this.gateway, this.code, this.state, this.label, @@ -25,6 +27,7 @@ class PaymentOperationDTO { PaymentOperationDTO( stepRef: _asString(json['stepRef'] ?? json['step_ref']), operationRef: _asString(json['operationRef'] ?? json['operation_ref']), + gateway: _asString(json['gateway']), code: _asString(json['code']), state: _asString(json['state']), label: _asString(json['label']), @@ -39,6 +42,7 @@ class PaymentOperationDTO { Map toJson() => { 'stepRef': stepRef, 'operationRef': operationRef, + 'gateway': gateway, 'code': code, 'state': state, 'label': label, diff --git a/frontend/pshared/lib/data/mapper/payment/operation.dart b/frontend/pshared/lib/data/mapper/payment/operation.dart index 5114d0c0..4ab8e3fd 100644 --- a/frontend/pshared/lib/data/mapper/payment/operation.dart +++ b/frontend/pshared/lib/data/mapper/payment/operation.dart @@ -1,11 +1,11 @@ import 'package:pshared/data/dto/payment/operation.dart'; import 'package:pshared/models/payment/execution_operation.dart'; - extension PaymentOperationDTOMapper on PaymentOperationDTO { PaymentExecutionOperation toDomain() => PaymentExecutionOperation( stepRef: stepRef, operationRef: operationRef, + gateway: gateway, code: code, state: state, label: label, @@ -20,6 +20,7 @@ extension PaymentExecutionOperationMapper on PaymentExecutionOperation { PaymentOperationDTO toDTO() => PaymentOperationDTO( stepRef: stepRef, operationRef: operationRef, + gateway: gateway, code: code, state: state, label: label, diff --git a/frontend/pshared/lib/models/payment/execution_operation.dart b/frontend/pshared/lib/models/payment/execution_operation.dart index a2ef8b74..427f93da 100644 --- a/frontend/pshared/lib/models/payment/execution_operation.dart +++ b/frontend/pshared/lib/models/payment/execution_operation.dart @@ -1,6 +1,7 @@ class PaymentExecutionOperation { final String? stepRef; final String? operationRef; + final String? gateway; final String? code; final String? state; final String? label; @@ -12,6 +13,7 @@ class PaymentExecutionOperation { const PaymentExecutionOperation({ required this.stepRef, required this.operationRef, + required this.gateway, required this.code, required this.state, required this.label, diff --git a/frontend/pshared/lib/models/payment/operation.dart b/frontend/pshared/lib/models/payment/operation.dart index 2beb03af..a8ba37b9 100644 --- a/frontend/pshared/lib/models/payment/operation.dart +++ b/frontend/pshared/lib/models/payment/operation.dart @@ -1,7 +1,6 @@ import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/status.dart'; - class OperationItem { final OperationStatus status; final String? fileName; @@ -11,6 +10,8 @@ class OperationItem { final String toCurrency; final String payId; final String? paymentRef; + final String? operationRef; + final String? gatewayService; final String? cardNumber; final PaymentMethod? paymentMethod; final String name; @@ -26,6 +27,8 @@ class OperationItem { required this.toCurrency, required this.payId, this.paymentRef, + this.operationRef, + this.gatewayService, this.cardNumber, this.paymentMethod, required this.name, diff --git a/frontend/pshared/lib/service/payment/documents.dart b/frontend/pshared/lib/service/payment/documents.dart index 8408927c..37c0b1df 100644 --- a/frontend/pshared/lib/service/payment/documents.dart +++ b/frontend/pshared/lib/service/payment/documents.dart @@ -8,27 +8,27 @@ class PaymentDocumentsService { static final _logger = Logger('service.payment_documents'); static const String _objectType = Services.payments; - static Future getAct( + static Future getOperationDocument( String organizationRef, - String paymentRef, { - String? operationRef, - }) async { - final query = {'payment_ref': paymentRef}; - final operationRefValue = operationRef; - if (operationRefValue != null && operationRefValue.isNotEmpty) { - query['operation_ref'] = operationRefValue; - query['operationRef'] = operationRefValue; - } + String gatewayService, + String operationRef, + ) async { + final query = { + 'gateway_service': gatewayService, + 'operation_ref': operationRef, + }; final queryString = Uri(queryParameters: query).query; - final url = '/documents/act/$organizationRef?$queryString'; - _logger.fine('Downloading act document for payment $paymentRef'); + final url = '/documents/operation/$organizationRef?$queryString'; + _logger.fine( + 'Downloading operation document for operation $operationRef in gateway $gatewayService', + ); final response = await AuthorizationService.getGETBinaryResponse( _objectType, url, ); final filename = _filenameFromDisposition(response.header('content-disposition')) ?? - 'act_$paymentRef.pdf'; + 'operation_$operationRef.pdf'; final mimeType = response.header('content-type') ?? 'application/pdf'; return DownloadedFile( bytes: response.bytes, diff --git a/frontend/pweb/lib/controllers/payments/details.dart b/frontend/pweb/lib/controllers/payments/details.dart index 06d1e7ce..7f99a55e 100644 --- a/frontend/pweb/lib/controllers/payments/details.dart +++ b/frontend/pweb/lib/controllers/payments/details.dart @@ -26,9 +26,20 @@ class PaymentDetailsController extends ChangeNotifier { bool get canDownload { final current = _payment; if (current == null) return false; - final status = statusFromPayment(current); - final paymentRef = current.paymentRef ?? ''; - return status == OperationStatus.success && paymentRef.trim().isNotEmpty; + if (statusFromPayment(current) != OperationStatus.success) return false; + return primaryOperationDocumentRequest != null; + } + + OperationDocumentRequestModel? get primaryOperationDocumentRequest { + final current = _payment; + if (current == null) return null; + for (final operation in current.operations) { + final request = operationDocumentRequest(operation); + if (request != null) { + return request; + } + } + return null; } OperationDocumentRequestModel? operationDocumentRequest( @@ -37,18 +48,17 @@ class PaymentDetailsController extends ChangeNotifier { final current = _payment; if (current == null) return null; - final paymentRef = current.paymentRef?.trim() ?? ''; - if (paymentRef.isEmpty) return null; - final operationRef = operation.operationRef; if (operationRef == null || operationRef.isEmpty) return null; + final gatewayService = operation.gateway; + if (gatewayService == null || gatewayService.isEmpty) return null; final pair = parseOperationCodePair(operation.code); if (pair == null) return null; if (pair.operation != 'card_payout' || pair.action != 'send') return null; return OperationDocumentRequestModel( - paymentRef: paymentRef, + gatewayService: gatewayService, operationRef: operationRef, ); } diff --git a/frontend/pweb/lib/models/documents/operation.dart b/frontend/pweb/lib/models/documents/operation.dart index 6fbc1547..c669d04c 100644 --- a/frontend/pweb/lib/models/documents/operation.dart +++ b/frontend/pweb/lib/models/documents/operation.dart @@ -1,9 +1,9 @@ class OperationDocumentRequestModel { - final String paymentRef; + final String gatewayService; final String operationRef; const OperationDocumentRequestModel({ - required this.paymentRef, + required this.gatewayService, required this.operationRef, }); -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/models/report/operation/document.dart b/frontend/pweb/lib/models/report/operation/document.dart new file mode 100644 index 00000000..9379305e --- /dev/null +++ b/frontend/pweb/lib/models/report/operation/document.dart @@ -0,0 +1,9 @@ +class OperationDocumentInfo { + final String operationRef; + final String gatewayService; + + const OperationDocumentInfo({ + required this.operationRef, + required this.gatewayService, + }); +} diff --git a/frontend/pweb/lib/pages/report/details/page.dart b/frontend/pweb/lib/pages/report/details/page.dart index 5697c4c2..998d2b3b 100644 --- a/frontend/pweb/lib/pages/report/details/page.dart +++ b/frontend/pweb/lib/pages/report/details/page.dart @@ -65,7 +65,15 @@ class _PaymentDetailsView extends StatelessWidget { payment: payment, onBack: () => _handleBack(context), onDownloadAct: controller.canDownload - ? () => downloadPaymentAct(context, payment.paymentRef ?? '') + ? () { + final request = controller.primaryOperationDocumentRequest; + if (request == null) return; + downloadPaymentAct( + context, + gatewayService: request.gatewayService, + operationRef: request.operationRef, + ); + } : null, canDownloadOperationDocument: controller.canDownloadOperationDocument, @@ -74,7 +82,7 @@ class _PaymentDetailsView extends StatelessWidget { if (request == null) return; downloadPaymentAct( context, - request.paymentRef, + gatewayService: request.gatewayService, operationRef: request.operationRef, ); }, diff --git a/frontend/pweb/lib/pages/report/table/row.dart b/frontend/pweb/lib/pages/report/table/row.dart index a8797bc5..3ee87b52 100644 --- a/frontend/pweb/lib/pages/report/table/row.dart +++ b/frontend/pweb/lib/pages/report/table/row.dart @@ -9,7 +9,6 @@ import 'package:pweb/utils/report/download_act.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - class OperationRow { static DataRow build(OperationItem op, BuildContext context) { final isUnknownDate = op.date.millisecondsSinceEpoch == 0; @@ -18,29 +17,37 @@ class OperationRow { final dateLabel = isUnknownDate ? '-' : '${TimeOfDay.fromDateTime(localDate).format(context)}\n' - '${localDate.toIso8601String().split("T").first}'; + '${localDate.toIso8601String().split("T").first}'; - final canDownload = op.status == OperationStatus.success && - (op.paymentRef ?? '').trim().isNotEmpty; + final canDownload = + op.status == OperationStatus.success && + (op.operationRef ?? '').trim().isNotEmpty && + (op.gatewayService ?? '').trim().isNotEmpty; final documentCell = canDownload ? TextButton.icon( - onPressed: () => downloadPaymentAct(context, op.paymentRef ?? ''), + onPressed: () => downloadPaymentAct( + context, + gatewayService: op.gatewayService ?? '', + operationRef: op.operationRef ?? '', + ), icon: const Icon(Icons.download), label: Text(loc.downloadAct), ) : Text(op.fileName ?? ''); - return DataRow(cells: [ - DataCell(OperationStatusBadge(status: op.status)), - DataCell(documentCell), - DataCell(Text('${amountToString(op.amount)} ${op.currency}')), - DataCell(Text('${amountToString(op.toAmount)} ${op.toCurrency}')), - DataCell(Text(op.payId)), - DataCell(Text(op.cardNumber ?? '-')), - DataCell(Text(op.name)), - DataCell(Text(dateLabel)), - DataCell(Text(op.comment)), - ]); + return DataRow( + cells: [ + DataCell(OperationStatusBadge(status: op.status)), + DataCell(documentCell), + DataCell(Text('${amountToString(op.amount)} ${op.currency}')), + DataCell(Text('${amountToString(op.toAmount)} ${op.toCurrency}')), + DataCell(Text(op.payId)), + DataCell(Text(op.cardNumber ?? '-')), + DataCell(Text(op.name)), + DataCell(Text(dateLabel)), + DataCell(Text(op.comment)), + ], + ); } } diff --git a/frontend/pweb/lib/utils/report/download_act.dart b/frontend/pweb/lib/utils/report/download_act.dart index d0e79c97..55e7b8ea 100644 --- a/frontend/pweb/lib/utils/report/download_act.dart +++ b/frontend/pweb/lib/utils/report/download_act.dart @@ -10,18 +10,18 @@ import 'package:pweb/utils/error/snackbar.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; - Future downloadPaymentAct( - BuildContext context, - String paymentRef, { - String? operationRef, + BuildContext context, { + required String gatewayService, + required String operationRef, }) async { final organizations = context.read(); if (!organizations.isOrganizationSet) { return; } - final trimmed = paymentRef.trim(); - if (trimmed.isEmpty) { + final gateway = gatewayService.trim(); + final operation = operationRef.trim(); + if (gateway.isEmpty || operation.isEmpty) { return; } @@ -29,10 +29,10 @@ Future downloadPaymentAct( await executeActionWithNotification( context: context, action: () async { - final file = await PaymentDocumentsService.getAct( + final file = await PaymentDocumentsService.getOperationDocument( organizations.current.id, - trimmed, - operationRef: operationRef, + gateway, + operation, ); await downloadFile(file); }, diff --git a/frontend/pweb/lib/utils/report/payment_mapper.dart b/frontend/pweb/lib/utils/report/payment_mapper.dart index f76b2ad3..2e9a7e08 100644 --- a/frontend/pweb/lib/utils/report/payment_mapper.dart +++ b/frontend/pweb/lib/utils/report/payment_mapper.dart @@ -4,6 +4,9 @@ import 'package:pshared/models/payment/state.dart'; import 'package:pshared/models/payment/status.dart'; import 'package:pshared/utils/money.dart'; +import 'package:pweb/models/report/operation/document.dart'; +import 'package:pweb/utils/payment/operation_code.dart'; + OperationItem mapPaymentToOperation(Payment payment) { final debit = payment.lastQuote?.amounts?.sourceDebitTotal; @@ -33,6 +36,7 @@ OperationItem mapPaymentToOperation(Payment payment) { payment.state, ]) ?? ''; + final operationDocument = _resolveOperationDocument(payment); return OperationItem( status: statusFromPayment(payment), @@ -43,6 +47,8 @@ OperationItem mapPaymentToOperation(Payment payment) { toCurrency: toCurrency, payId: payId, paymentRef: payment.paymentRef, + operationRef: operationDocument?.operationRef, + gatewayService: operationDocument?.gatewayService, cardNumber: null, name: name, date: resolvePaymentDate(payment), @@ -50,6 +56,25 @@ OperationItem mapPaymentToOperation(Payment payment) { ); } +OperationDocumentInfo? _resolveOperationDocument(Payment payment) { + for (final operation in payment.operations) { + final operationRef = operation.operationRef; + final gatewayService = operation.gateway; + if (operationRef == null || operationRef.isEmpty) continue; + if (gatewayService == null || gatewayService.isEmpty) continue; + + final pair = parseOperationCodePair(operation.code); + if (pair == null) continue; + if (pair.operation != 'card_payout' || pair.action != 'send') continue; + + return OperationDocumentInfo( + operationRef: operationRef, + gatewayService: gatewayService, + ); + } + return null; +} + OperationStatus statusFromPayment(Payment payment) { switch (payment.orchestrationState) { case PaymentOrchestrationState.failed: @@ -108,4 +133,4 @@ String? _firstNonEmpty(List values) { if (trimmed != null && trimmed.isNotEmpty) return trimmed; } return null; -} +} \ No newline at end of file