Acciones con Popup en RAP: Solicitar datos del usuario antes de la ejecución

Kategorie
RAP
Veröffentlicht
Autor
Johannes

A veces un simple clic en un botón no es suficiente. Antes de cancelar una reserva, el usuario debe indicar un motivo. Antes de una reprogramación, se deben capturar nuevos datos. Acciones con Popup en RAP permiten exactamente esto: entradas de usuario estructuradas antes de ejecutar una Action.

El concepto: Action con Parameter-Entity

Una acción con popup consta de tres componentes:

  1. Abstract Entity: Define la estructura del popup (campos, tipos de datos)
  2. Behavior Definition: Vincula la Action con el parámetro
  3. Implementation: Procesa los valores ingresados
┌─────────────────────────────────────────────────────────────────┐
│ Cancelar reserva [X] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Motivo de cancelación * │
│ ┌─────────────────────────────────────┐ │
│ │ Cambio del cliente ▼ │ │
│ └─────────────────────────────────────┘ │
│ │
│ Comentario │
│ ┌─────────────────────────────────────┐ │
│ │ El cliente reprogramó a un vuelo │ │
│ │ posterior a Múnich. │ │
│ └─────────────────────────────────────┘ │
│ │
│ ☑ Notificar al cliente por email │
│ │
│ Monto de reembolso │
│ ┌───────────────┐ EUR │
│ │ 450,00 │ │
│ └───────────────┘ │
│ │
│ [ Cancelar ] [ Confirmar ] │
└─────────────────────────────────────────────────────────────────┘

Paso 1: Definir Abstract Entity

La Abstract Entity describe los campos del diálogo popup:

@EndUserText.label: 'Datos de cancelación'
define abstract entity ZA_CancelBookingParams
{
@EndUserText.label: 'Motivo de cancelación'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_CancellationReasonVH', element: 'ReasonCode' }
}]
CancellationReason : abap.char(2);
@EndUserText.label: 'Comentario'
@UI.multiLineText: true
Remarks : abap.string(500);
@EndUserText.label: 'Notificar al cliente'
NotifyCustomer : abap_boolean;
@EndUserText.label: 'Monto de reembolso'
@Semantics.amount.currencyCode: 'Currency'
RefundAmount : abap.curr(15,2);
@EndUserText.label: 'Moneda'
@Semantics.currencyCode: true
Currency : abap.cuky;
}

Anotaciones importantes para el popup:

AnotaciónEfecto
@EndUserText.labelEtiqueta del campo en el popup
@Consumption.valueHelpDefinitionDropdown/Value Help
@UI.multiLineText: trueCampo de texto multilínea
@Semantics.amount.currencyCodeCampo de moneda con formato

Paso 2: Behavior Definition

Vincula la Action con el parámetro:

managed implementation in class zbp_i_flightbooking unique;
strict ( 2 );
define behavior for ZI_FlightBooking alias FlightBooking
persistent table zflight_book
lock master
authorization master ( instance )
{
// CRUD estándar
create;
update;
delete;
// Action CON Parameter-Entity
action cancelBooking
parameter ZA_CancelBookingParams
result [1] $self;
// Action sin parámetro (para comparar)
action confirmBooking result [1] $self;
// Validación para cancelación
validation validateCancellation on save
{ field BookingStatus; }
}

La palabra clave parameter hace que Fiori Elements genere automáticamente un diálogo.

Paso 3: Implementación de la Action

En la Behavior Implementation Class procesas los parámetros:

CLASS lhc_flightbooking DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS cancelBooking FOR MODIFY
IMPORTING keys FOR ACTION FlightBooking~cancelBooking
RESULT result.
ENDCLASS.
CLASS lhc_flightbooking IMPLEMENTATION.
METHOD cancelBooking.
" Leer datos actuales
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
FIELDS ( booking_id booking_status flight_price currency_code )
WITH CORRESPONDING #( keys )
RESULT DATA(bookings).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Obtener reserva del resultado
READ TABLE bookings INTO DATA(booking)
WITH KEY booking_id = <key>-booking_id.
IF sy-subrc <> 0.
" Reserva no encontrada
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Reserva no encontrada' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" === Leer parámetros del popup ===
DATA(cancel_reason) = <key>-%param-CancellationReason.
DATA(remarks) = <key>-%param-Remarks.
DATA(notify) = <key>-%param-NotifyCustomer.
DATA(refund_amount) = <key>-%param-RefundAmount.
DATA(currency) = <key>-%param-Currency.
" Validación: Campo obligatorio motivo de cancelación
IF cancel_reason IS INITIAL.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'El motivo de cancelación es obligatorio' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Validación: Reembolso no mayor que precio de reserva
IF refund_amount > booking-flight_price.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |El reembolso no puede ser mayor que {
booking-flight_price } { booking-currency_code
}| )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Establecer estado a Cancelado
MODIFY ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
UPDATE FIELDS ( booking_status cancellation_reason remarks )
WITH VALUE #( (
%tky = <key>-%tky
booking_status = 'X' " Cancelled
cancellation_reason = cancel_reason
remarks = remarks
) ).
" Opcional: Notificar al cliente
IF notify = abap_true.
send_cancellation_email(
booking_id = booking-booking_id
reason = cancel_reason
remarks = remarks
).
ENDIF.
" Opcional: Procesar reembolso
IF refund_amount > 0.
process_refund(
booking_id = booking-booking_id
amount = refund_amount
currency = currency
).
ENDIF.
ENDLOOP.
" Devolver datos actualizados
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated_bookings).
result = VALUE #( FOR bk IN updated_bookings (
%tky = bk-%tky
%param = bk
) ).
" Mensaje Success
APPEND VALUE #(
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = |{ lines( updated_bookings ) } reserva(s) cancelada(s)| )
) TO reported-flightbooking.
ENDMETHOD.
ENDCLASS.

