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:
- Abstract Entity: Define la estructura del popup (campos, tipos de datos)
- Behavior Definition: Vincula la Action con el parámetro
- 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ón | Efecto |
|---|---|
@EndUserText.label | Etiqueta del campo en el popup |
@Consumption.valueHelpDefinition | Dropdown/Value Help |
@UI.multiLineText: true | Campo de texto multilínea |
@Semantics.amount.currencyCode | Campo 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 FlightBookingpersistent table zflight_booklock masterauthorization 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ón | Efecto | Ejemplo |
|---|---|---|
@UI.defaultValue | Valor predeterminado | 'EUR' |
@UI.placeholder | Texto placeholder | 'Ingresar comentario...' |
@UI.multiLineText | Campo de entrada multilínea | Para textos largos |
@UI.hidden | Ocultar campo | Campos técnicos |
@Consumption.valueHelpDefinition | Value Help/Dropdown | Campos de selección |
Diferentes tipos de campos en el Popup
Dropdown con Value Help
@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: trueJustification : abap.string(1000);Monto con moneda
@EndUserText.label: 'Monto'@Semantics.amount.currencyCode: 'Currency'Amount : abap.curr(15,2);
@EndUserText.label: 'Moneda'@Semantics.currencyCode: trueCurrency : abap.cuky;Campo de cantidad
@EndUserText.label: 'Cantidad'@Semantics.quantity.unitOfMeasure: 'Unit'Quantity : abap.quan(13,3);
@EndUserText.label: 'Unidad'@Semantics.unitOfMeasure: trueUnit : 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 obligatorioIF <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 obligatorioReason : abap.char(2);2. Valores predeterminados sensatos
@UI.defaultValue: 'EUR' " Para moneda@UI.defaultValue: 'true' " Para checkbox@UI.defaultValue: '#(TODAY)' " Para fecha3. Value Helps para campos de selección
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_ReasonVH', element: 'Code' }, useForValidation: true}]4. Mensajes de error claros
" Malotext = 'Error en la entrada'
" Buenotext = |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.
Popup vs. Edición inline
| Escenario | Recomendación |
|---|---|
| 1-2 valores adicionales | Popup-Action |
| Formularios complejos | Object Page separada |
| Cambio masivo | Inline-Edit o app propia |
| Confirmación sin entrada | Action sin parámetro |
Resumen
Las acciones con Popup en RAP permiten entradas de usuario interactivas antes de la ejecución:
| Componente | Función |
|---|---|
| Abstract Entity | Define campos y tipos de datos del popup |
| Behavior Definition | Vincula Action con parameter |
| Implementation | Lee %param y procesa valores |
| Anotaciones UI | Controla 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.