RAP: validaciones, determinaciones y acciones sin sorpresas
Las tres piezas de lógica de negocio de un Business Object RAP parecen sencillas en la documentación y dan bastantes sorpresas en el primer proyecto. Estas son las que me encontré construyendo una app de gestión de incidencias, y cómo las resolví.
Qué es cada una
| Pieza | Para qué | Puede modificar datos | Puede rechazar |
|---|---|---|---|
| Determinación | Calcular o rellenar campos automáticamente | Sí | No |
| Validación | Comprobar que los datos son correctos | No | Sí (FAILED) |
| Acción | Operación que lanza el usuario (un botón) | Sí | Sí |
Cuándo se ejecutan
Aquí está la mayoría de sorpresas. Una determinación on modify se ejecuta en cuanto cambian los campos que declara; una on save, justo antes de guardar. Las validaciones siempre son on save.
determination setDefaultValues on modify { create; }
determination setDefaultHistory on save { create; }
validation validateMandatoryFields on save { create; update; }
validation validateDates on save { field CreationDate, ChangedDate; }
validation validateDeleteStatus on save { delete; }
Con draft hay un matiz importante: mientras el usuario edita, los datos viven en la tabla de borradores y "guardar" no ocurre hasta pulsar el botón de activar. Si quieres que las validaciones se ejecuten antes, al salir de un campo o al pulsar "Guardar" en el borrador, hay que listarlas en la acción Prepare:
draft determine action Prepare
{
validation validateMandatoryFields;
validation validateDates;
}
Si una validación no está en Prepare, el usuario no verá el error hasta activar, y en apps con muchas pestañas eso es confuso.
Marcar el campo, no solo mostrar un mensaje
Un mensaje genérico arriba de la pantalla obliga al usuario a buscar qué está mal. Rellenando %element, Fiori pone el campo en rojo y lleva el foco hasta él:
IF incident-Title IS INITIAL.
APPEND VALUE #( %tky = incident-%tky ) TO failed-incident.
APPEND VALUE #( %tky = incident-%tky
%state_area = 'VALIDATE_MANDATORY'
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Title is mandatory' )
%element-Title = if_abap_behv=>mk-on )
TO reported-incident.
ENDIF.
El %state_area sirve para que el mensaje desaparezca solo cuando el error se corrige. La práctica habitual es, al principio de la validación, añadir una entrada a REPORTED con el %state_area y sin mensaje, lo que limpia los mensajes anteriores de esa área antes de volver a comprobar.
El error de numerar con MAX + 1
Casi todos los ejemplos de formación asignan el número legible así:
SELECT SINGLE MAX( incident_id ) FROM zdt_inct_agg INTO @DATA(lv_max_id).
" ... IncidentID = lv_max_id + 1
Funciona con un usuario. Con dos usuarios creando a la vez, ambos leen el mismo máximo y obtienen el mismo número. Además, en una creación masiva todos los registros del mismo lote reciben el mismo valor si no se suma el índice. Lo correcto en productivo es un objeto de rango de números:
cl_numberrange_runtime=>number_get(
EXPORTING nr_range_nr = '01'
object = 'ZINC_NR'
IMPORTING number = DATA(lv_number) ).
Y hacerlo en early numbering o en una determinación on save, para que solo se consuma número cuando el registro se guarda de verdad.
Acciones: deshabilitar mejor que rechazar
Una acción que no tiene sentido en un estado (cambiar el estado de una incidencia cerrada) puede rechazarse con un error, pero es mejor no dejar pulsarla. Con features : instance, el método get_instance_features decide para cada registro si el botón está habilitado:
action ( features : instance ) changeStatus
parameter zdd_change_status_param_agg result [1] $self;
Dos detalles que conviene no olvidar:
result [1] $selfdevuelve la instancia actualizada, y Fiori refresca la pantalla sin que el usuario recargue.side effects { action changeStatus affects $self; }asegura que también se refrescan las secciones dependientes, como la tabla de historial.
Aun así, la acción debe volver a validar en el servidor. El botón deshabilitado es una comodidad para el usuario, no una medida de seguridad: el servicio OData se puede llamar directamente.
IN LOCAL MODE
Dentro del behavior pool, las lecturas y modificaciones con EML se hacen con IN LOCAL MODE. Eso salta las comprobaciones de autorización y de features del propio BO, que es lo que se quiere: el código interno puede cambiar el campo Status aunque para el usuario sea de solo lectura. Sin IN LOCAL MODE, el propio framework rechazaría la modificación.
Prepare, %state_area, %element y side effects.