Anotaciones UI para el layout del Popup

Refina el layout del popup con anotaciones adicionales:

@EndUserText.label: 'Datos de cancelación'
define abstract entity ZA_CancelBookingParams
{
@EndUserText.label: 'Motivo de cancelación'
@UI.defaultValue: 'KA'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_CancellationReasonVH', element: 'ReasonCode' },
useForValidation: true
}]
CancellationReason : abap.char(2);
@EndUserText.label: 'Comentario'
@UI.multiLineText: true
@UI.placeholder: 'Opcional: Información adicional sobre la cancelación'
Remarks : abap.string(500);
@EndUserText.label: 'Notificar al cliente'
@UI.defaultValue: 'true'
NotifyCustomer : abap_boolean;
@EndUserText.label: 'Monto de reembolso'
@Semantics.amount.currencyCode: 'Currency'
@UI.defaultValue: '0.00'
RefundAmount : abap.curr(15,2);
@EndUserText.label: 'Moneda'
@Semantics.currencyCode: true
@UI.defaultValue: 'EUR'
@Consumption.valueHelpDefinition: [{
entity: { name: 'I_Currency', element: 'Currency' }
}]
Currency : abap.cuky;
}

Anotaciones UI importantes

AnotaciónEfectoEjemplo
@UI.defaultValueValor predeterminado'EUR'
@UI.placeholderTexto placeholder'Ingresar comentario...'
@UI.multiLineTextCampo de entrada multilíneaPara textos largos
@UI.hiddenOcultar campoCampos técnicos
@Consumption.valueHelpDefinitionValue Help/DropdownCampos de selección

Diferentes tipos de campos en el Popup

@EndUserText.label: 'Prioridad'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_PriorityVH', element: 'Priority' }
}]
Priority : abap.char(1);

Campo de fecha

@EndUserText.label: 'Nueva fecha de vuelo'
@UI.defaultValue: '#(TODAY)'
NewFlightDate : abap.dats;

Checkbox

@EndUserText.label: 'Enviar confirmación por email'
@UI.defaultValue: 'false'
SendConfirmation : abap_boolean;

Texto multilínea

@EndUserText.label: 'Justificación'
@UI.multiLineText: true
Justification : abap.string(1000);

Monto con moneda

@EndUserText.label: 'Monto'
@Semantics.amount.currencyCode: 'Currency'
Amount : abap.curr(15,2);
@EndUserText.label: 'Moneda'
@Semantics.currencyCode: true
Currency : abap.cuky;

Campo de cantidad

@EndUserText.label: 'Cantidad'
@Semantics.quantity.unitOfMeasure: 'Unit'
Quantity : abap.quan(13,3);
@EndUserText.label: 'Unidad'
@Semantics.unitOfMeasure: true
Unit : abap.unit(3);

Ejemplo completo de reserva de vuelos

Aquí un ejemplo completo End-to-End para una acción de reprogramación:

Abstract Entity: Parámetros de reprogramación

@EndUserText.label: 'Parámetros de reprogramación'
define abstract entity ZA_RescheduleParams
{
@EndUserText.label: 'Nueva fecha de vuelo'
NewFlightDate : abap.dats;
@EndUserText.label: 'Nuevo número de vuelo'
@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_FlightConnectionVH', element: 'FlightId' }
}]
NewFlightId : abap.numc(4);
@EndUserText.label: 'Aceptar diferencia de precio'
AcceptPriceDiff : abap_boolean;
@EndUserText.label: 'Comentario'
@UI.multiLineText: true
Remarks : abap.string(500);
}

Behavior Definition

define behavior for ZI_FlightBooking alias FlightBooking
{
action rescheduleBooking
parameter ZA_RescheduleParams
result [1] $self;
}

Projection Behavior

projection implementation in class zbp_c_flightbooking unique;
define behavior for ZC_FlightBooking alias FlightBooking
{
use action rescheduleBooking;
}

