RAP Actions y Functions: Implementar Lógica de Negocio

Kategorie
RAP
Veröffentlicht
Autor
Johannes

Actions y Functions son los medios en RAP para implementar lógica de negocio propia más allá de las operaciones CRUD estándar. Este artículo explica las diferencias y muestra cuándo usar cada concepto.

Action vs. Function: La Diferencia

AspectoActionFunction
PropósitoModifica datos (efectos secundarios)Lee/calcula datos (sin efectos secundarios)
Método HTTPPOSTGET
TransaccionalSí, parte de la transacciónNo, solo lectura
IdempotenteNo
EjemplosCancelar, Aprobar, CopiarCalcular precio, Verificar disponibilidad

¿Cuándo Action, cuándo Function?

  • Action: Cuando la operación cambia el estado de la entidad o del sistema
  • Function: Cuando solo se calcula un valor o se consulta información, sin modificar datos

Bound vs. Unbound (Static)

AspectoBound (Instance)Unbound (Static)
ContextoVinculada a una instanciaSin contexto de instancia
LlamadaEn fila seleccionadaMediante botón en toolbar
KeysKeys de la instancia disponiblesSin keys automáticas
Caso de uso”Cancelar este viaje""Liberar todos los viajes abiertos”

Instance-bound Action

Una Instance-bound Action se ejecuta en una instancia concreta:

Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
persistent table ztravel
lock master
authorization master ( instance )
{
// Instance-bound Action sin parámetro
action cancel result [1] $self;
// Instance-bound Action con resultado
action accept result [1] $self;
// Instance-bound Action que devuelve múltiples instancias
action copyTravel result [0..*] $self;
}

Implementación

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS cancel FOR MODIFY
IMPORTING keys FOR ACTION Travel~cancel RESULT result.
METHODS accept FOR MODIFY
IMPORTING keys FOR ACTION Travel~accept RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD cancel.
" Leer datos actuales
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TravelID Status )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
" Solo los viajes abiertos pueden cancelarse
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>)
WHERE Status = 'O'.
" Establecer estado a Cancelled
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = <travel>-%tky
Status = 'X' " Cancelled
) ).
ENDLOOP.
" Devolver resultado
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels_result).
result = VALUE #( FOR travel IN travels_result (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
METHOD accept.
" Establecer estado a Accepted
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
Status = 'A' " Accepted
) ).
" Devolver datos actualizados
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
result = VALUE #( FOR travel IN travels (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
ENDCLASS.

Cardinalidad del Result

CardinalidadSignificado
result [1] $selfExactamente una instancia de vuelta (la misma entidad)
result [0..1] $selfOpcionalmente una instancia
result [0..*] $selfCualquier cantidad de instancias
result [1] EntityNameUna instancia de otra entidad
(sin result)Sin devolución

Static (Unbound) Action

Las Static Actions no están vinculadas a una instancia y se usan típicamente para operaciones masivas:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Static Action sin parámetro
static action releaseAllOpen;
// Static Action con resultado
static action createFromTemplate result [1] $self;
// Static Action con Global Feature Control
static action ( features: global ) massUpdate;
// Para Global Feature Control
static features;
}

Implementación

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS releaseAllOpen FOR MODIFY
IMPORTING keys FOR ACTION Travel~releaseAllOpen.
METHODS createFromTemplate FOR MODIFY
IMPORTING keys FOR ACTION Travel~createFromTemplate RESULT result.
METHODS get_global_features FOR GLOBAL FEATURES
IMPORTING REQUEST requested_features FOR Travel RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD releaseAllOpen.
" Encontrar todos los viajes abiertos
SELECT * FROM ztravel
WHERE status = 'O'
INTO TABLE @DATA(open_travels).
" Establecer estado a Released
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( FOR travel IN open_travels (
TravelUUID = travel-travel_uuid
Status = 'R' " Released
) ).
ENDMETHOD.
METHOD createFromTemplate.
" Crear nuevo viaje desde plantilla
DATA(template_id) = 'DEFAULT'.
" Cargar plantilla (ejemplo simplificado)
SELECT SINGLE * FROM ztravel_template
WHERE template_id = @template_id
INTO @DATA(template).
" Crear nuevo viaje
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
CREATE FIELDS ( CustomerID BeginDate EndDate Description )
WITH VALUE #( (
%cid = 'NEW_TRAVEL'
CustomerID = template-customer_id
BeginDate = cl_abap_context_info=>get_system_date( )
EndDate = cl_abap_context_info=>get_system_date( ) + 7
Description = 'Creado desde plantilla'
) )
MAPPED DATA(mapped).
" Devolver entidad creada
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH VALUE #( ( %tky = mapped-travel[ 1 ]-%tky ) )
RESULT DATA(created_travel).
result = VALUE #( (
%cid_ref = 'NEW_TRAVEL'
%param = created_travel[ 1 ]
) ).
ENDMETHOD.
METHOD get_global_features.
" Verificar si el usuario tiene autorización para actualización masiva
DATA(has_auth) = check_mass_update_auth( ).
result = VALUE #(
%action-massUpdate = COND #(
WHEN has_auth = abap_true
THEN if_abap_behv=>fc-o-enabled
ELSE if_abap_behv=>fc-o-disabled )
).
ENDMETHOD.
ENDCLASS.