Implementación

METHOD rescheduleBooking.
" Leer reservas actuales
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(bookings).
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
READ TABLE bookings INTO DATA(booking)
WITH KEY booking_id = <key>-booking_id.
" === Parámetros del popup ===
DATA(new_date) = <key>-%param-NewFlightDate.
DATA(new_flight) = <key>-%param-NewFlightId.
DATA(accept_diff) = <key>-%param-AcceptPriceDiff.
DATA(remarks) = <key>-%param-Remarks.
" Validación: Fecha en el futuro
IF new_date < cl_abap_context_info=>get_system_date( ).
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'La fecha de vuelo debe ser futura' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Calcular diferencia de precio
DATA(new_price) = get_flight_price( new_flight ).
DATA(price_diff) = new_price - booking-flight_price.
" Aumento de precio sin aceptación = Error
IF price_diff > 0 AND accept_diff = abap_false.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |La diferencia de precio de { price_diff } {
booking-currency_code } debe ser aceptada| )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
CONTINUE.
ENDIF.
" Ejecutar reprogramación
MODIFY ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
UPDATE FIELDS ( flight_date flight_id flight_price remarks )
WITH VALUE #( (
%tky = <key>-%tky
flight_date = new_date
flight_id = new_flight
flight_price = new_price
remarks = remarks
) ).
ENDLOOP.
" Devolver resultado
READ ENTITIES OF zi_flightbooking IN LOCAL MODE
ENTITY FlightBooking
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(updated).
result = VALUE #( FOR bk IN updated (
%tky = bk-%tky
%param = bk
) ).
" Mensaje Success
APPEND VALUE #(
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = |{ lines( updated ) } reserva(s) reprogramada(s)| )
) TO reported-flightbooking.
ENDMETHOD.

Integración UI en CDS View

@EndUserText.label: 'Reserva de vuelo'
define view entity ZC_FlightBooking
as projection on ZI_FlightBooking
{
@UI.lineItem: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'cancelBooking',
label: 'Cancelar', criticality: #NEGATIVE },
{ type: #FOR_ACTION, dataAction: 'rescheduleBooking',
label: 'Reprogramar' }
]
@UI.identification: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'rescheduleBooking',
label: 'Reprogramar' }
]
key BookingId,
// más campos...
}

Validación de entradas del Popup

En el código de la Action (recomendado)

" Verificar campo obligatorio
IF <key>-%param-CancellationReason IS INITIAL.
APPEND VALUE #(
%tky = <key>-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'El motivo de cancelación es obligatorio' )
) TO reported-flightbooking.
APPEND VALUE #( %tky = <key>-%tky ) TO failed-flightbooking.
RETURN.
ENDIF.

Con Validation en Behavior

define behavior for ZI_FlightBooking
{
action cancelBooking
parameter ZA_CancelBookingParams
result [1] $self;
" Validation se ejecuta después de la Action
validation validateAfterCancel on save
{ field booking_status; }
}

Mejores prácticas para acciones con Popup

1. Marcar campos obligatorios

@EndUserText.label: 'Motivo *' " Asterisco en label
@ObjectModel.mandatory: true " Campo obligatorio
Reason : abap.char(2);

2. Valores predeterminados sensatos

@UI.defaultValue: 'EUR' " Para moneda
@UI.defaultValue: 'true' " Para checkbox
@UI.defaultValue: '#(TODAY)' " Para fecha

3. Value Helps para campos de selección

@Consumption.valueHelpDefinition: [{
entity: { name: 'ZI_ReasonVH', element: 'Code' },
useForValidation: true
}]

4. Mensajes de error claros

" Malo
text = 'Error en la entrada'
" Bueno
text = |El reembolso ({ refund_amount } { currency }) no puede ser mayor que el precio de reserva ({ booking-flight_price } { booking-currency_code })|

5. Limitar tamaño del Popup

Máximo 5-6 campos por popup. Para entradas más complejas: Wizard o página separada.

EscenarioRecomendación
1-2 valores adicionalesPopup-Action
Formularios complejosObject Page separada
Cambio masivoInline-Edit o app propia
Confirmación sin entradaAction sin parámetro

Resumen

Las acciones con Popup en RAP permiten entradas de usuario interactivas antes de la ejecución:

ComponenteFunción
Abstract EntityDefine campos y tipos de datos del popup
Behavior DefinitionVincula Action con parameter
ImplementationLee %param y procesa valores
Anotaciones UIControla apariencia y comportamiento

Con pocas anotaciones y estructura clara, creas diálogos amigables que se integran perfectamente en Fiori Elements.

Artículos relacionados: RAP Actions y Functions para fundamentos de Actions, RAP Mensajes para manejo de errores, RAP CDS Pattern para arquitectura simplificada y Anotaciones CDS para más posibilidades de anotación.