Function: Cálculos sin Efectos Secundarios

Las Functions son ideales para cálculos y consultas que no modifican datos:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Instance-bound Function
function calculatePrice result [1] ZA_Price;
// Function con múltiples valores de retorno
function getAvailability result [1] ZA_Availability;
// Static Function
static function getExchangeRates result [0..*] ZA_ExchangeRate;
// Function con parámetros
function simulateDiscount
parameter ZA_DiscountInput
result [1] ZA_DiscountResult;
}

Abstract Entity para Valores de Retorno

Las Functions a menudo necesitan entidades abstractas para valores de retorno estructurados:

define abstract entity ZA_Price
{
TravelUUID : sysuuid_x16;
NetPrice : abap.dec( 15, 2 );
TaxAmount : abap.dec( 15, 2 );
TotalPrice : abap.dec( 15, 2 );
Currency : abap.cuky;
}
define abstract entity ZA_Availability
{
TravelUUID : sysuuid_x16;
IsAvailable : abap_boolean;
SeatsLeft : abap.int4;
NextAvailable: abap.dats;
}

Implementación

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS calculatePrice FOR READ
IMPORTING keys FOR FUNCTION Travel~calculatePrice RESULT result.
METHODS getAvailability FOR READ
IMPORTING keys FOR FUNCTION Travel~getAvailability RESULT result.
METHODS getExchangeRates FOR READ
IMPORTING keys FOR FUNCTION Travel~getExchangeRates RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD calculatePrice.
" Leer datos del viaje
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TotalPrice Currency )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
" Calcular precio (simplificado)
DATA(net_price) = <travel>-TotalPrice * '0.84'.
DATA(tax_amount) = <travel>-TotalPrice * '0.16'.
APPEND VALUE #(
%tky = <travel>-%tky
%param = VALUE za_price(
TravelUUID = <travel>-TravelUUID
NetPrice = net_price
TaxAmount = tax_amount
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
)
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD getAvailability.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( BeginDate MaxSeats BookedSeats )
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
DATA(seats_left) = <travel>-MaxSeats - <travel>-BookedSeats.
APPEND VALUE #(
%tky = <travel>-%tky
%param = VALUE za_availability(
TravelUUID = <travel>-TravelUUID
IsAvailable = COND #( WHEN seats_left > 0 THEN abap_true ELSE abap_false )
SeatsLeft = seats_left
NextAvailable = COND #( WHEN seats_left = 0
THEN <travel>-BeginDate + 30
ELSE <travel>-BeginDate )
)
) TO result.
ENDLOOP.
ENDMETHOD.
METHOD getExchangeRates.
" Static Function - sin keys disponibles
" Leer tipos de cambio del sistema
SELECT * FROM tcurr
WHERE kurst = 'M'
AND gdatu >= @( cl_abap_context_info=>get_system_date( ) )
INTO TABLE @DATA(rates)
UP TO 100 ROWS.
result = VALUE #( FOR rate IN rates (
%param = VALUE za_exchangerate(
SourceCurrency = rate-fcurr
TargetCurrency = rate-tcurr
ExchangeRate = rate-ukurs
ValidFrom = rate-gdatu
)
) ).
ENDMETHOD.
ENDCLASS.

Action con Parámetros de Entrada

Las Actions pueden recibir parámetros de entrada estructurados:

Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Action con parámetro abstracto
action rejectWithReason
parameter ZA_RejectionReason
result [1] $self;
// Action con múltiples parámetros
action reschedule
parameter ZA_RescheduleInput
result [1] $self;
// Action con Deep Structure
action addParticipants
parameter ZA_ParticipantList
result [1] $self;
}

Abstract Entity para Parámetros de Entrada

define abstract entity ZA_RejectionReason
{
ReasonCode : abap.char( 2 );
ReasonText : abap.string( 255 );
NotifyCustomer : abap_boolean;
}
define abstract entity ZA_RescheduleInput
{
NewBeginDate : abap.dats;
NewEndDate : abap.dats;
UpdatePrice : abap_boolean;
}

Implementación

CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS rejectWithReason FOR MODIFY
IMPORTING keys FOR ACTION Travel~rejectWithReason RESULT result.
METHODS reschedule FOR MODIFY
IMPORTING keys FOR ACTION Travel~reschedule RESULT result.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD rejectWithReason.
" Leer parámetro de keys
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Motivo del rechazo desde parámetro
DATA(reason_code) = <key>-%param-ReasonCode.
DATA(reason_text) = <key>-%param-ReasonText.
DATA(notify) = <key>-%param-NotifyCustomer.
" Guardar estado y justificación
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status RejectionReason )
WITH VALUE #( (
%tky = <key>-%tky
Status = 'X' " Rejected
RejectionReason = reason_text
) ).
" Opcional: Notificar al cliente
IF notify = abap_true.
" Enviar email...
send_rejection_notification(
travel_key = <key>-%tky
reason = reason_text
).
ENDIF.
ENDLOOP.
" Devolver datos actualizados
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
result = VALUE #( FOR travel IN travels (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
METHOD reschedule.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<key>).
" Nuevas fechas desde parámetro
DATA(new_begin) = <key>-%param-NewBeginDate.
DATA(new_end) = <key>-%param-NewEndDate.
DATA(update_price) = <key>-%param-UpdatePrice.
" Actualizar fechas
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( BeginDate EndDate )
WITH VALUE #( (
%tky = <key>-%tky
BeginDate = new_begin
EndDate = new_end
) ).
" Opcional: Recalcular precio
IF update_price = abap_true.
recalculate_price( <key>-%tky ).
ENDIF.
ENDLOOP.
" Devolver resultado
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
result = VALUE #( FOR travel IN travels (
%tky = travel-%tky
%param = travel
) ).
ENDMETHOD.
ENDCLASS.

Actions en la Projection

Las Actions deben exponerse en la Projection:

projection implementation in class zbp_c_travel unique;
strict ( 2 );
define behavior for ZC_Travel alias Travel
{
use action cancel;
use action accept;
use action rejectWithReason;
use action reschedule;
use function calculatePrice;
use function getAvailability;
}

Integración UI con Fiori Elements

Ubicación de Botones

define root view entity ZC_Travel
provider contract transactional_query
as projection on ZI_Travel
{
@UI.lineItem: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'cancel', label: 'Cancelar' },
{ type: #FOR_ACTION, dataAction: 'accept', label: 'Aprobar' }
]
@UI.identification: [
{ position: 10 },
{ type: #FOR_ACTION, dataAction: 'rejectWithReason', label: 'Rechazar' }
]
key TravelUUID,
// Header Actions
@UI.fieldGroup: [{ qualifier: 'Actions',
type: #FOR_ACTION, dataAction: 'reschedule', label: 'Reprogramar' }]
Status
}

Para Actions con parámetros de entrada se genera automáticamente un diálogo:

define abstract entity ZA_RejectionReason
{
@UI.defaultValue: 'OT'
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZI_ReasonCode', element: 'Code' }}]
ReasonCode : abap.char( 2 );
@UI.multiLineText: true
ReasonText : abap.string( 255 );
@UI.defaultValue: 'true'
NotifyCustomer : abap_boolean;
}

Factory Actions

Las Factory Actions crean nuevas instancias basadas en existentes:

define behavior for ZI_Travel alias Travel
{
// Factory Action crea nueva instancia
factory action copyTravel [1];
// Factory Action con parámetro
factory action createFromBooking parameter ZA_BookingRef [1];
}

Implementación

METHOD copyTravel.
" Leer datos originales
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(travels).
LOOP AT travels ASSIGNING FIELD-SYMBOL(<travel>).
" Crear copia con nuevo %cid
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
CREATE FIELDS ( CustomerID BeginDate EndDate Description TotalPrice Currency )
WITH VALUE #( (
%cid = |COPY_{ sy-index }|
CustomerID = <travel>-CustomerID
BeginDate = <travel>-BeginDate
EndDate = <travel>-EndDate
Description = |Copia de { <travel>-TravelID }|
TotalPrice = <travel>-TotalPrice
Currency = <travel>-Currency
) )
MAPPED DATA(mapped).
" mapped contiene %cid y %tky de la nueva instancia
APPEND VALUE #(
%cid_ref = keys[ sy-index ]-%cid_ref
%key = mapped-travel[ 1 ]-%key
) TO mapped-travel.
ENDLOOP.
ENDMETHOD.

Mejores Prácticas

  1. Elegir correctamente Action vs. Function: Actions para cambios, Functions para cálculos.

  2. Definir Result Type: Siempre especifica result [n] $self para que la UI se actualice.

  3. Usar IN LOCAL MODE: Para llamadas internas omitir verificaciones de autorización.

  4. Manejo de Errores: Devolver errores via FAILED y REPORTED:

METHOD cancel.
" Validación
IF travel-Status <> 'O'.
APPEND VALUE #(
%tky = travel-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Solo los viajes abiertos pueden cancelarse'
)
) TO reported-travel.
APPEND VALUE #( %tky = travel-%tky ) TO failed-travel.
RETURN.
ENDIF.
ENDMETHOD.
  1. Combinar con Feature Control: Desactivar Actions cuando no sean aplicables.

  2. Consistencia Transaccional: Todos los cambios se confirman en el SAVE.

Temas Relacionados