Agregando botones a una selection screen

A veces es necesario agregar botones a la pantalla de selección de un reporte.

Esto lo podemos realizar de la siguiente forma:

Primero debemos definir que vamos a usar la estructura: SSCRFIELDS

TABLES: SSCRFIELDS

Luego en el evento INITIALIZATION debemos asignarle por ejemplo el texto de los botones.

  MOVE ‘Este es el boton 1’ TO sscrfields-functxt_01.
  MOVE ‘Cambiar 1’ TO sscrfields-functxt_02.

En el evento AT SELECTION-SCREEN podemos manejar la lógica de estos botones.

AT SELECTION-SCREEN.
  IF sy-ucomm = ‘FC01’.
    d_butt1 = ‘YES’.
    sscrfields-ucomm = ‘ONLI’.
  ELSEIF sy-ucomm = ‘FC02’.
    IF sscrfields-functxt_02 = ‘Cambiar 1’.
      sscrfields-functxt_02 = ‘Cambiar 2’.
    ELSE.
      sscrfields-functxt_02 = ‘Cambiar 1’.
    ENDIF.
  ENDIF.

Cada botón que agreguemos va a tener el código (sy-ucomm) FC01 a FC05.

Acá podemos ver un código a modo de ejemplo:

REPORT ZSCREEN3.
TABLES: sscrfields.

DATA: d_butt1(4).

PARAMETERS: p_grpa1(10) MODIF ID a,
            p_grpa2(10) MODIF ID a,
            p_grpb1(10) MODIF ID b.

SELECTION-SCREEN FUNCTION KEY 1.
SELECTION-SCREEN FUNCTION KEY 2.

INITIALIZATION.

  MOVE 'Este es el boton 1' TO sscrfields-functxt_01.
  MOVE 'Cambiar 1' TO sscrfields-functxt_02.
  d_butt1 = 'NO'.

AT SELECTION-SCREEN.
  IF sy-ucomm = 'FC01'.
    d_butt1 = 'YES'.
    sscrfields-ucomm = 'ONLI'.
  ELSEIF sy-ucomm = 'FC02'.
    IF sscrfields-functxt_02 = 'Cambiar 1'.
      sscrfields-functxt_02 = 'Cambiar 2'.
    ELSE.
      sscrfields-functxt_02 = 'Cambiar 1'.
    ENDIF.
  ENDIF.

START-OF-SELECTION.

WRITE: / 'd_butt1: ', d_butt1.

Error Form Painter (SAP SCRIPT)

En las ultimas instalaciones del SAP GUI un componente para ver la representación grafica del formulario (SAP SCRIPT) fue removida por SAP, lo que causa un error al hacer click en el boton Layout en la transacción SE71.

El error es:

Graphical Form Painter could not be called (FORMPAINTER_CREATE_WINDOW, )

Para solucionar este error hay que bajar un archivo desde la pagina de soporte de SAP ( https://launchpad.support.sap.com/#/softwarecenter ), desde ahí buscamos SAPSLTESP00_0-70001053.EXE y nos mostrara algo como

Una vez bajado simplemente lo instalamos (Hay que cerrar completamente el SAP GUI) y ya podremos ver la representación grafica del formulario (SAP SCRIPT).

Form painter

IDOCs

Es un Formato SAP estándar para el intercambio electrónico de datos entre sistemas.

La sigla IDOC es “documento intermedio“ o “Intermediate Document”. 

Los IDOCs permiten intercambiar información entre distintos sistemas. Se lo puede ver como un archivo de texto plano con registros.

Un IDOC esta compuesto por un “IDOC Basic Type” o “Tipo Básico” y “Segments” o “Segmentos”.

Composición de un IDOC:

Un segmento define el formato y estructura de un registro de datos. Los segmentos son reusables con lo cual se pueden usar en mas de un tipo básico de IDOC.

Un segmento consiste de varios campos que representan el dato a enviar.

En el ejemplo anterior si hacemos doble click sobre el segmento veremos lo siguiente:

El nombre, un campo indicando si es mandatorio o no, y un Máximo y Mínimo de cantidad de veces que se puede repetir el registro en el segmento.

Si hacemos click en “Segment Editor” podemos ver en mas detalle la composición.

En el editor de segmentos vemos como esta compuesto.

Vemos que tiene los campos con sus correspondientes tipo.

Un IDOC tiene los siguientes atributos técnicos:

Dirección: Puede ser de Salida (Outbound) o de Entrada (Inbound)

Status: El status actual del IDOC, OK, Error, etc.

Tipo Básico: El tipo básico con el cual se definió el IDOC.

Extensión (Opcional): Un IDOC ya existente se puede extender con campos de otro.

Partner No: Un partner representa a un sistema, ya sea el que envía o recibe.

Partner Type: Representa el tipo de sistema, por ejemplo, Sistema Lógico.

Port: Un puerto es un nombre lógico que se usa en combinación con el Partner para definir a donde enviar o recibir un IDOC.

Por lo general un message type describe un documento de negocio, por ejemplo:

  • INVOIC –> Factura
  • ORDERS –> Sales / Purchase Orders
  • WHSORD –> Stock Order

Los message types se crean de la transacción WE81

Con un Message Type y Basic Type definidos debemos asociarlos.

Esto se hace de la transacción WE82

Definir sistemas lógicos:

Un sistema lógica representa un ambiente al cual nos queremos conectar.

Se definen desde la transacción BD54.

En la imagen de abajo tenemos definidos dos sistemas lógicos, para este caso como solo tenemos un solo sistema, vamos a asumir que cada mandante representados por los sistemas lógicos A4HCLNT001 y A4HCLNT002 son sistemas diferentes.

Creación de un sistema Lógico:

1)Hacemos click en editar

2)Hacemos click en New Entries

3) Ingresamos los datos y hacemos click en guardar (Nos pedirá una orden de transporte)

Definimos los puertos:

Se definen desde la transacción WE21.
En la imagen de abajo tenemos definidos dos puertos, como vimos antes simulando sistemas separados.

Creación de un nuevo puerto:

1)Nos paramos sobre el tipo de puerto a crear, por ejemplo “Transactional RFC” y hacemos click en Nuevo.

2)En la siguiente ventana tenemos la opción de elegir un nombre para el puerto o que el mismo sea autogenerado.

3)Completamos la descripción, le asignamos una RFC y lo grabamos.

Las RFC o Remote Function Call, básicamente son modulos de funciones común y corrientes con la particularidad de
que estos se pueden llamar desde otro SAP.

Las RFC se crean y modifican desde la transacción SM59, por lo general la creación o modificación de una RFC es tarea del equipo de BASIS, pero se pueden dar casos donde también lo hagan los ABAPers.

Para configurar una RFC mínimamente debemos llenar los siguientes campos:

Usamos el Process Code para determinar a que modulo de función llamar en la WE20.

Los Process Code se pueden definir de dos transacciones diferentes dependiendo de si son de Inbound u Outbound

WE41 –> Outbound Process Code

WE42 –> Inbound Process Code

Partners:

Los partners se crean desde la transacción WE20.

Un partner puede tener asociado (Entre otras cosas) un sistema lógico, y a su vez, este sistema lógico tiene asociado:

Para Outbound: -Message Type: Un identificador. -Message Code/Variant: Son también identificadores que sirven para segregar mas la funcionalidad. -Message Function: Idem Anterior. -Port: El puerto del sistema remoto al que nos estaremos conectando. –

Para Inbound: -Message Type: Idem anterior. -Message Code/Variant: Idem anterior. -Message Function: Idem anterior -Process Code: Idem anterior. –

Ejemplo:

Podemos tener:

Message Type: INVOIC

Message Code: CRE

Message Function: FACT

Process Code: PR1


Message Type: INVOIC

Message Code: MOD

Message Function: FACT

Process Code: PR2

Partners:

Para ver o buscar IDOCs podemos usar las transacciones WE02 / WE05, desde la pantalla de selección podemos filtrar, por numero de IDOC, estado, fecha / hora, message type, etc.

Para copiar y procesar un IDOC ya existente podemos usar la transacción WE19, entre algunos de los parámetros que podemos usar esta el numero de IDOC, basic type, etc.

Dependiendo de si queremos procesar un Outbound o Inbound hacemos click en el botón correspondiente y confirmamos el popup que nos aparece.

Para reprocesar un IDOC se usa la transacción BD87, ingresando con un numero de IDOC.

Ejemplo de codigo de programa para enviar IDOCs:

REPORT zcrear_banco.
PARAMETERS: p_pais TYPE bnka-banks,
            p_key  TYPE bnka-bankl.
DATA: lwa_idoc_ctrl     TYPE edidc,
      lt_idoc_comm      TYPE TABLE OF edidc,
      lt_idoc_Data      TYPE TABLE OF edidd,
      lwa_idoc_data     TYPE edidd,
      lwa_E1BANK_CREATE TYPE e1bank_create,
      lwa_dir           TYPE e1bp1011_address.

lwa_idoc_ctrl-direct = 1. "Outbound.
lwa_idoc_ctrl-outmod = 1. "Transferir el IDOC inmediatamente.
lwa_idoc_ctrl-rcvpor = 'A000000002'. "Puerto de recepcion.
lwa_idoc_ctrl-rcvprt = 'LS'. "Partner type de recepcion.
lwa_idoc_ctrl-rcvprn = 'A4HCLNT002'. "Partner de recepcion.
lwa_idoc_ctrl-stdmes = 'BANK_CREATE'. "Message Type.
lwa_idoc_ctrl-mescod = 'BCR'. "Message Variant.
lwa_idoc_ctrl-mesfct = 'CRT'. "Message Function.
lwa_idoc_ctrl-sndprt = 'LS'. "Partner Type de envio.
lwa_idoc_ctrl-SNDPRn = 'A4HCLNT001'. "Partner de envio.
lwa_idoc_ctrl-mestyp = 'BANK_CREATE'. "Message Type.
lwa_idoc_ctrl-idoctp = 'BANK_CREATE01'. "Basic Type.

lwa_idoc_data-segnam = 'E1BANK_CREATE'. "Nombre del segmento.

lwa_e1bank_create-bank_ctry = p_pais.
lwa_e1bank_create-bank_key = p_key.

lwa_idoc_data-sdata = lwa_e1bank_create.
APPEND lwa_idoc_data TO lt_idoc_data.
CLEAR lwa_idoc_Data.

CALL FUNCTION 'MASTER_IDOC_DISTRIBUTE'
  EXPORTING
    master_idoc_control            = lwa_idoc_ctrl
  TABLES
    communication_idoc_control     = lt_idoc_comm
    master_idoc_data               = lt_idoc_data
  EXCEPTIONS
    error_in_idoc_control          = 1
    error_writing_idoc_status      = 2
    error_in_idoc_data             = 3
    sending_logical_system_unknown = 4
    OTHERS                         = 5.
IF sy-subrc EQ 0.
  COMMIT WORK.
ENDIF.

END-OF-SELECTION.

Message Types:

Message TypeIDOC TypeDescription
BENREPBENEFIT1Benefits Participation
Benefits Retirement Plan
CREADVPEXR2001, PEXR2002Credit Memo Display
DEBADVPEXR2001, PEXR2002Debit Advice
DELFORDELFOR01Delivery Schedule
DELINSDELFOR01Forecast Delivery Schedule
DELJITDELFOR01Just-In-Time Delivery Schedule
DELORDORDERS03, ORDERS04, ORDERS05Delivery Order (Pickup sheet)
DESADVDELVRY01, DELVRY02, DELVRY03 (previously: DESADV01)Delivery Shipping Notification
EXPINVEXPINV01, EXPINV02, EXPINV03Foreign Trade Billing Document
FINSTAFINSTA01Bank Statement
GSVERFGSVERF01Self-Billing procedure
IMPINVIMPINV01Import Basis IDOC
INVOICINVOIC01Invoice
LOCKBXFINSTA01Lockbox
ORDCHGORDERS01, ORDERS02, ORDERS03,
ORDERS04, ORDERS05
Sales Order Change
Purchase Order Change
ORDERSORDERS01, ORDERS02, ORDERS03,
ORDERS04, ORDERS05
Sales Order
Purchase Order
ORDRSPORDERS01, ORDERS02, ORDERS03,
ORDERS04, ORDERS05
Sales Order Confirmation
Purchase Order Confirmation
PAYEXTPEXR2001, PEXR2002Extended Payment Order
PRICATPRICAT01Price List/Catalog
PROACTPROACT01Stock and Sales Data
QUOTESORDERS01, ORDERS02, ORDERS03,
ORDERS04, ORDERS05
Quotation
REMADVPEXR2001, PEXR2002Payment Advice
REQOTEORDERS01, ORDERS02, ORDERS03,
ORDERS04, ORDERS05
Inquiry
SHPADVSHPMNT03Shipping Notification
SHPCONDELVRY01Shipping Confirmation
SHPMNTSHPMNT01, SHPMNT02, SHPMNT03, SHPMNT04Shipment Notification
SHPORDDELVRY01Delivery: Dispatch Order
TXTRAWTXTRAW01, TXTRAW02Message for free text in SAP office format
WHSORDDELVRY01Delivery: Stock Order

Programas:

• RBDMIDOC –> Generar IDOCs a partir de punteros modif.

• RSEOUT00 –> Proceso de todos los IDOCs seleccionados.

• RBDAPP01 –> Proceso de IDOCs entrantes dispuestos a ser transferidos.

• RSARFCEX –> Ejecutar llamadas aún no ejecutadas.

• RBDMOIND –> Conversión status si ejecución correcta del TRFC.

• RBDMANIN –> Lanzar tratamiento de errores para IDOCs no contabilizados.


Transacciones:

WE05: IDOC List Overview 

WE19: Herramientas de test para el proceso de IDOC (Copia IDOCs) 

WE20: Partners, destinos lógicos. 

WE21: Puertos 

WE30: Creación de Tipo Base/Extensión 

WE31: Creación de segmentos 

WE41: Outbound process code 

WE42: Inbound process code 

WE57: Asociar Basic Type, Message Type y Function Module 

WE81: Creación de mensajes Lógicos 

WE82: Asignación de mensaje Lógico, Tipo Base y Extensión 

BD51: Inbound Function Modules 

BD54: Definir sistemas logicos. 

BD64: Distribution Model.

BD87: Inbound IDoc reprocessing. 

BD88: Outbound IDoc reprocessing. 

BDM2: IDoc Trace.

SM59: Creación / Modificación de RFCs.

Extension de la transacción XD/01/02/03

Vamos a la SPRO a la siguiente ruta:

SPRO

Agregamos una nueva entrada.

Hacemos click en Rotulación de etiquetas y agregamos una nueva entrada:

Volvemos a la SPRO y vamos a:

Creamos una nueva implementación:

Implementamos el método CHECK_ADD_ON_ACTIVE:

Volvemos a la SPRO:

Creamos una nueva implementación:

Creamos el filtro:

Creamos un grupo de función y le creamos una DynPro (La que especificamos anteriormente)

En la dynpro Agregamos el campo  que queremos mostrar:

Implementamos el método GET_TAXI_SCREEN de la BADI:

En este punto ya podríamos ir a la transacción XD01 / 02 / 03 y ver nuestro campo.

Ahora hay que agregar una APPEND STRUCTURE a la tabla KNA1.

Ahora hay que hacer que en modo visualización el campo aparezca no editable, para esto vamos al grupo de función que creamos y agregamos el siguiente código:

Ahora debemos agregar la lógica para cargar y guardar el dato de nuestro campo Z.

Cargar:

Guardar:

Implementamos el método de la BADI (La de la subscreen).

Creación de IDOCs custom

Definir el sistema lógico desde la transacción SALE.

Nos aparecer un mensaje como el siguiente, el mismo es una advertencia, diciéndonos que la tabla no es dependiente del mandante.

En el caso de no tener uno definido lo definimos.

Creamos los segmentos de nuestro IDOC custom. Para esto vamos a la transacción WE31.

Definimos los campos del segmento, la descripción y le damos guardar.

Nos pedirá un responsable (Nuestro usuario)

Creamos un tipo básico de IDOC desde la transacción WE30.

Al darle OK nos llevara a la siguiente ventana donde hacemos click en el icono marcado para agregar al IDOC los segmentos que creamos anteriormente.

Nos aparecerá la siguiente ventana:

Completamos el tipo (El nombre del segmento que creamos anteriormente).

Le indicamos que es mandatorio.

El máximum y mínimum number indican cuantas “filas” un segmento del IDOC puede tener, lo podemos imaginar como si fuera una tabla interna.

Si estamos creando un IDOC de ítems de una factura, y le ponemos que como máximo puede tener 10, entonces no podremos tener mas de 1 items en el IDOC.

Hacemos click en guardar:

Creamos un MessageType desde la transacción WE81.

Un Message Type es una forma de determinar para que se usa un IDOC, por ejemplo los IDOCs de ordenes de compras pueden estar en un Message Type ORDER.

Hacemos click en New Entries…

Ingresamos el nombre, una descripción y hacemos click en guardar.

Asignamos el Message Type a un IDOC usando la transacción WE82.

Usando los datos de todo lo que creamos anteriormente completamos la siguiente ventana:

Creamos un grupo de función usando la transacción SE80.

Hacemos click en Edit object

Hacemos click en Enhanced Options.

Seleccionamos del icono de la carpeta Function Group.

Ingresamos un nombre y hacemos click en New.

Ingresamos una descripción y hacemos click en Save.

Creamos un modulo de función para procesar nuestro IDOC desde la transacción SE37.

Los modulo de función para el procesamiento de IDOCs tienen que tener ciertos parámetros definidos para que funcionen correctamente.

Los parámetros que DEBEN tener son los siguientes:

INPUT_METHOD              LIKE        BDWFAP_PAR-INPUTMETHD

MASS_PROCESSING       LIKE        BDWFAP_PAR-MASS_PROC

WORKFLOW_RESULT     LIKE        BDWF_PARAM-RESULT

APPLICATION_VARIABLE              LIKE        BDWF_PARAM-APPL_VAR

IN_UPDATE_TASK           LIKE        BDWFAP_PAR-UPDATETASK

CALL_TRANSACTION_DONE       LIKE        BDWFAP_PAR-CALLTRANS

IDOC_CONTRL   LIKE        EDIDC

IDOC_DATA        LIKE        EDIDD

IDOC_STATUS   LIKE        BDIDOCSTAT

RETURN_VARIABLES      LIKE        BDWFRETVAR

SERIALIZATION_INFO    LIKE        BDI_SER

WRONG_FUNCTION_CALLED

Podemos crear la función a mano o copiarnos una función standard como la BAPI_IDOC_INPUT1.

Ingresamos el nombre del modulo de función nuevo y el grupo de funciones que creamos anteriormente y hacemos click en Copy.

Ingresamos a nuestra función recién creada, y le borramos el código, dado que haremos nuestra propia lógica y la activamos.

El siguiente paso es asignar el Message Type y IDOC type al modulo de funciones. Para esto ingresamos a la transacción WE57. Esto es para saber el IDOC a que modulo de función debe llamar.

Hacemos click en New Entries.

Completamos los datos marcados con los diferentes objetos que creamos anteriormente.

Configuramos las características del modulo de función que creamos anteriormente usando la transacción BD51.

Hacemos click en New Entries

Completamos los datos con el modulo de función, el input type y si esta permitido el modo dialogo.

Creamos un Process Code en la transacción WE42.

Hacemos click en New Entries

Ingresamos un nombre, descripción y seleccionamos Function Module.

Hacemos click en guardar.

Seleccionamos nuestra función:

Hacemos click en guardar.

Hacemos doble click en Logical Message:

Para nuestro process code hacemos click en New entries.

Ingresamos nuestro Message Type que creamos anteriormente y hacemos click en guardar.

En la transacción WE20 creamos el Partner Profile (Si no existe ya)

En este caso como ya existe, hacemos click en el botón Editar.

Hacemos click en Create Inbound parameter

Ingresamos el Process Code y Message Type creados anteriormente y hacemos click en guardar.

Ahora necesitamos agregar código a nuestra función que creamos en uno de los pasos anteriores.

La idea es que nuestra función inserte un registro en una tabla al recibir un IDOC.

Primero creamos la tabla:

Vamos a nuestra función y completamos el código:

  DATA: lt_idoc_data TYPE TABLE OF EDIDD,
        lwa_auto TYPE ZAUTOS_IDOC,
        lwa_zauto TYPE ZAUTO.

"Pasamos los datos del parametro de la funcion a una tabla interna.
  lt_idoc_data = idoc_Data[].

"Leemos los datos del segmento ZAUTO y lo asignamos a una WA de la misma estructura
  lwa_zauto = lt_idoc_data[ segnam = 'ZAUTO' ]-sdata.

  lwa_auto-marca = lwa_zauto-marca.
  lwa_auto-modelo = lwa_zauto-modelo.
  lwa_auto-mandt = sy-mandt.

"Actualizamos la tabla de BD.
  MODIFY ZAUTOS_IDOC FROM lwa_auto.

Para probar nuestro IDOC podemos ir a la transacción WE19:

Hacemos click en la línea vacía y completamos los datos:

Volvemos a la pantalla anterior y hacemos click en Inbound Function Module

Si no nos carga automáticamente el modulo de función lo ingresamos.
También tenemos la opción de ejecutarlo en modo debug.

Luego de ejecutarlo nos aparecerá un mensaje como el siguiente dándonos el numero de IDOC.

Si vamos a nuestra tabla Z, ahora debería contener la información que ingresamos en el IDOC.

Para ver el IDOC que creamos podemos ir a la transacción WE05 e ingresamos el numero de IDOC.

Desde esta pantalla podemos ver el segmento, los datos y el status del IDOC.

Usando Table Functions y Vistas CDS

Al igual que cuando creamos la vista CDS, para crear una table function debemos crear un data definition como hicimos antes, pero al momento de llegar al template seleccionamos “Define Table Function with parameters”

Al hacer click en finish nos aparecerá el template seleccionado el cual debemos completar para que quede similar a lo siguiente:

@EndUserText.label: 'Table function para vuelos'
define table function ZTF_VUELOS
with parameters 
@Environment.systemField: #CLIENT
client : mandt,
returns {
  client : abap.clnt;
  carrid : s_carr_id;
  connid : s_conn_id;
  fldate : s_date;
  carrname : s_carrname;
  URL : s_carrurl;
  price : s_price;
  currency : s_currcode;
  passname : s_passname;
}
implemented by method zcl_vuelos2=>obtener_datos_pasajeros;

Vemos que al final le estamos especificando la clase (ZCL_VUELOS2) y el método (obtener_datos_pasajeros) que vamos a llamar.

El próximo paso será crear esta clase y método, para esto hacemos click derecho sobre nuestro package o objeto local y seleccionamos crear una clase.

Ingresamos un nombre (El que usamos en la Table function) y descripción.

De nuevo seleccionamos una orden de transporte y le damos click a Finish o si es un objeto local directamente hacemos click en finish.

Nos creara el template que se vera similar a:

Adaptamos la clase para que tenga la lógica que nosotros queramos, por ejemplo:

CLASS zcl_vuelos2 DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES : if_amdp_marker_hdb.
    CLASS-METHODS :  obtener_datos_pasajeros FOR TABLE FUNCTION ZTF_VUELOS.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_vuelos2 IMPLEMENTATION.

        METHOD obtener_datos_pasajeros BY DATABASE FUNCTION FOR HDB
                        LANGUAGE SQLSCRIPT
                        OPTIONS READ-ONLY
                        USING scarr sflight sbook.

               RETURN

                    SELECT a.mandt AS client, a.carrid AS carrid, a.connid AS connid, a.fldate AS fldate,
                           b.carrname AS carrname, b.URL as URL, a.price AS price, a.currency AS currency,
                           c.passname AS passname FROM sflight AS a
                           INNER JOIN scarr AS b
                            ON a.carrid = b.carrid
                           INNER JOIN sbook AS c
                            ON a.carrid = c.carrid AND
                               a.connid = c.connid AND
                               a.fldate = c.fldate
                           WHERE a.mandt = c.mandt;
        ENDMETHOD.
ENDCLASS.

Ahora debemos creamos una vista CDS que utilice nuestra table function. Para esto, como hicimos antes nos creamos una vista CDS.

Nuestra vista debería ser algo similar a esto:

Lo importante a ver acá, es que estamos haciendo el SELECT desde la Table Function (SELECT FROM ZTF_VUELOS ).

@AbapCatalog.sqlViewName: 'ZCDS_TF_DATOS_V'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Datos de pasajeros usando TF'
define view ZCDS_TF_DATOS as select from ZTF_VUELOS ( client : $session.client ) {
    carrid,
    connid,
    fldate,
    carrname,
    URL,
    @Semantics.amount.currencyCode: 'CurrencyCode' 
    price,
    @Semantics.currencyCode
      cast ('USD' as abap.cuky) as CurrencyCode,
    currency,
    passname
}

Una ves con nuestra nueva vista creada, la podemos consultar como hicimos antes desde la SE16. Pero también lo que podemos hacer es ponerle un breakpoint en la clase que creamos.

Para hacer esto, en eclipse, vamos a la clase que creamos y hacemos doble click donde esta marcado en la captura de abajo. Nos debería aparecer un puntito en color verde si el breakpoint esta activo.

Una ves con el breakpoint activo, podemos ejecutar la vista desde la SE16 y se nos debería activar el debugger en eclipse.

Para ver mejor por debug podemos cambiar el código por:

CLASS zcl_vuelos2 DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES : if_amdp_marker_hdb.
    CLASS-METHODS :  obtener_datos_pasajeros FOR TABLE FUNCTION ZTF_VUELOS.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_vuelos2 IMPLEMENTATION.

        METHOD obtener_datos_pasajeros BY DATABASE FUNCTION FOR HDB
                        LANGUAGE SQLSCRIPT
                        OPTIONS READ-ONLY
                        USING scarr sflight sbook.

/*               RETURN */

                    lt_datos = SELECT a.mandt AS client, a.carrid AS carrid, a.connid AS connid, a.fldate AS fldate,
                           b.carrname AS carrname, b.URL as URL, a.price AS price, a.currency AS currency,
                           c.passname AS passname FROM sflight AS a
                           INNER JOIN scarr AS b
                            ON a.carrid = b.carrid
                           INNER JOIN sbook AS c
                            ON a.carrid = c.carrid AND
                               a.connid = c.connid AND
                               a.fldate = c.fldate
                           WHERE a.mandt = c.mandt;

                    RETURN :lt_datos;

        ENDMETHOD.
ENDCLASS.

En la imagen de abajo podemos ver el breakpoint en la línea 22, a la derecha del código podemos ver nuestra variable lt_datos donde se guarda el resultado de la consulta y en la parte de inferior de la pantalla podemos ver el contenido de la tabla interna.

Tambien al igual que con una vista CDS común, la podemos usar desde un reporte ABAP:

SELECT * FROM ZCDS_TF_DATOS( pcarrid = 'AA', pconnid = '0017', pfldate = @lv_fecha )
  INTO TABLE @DATA(lt_datos2).
IF sy-subrc = 0.

ENDIF.

Tambien podemos usar un ALV IDA para mostrar los datos:

REPORT zida_cds.
data: lo_exc type ref to cx_salv_function_not_supported.

try.
  cl_salv_gui_table_ida=>create_for_cds_view( 'ZCDS_VUELOS' )->fullscreen( )->display( ).
  catch cx_salv_function_not_supported into lo_exc.
    message lo_exc type 'I'.
endtry.

Creando una vista CDS

En este post rápido, les voy a mostrar como crear una vista CDS fácilmente.

Ingresamos a Eclipse, vamos al package que usemos o creamos un Data Definition como objeto local.

En la ventana que se abre, buscamos Core Data Services y seleccionamos Data Definition.

Ingresamos un nombre / Descripción y hacemos click en next.

En la siguiente pantalla, se nos dará la opción de elegir una orden de transporte, en el caso que estemos trabajando en un package, si estamos en un objeto local nos aparecerá la pantalla griseada.

Seleccionamos la orden de transporte y hacemos click en next.

Si estamos en un objeto local hacemos click en next.

En la siguiente pantalla se nos dará a seleccionar un template para la vista CDS, seleccionamos Define View y hacemos click en Finish.

Al hacer click en Finish volveremos a Eclipse y veremos algo como lo siguiente:

En esta pantalla, debemos ingresar un nombre para nuestra vista ABAP (sqlViewName) y reemplazar el data_source_name por las tablas de donde sacamos los datos. Por ejemplo:

@AbapCatalog.sqlViewName: 'ZCDS_VUELOS_V'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'CDS Para aerolineas y vuelos'
define view ZCDS_VUELOS as select from scarr 
                        join sflight 
                            on scarr.carrid = sflight.carrid
                        join sbook
                            on sflight.carrid = sbook.carrid and
                               sflight.connid = sbook.connid and
                               sflight.fldate = sbook.fldate
{
    key scarr.carrid,
    key sflight.connid,
    key sflight.fldate,
    key sbook.bookid,
    scarr.carrname,
    scarr.url,
    sflight.price,
    sflight.currency,
    sflight.planetype,
    sflight.seatsmax,
    sflight.seatsocc,
    sbook.customid,
    sbook.custtype,
    sbook.class,
    sbook.loccuram,
    sbook.loccurkey,
    sbook.order_date,
    sbook.passname
}

En esta vista CDS podemos ver que estamos haciendo un JOIN de 3 tablas.

Una ves que tenemos nuestra vista CDS lista, activamos (CTRL+F3).

Con nuestra vista CDS ya activa podemos ir a la SE16 con el nombre, en este caso ZCDS_VUELOS_V y probarla.

Vemos que nos trae datos:

También la podemos probar desde eclipse, haciendo click derecho sobre nuestra vista CDS y yendo a:

Tambien podemos hacer uso de la vista CDS desde un reporte de ABAP común y corriente.

Como podemos ver en la captura, estamos parados después de que se realizo el SELECT y la tabla interna LT_DATOS contiene datos.

Clases ABAP (Globales)

En SAP hay dos tipos de clases, las que podemos desarrollar en un reporte Z, como vimos en el post anterior, o las clases globales que son las que definimos en la transacción SE24.

La principal diferencia entre una clase global y una local es que las locales son especificas a un reporte, mientras las clases globales pueden ser usadas desde cualquier lado.

Para definir una clase, vamos a la transacción SE24, para seguir con el ejemplo anterior vamos a volver a crear la clase vehículo.

Hacemos click en “Create”

En la siguiente ventana ingresamos una descripción y hacemos click en “SAVE”

Creamos el objeto como un objeto local.

Nos aparecerá la siguiente ventana donde debemos especificar el método a crear, visibilidad, tipo, etc.

El siguiente paso es definir los parámetros de los métodos, para eso seleccionamos un método y hacemos click en el botón “Parameters”.

Vamos a la solapa “Attributes” y definimos nuestra variable protegida.

Con nuestra clase con los métodos y variable definidas le damos click a activar.

Nos aparecerá una ventana como la siguiente en donde seleccionamos todos los objetos y le damos OK.

Nuestra clase debería haber quedado activa.

Finalmente hay que hacer el código que en cada método para que la clase haga algo.

Metodo obtener tipo:

WRITE 'AUTO'.

Método obtener marca:

WRITE 'FORD'.

Método set_notas:

lv_notas = notas.

Método ver_notas:

notas = lv_notas.

En este punto ya tenemos nuestra clase lista, solo nos resta volver a activarla para asegurarnos que esta todo OK.

Ahora nuestra clase estará disponible para ser usada desde cualquier reporte que se quiera crear.

Abajo dejo un ejemplo de uso.

*&---------------------------------------------------------------------*
*& Report Z_CLASE_GLOBAL
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT Z_CLASE_GLOBAL.

DATA: lc_cg TYPE REF TO ZCL_VEHICULO.

CREATE OBJECT lc_cg.

lc_cg->set_notas( 'Una nota' ).
lc_cg->obtener_tipo( ).
NEW-LINE.
lc_cg->obtener_marca( ).
NEW-LINE.

DATA: lv_notas TYPE string.
lc_cg->ver_notas( IMPORTING notas = lv_notas ).
WRITE lv_notas.

Encapsulamiento, Herencia y Polimorfismo en ABAP

Estaba refrescando un poco mi memoria de programación orientada a objetos (OOP) en ABAP, y decidí hacer el siguiente ejemplo, usando los conceptos de Encapsulamiento, Herencia y Polimorfismo.

Haciendo un ejemplo de polimorfismo podemos de paso abarcar también Herencia y Encapsulamiento, por ejemplo, digamos que queremos tener una clase con los atributos de vehículos, por ejemplo autos y motos. Para hacer esto podríamos definir una clase “Vehículos” que abarque a varios tipos de valga la redundancia vehículos.

Primero definimos una clase abstracta llamada vehículo, una clase abstracta tiene la particularidad que solo necesitamos definir, la DEFINITION y no tiene IMPLEMENTATION. Esto lo usaremos de mano de la definición de métodos para lograr polimorfismo.

CLASS lcl_vehiculo DEFINITION ABSTRACT.

  PUBLIC SECTION.
    METHODS: obtener_tipo ABSTRACT,
             obtener_marca ABSTRACT,
             set_notas ABSTRACT
              IMPORTING notas TYPE string,
             ver_notas ABSTRACT
              EXPORTING notas TYPE string.

  PROTECTED SECTION.
    DATA: lv_notas TYPE string.

ENDCLASS.

En el código de arriba podemos ver que definimos la clase junto con sus métodos (Públicos) y en la sección Protegida la variable notas (Aca tenemos un ejemplo de encapsulamiento, donde la variable al estar dentro de la sección PROTECTED solo puede ser accedida desde si misma y clases heredadas. Podríamos pensar de una clase abstracta como un template o una “receta” para luego definir otras clases.

El paso siguiente seria por ejemplo definir una clase para los objetos de tipo auto usando la que ya tenemos.

CLASS lcl_auto DEFINITION
               INHERITING FROM lcl_vehiculo.

  PUBLIC SECTION.
    METHODS: obtener_tipo REDEFINITION,
             obtener_marca REDEFINITION,
             set_notas REDEFINITION,
             ver_notas REDEFINITION,
             cantidad_puertas.

ENDCLASS.

En el código de arriba definimos la clase lcl_auto y podemos ver la sentencia INHERITING FROM esto indica que estamos HEREDANDO la clase lcl_vehiculo, con todos sus métodos y variables.

Tambien podemos ver que esta clase (lcl_auto) tiene un método mas (cantidad_puertas), cada clase sea heredada o no puede tener sus propios métodos.

En los métodos podemos ver que tienen la palabra clave REDEFINITION, esto quiere decir que el método lo vamos a redefinir para que haga lo que nosotros deseemos. Ahora creamos la implementacion de la clase.

CLASS lcl_auto IMPLEMENTATION.

  METHOD obtener_tipo.
    WRITE 'AUTO'.
  ENDMETHOD.

  METHOD obtener_marca.
    WRITE 'FORD'.
  ENDMETHOD.

  METHOD set_notas.
    lv_notas = notas.
  ENDMETHOD.

  METHOD ver_notas.
    WRITE lv_notas.
  ENDMETHOD.

  METHOD cantidad_puertas.
    WRITE '4'.
  ENDMETHOD.

ENDCLASS.

En la implementacion de cada método podemos poner lo que nosotros queremos que el mismo haga. Podemos ver particularmente en el método set_notas que estamos seteando el valor de la variable lv_notas, esto es posible porque la misma esta declarada como PROTECTED, con lo cual una clase heredada puede modificar su valor. Si en lugar de PROTECTED fuese PRIVATE esto no seria posible.

Ahora hacemos lo mismo que antes pero para un objeto moto.

Primero declaramos la definición y luego la implementacion.

CLASS lcl_moto DEFINITION
               INHERITING FROM lcl_vehiculo.

  PUBLIC SECTION.
    METHODS: obtener_tipo REDEFINITION,
             obtener_marca REDEFINITION,
             set_notas REDEFINITION,
             ver_notas REDEFINITION.

ENDCLASS.
CLASS lcl_moto IMPLEMENTATION.

  METHOD obtener_tipo.
    WRITE 'MOTO'.
  ENDMETHOD.

  METHOD obtener_marca.
    WRITE 'HONDA'.
  ENDMETHOD.

  METHOD set_notas.
    lv_notas = notas.
  ENDMETHOD.

  METHOD ver_notas.
    WRITE lv_notas.
  ENDMETHOD.

ENDCLASS.

Como podemos ver en el ejemplo de arriba esta clase no tiene el método cantidad_puertas.

Finalmente solo resta hacer la declaración de los objetos y su llamado.

Para declarar los objetos hacemos lo siguiente:

"Declaramos los objetos.
DATA: lo_autos TYPE REF TO lcl_auto,
      lo_motos TYPE REF TO lcl_moto.

"Creamos los objetos.
CREATE OBJECT lo_autos.
CREATE OBJECT lo_motos.

Primero creamos dos variables que van a contener los objetos y después creamos los mismos.

El ultimo paso es la llamada a los métodos de los objetos

CALL METHOD lo_autos->set_notas( 'ALGO Autos' ).
CALL METHOD lo_motos->set_notas( 'ALGO 2 Motos').


WRITE 'TIPO:'.
CALL METHOD lo_autos->obtener_tipo( ).
NEW-LINE.
WRITE 'MARCA:'.
CALL METHOD lo_autos->obtener_marca( ).
NEW-LINE.
WRITE 'NOTAS:'.
CALL METHOD lo_autos->ver_notas( ).
NEW-LINE.
WRITE 'PUERTAS:'.
CALL METHOD lo_autos->cantidad_puertas( ).
NEW-LINE.
WRITE '**************'.
NEW-LINE.
WRITE 'TIPO:'.
CALL METHOD lo_motos->obtener_tipo( ).
NEW-LINE.
WRITE 'MARCA:'.
CALL METHOD lo_motos->obtener_marca( ).
NEW-LINE.
WRITE 'NOTAS:'.
CALL METHOD lo_motos->ver_notas( ).

Esto nos muestra como resultado:

El código completo:

*&---------------------------------------------------------------------*
*& Report ZPOLYTEST2
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT ZPOLYTEST2.

"Ejemplo de encapsulamiento, herencia y polimorfismo en ABAP.

"Definimos una clase abstracta, podriamos llamarla una plantilla
"para las clases heredadas. Cuando definimos una clase como abstracta
"no tiene implementacion.
CLASS lcl_vehiculo DEFINITION ABSTRACT.

  PUBLIC SECTION.
    METHODS: obtener_tipo ABSTRACT,
             obtener_marca ABSTRACT,
             set_notas ABSTRACT
              IMPORTING notas TYPE string,
             ver_notas ABSTRACT
              EXPORTING notas TYPE string.

  PROTECTED SECTION.
    DATA: lv_notas TYPE string.

ENDCLASS.

***********************************************************************
"Definimos una clase lcl_auto que hereda lcl_vehiculo y redefine los
"metodos de la clase padre (lcl_vehiculo).
CLASS lcl_auto DEFINITION
               INHERITING FROM lcl_vehiculo.

  PUBLIC SECTION.
    "Los metodos con REDEFINITION son los originales de lcl_vehiculo.
    "Como podemos ver podemos agregar nuestros propios metodos
    "como por ejemplo cantidad_puertas que pertenece solo a esta clase.
    METHODS: obtener_tipo REDEFINITION,
             obtener_marca REDEFINITION,
             set_notas REDEFINITION,
             ver_notas REDEFINITION,
             cantidad_puertas.

ENDCLASS.

"Implementamos la clase lcl_auto con sus metodos.
CLASS lcl_auto IMPLEMENTATION.

  METHOD obtener_tipo.
    WRITE 'AUTO'.
  ENDMETHOD.

  METHOD obtener_marca.
    WRITE 'FORD'.
  ENDMETHOD.

  METHOD set_notas.
    lv_notas = notas.
  ENDMETHOD.

  METHOD ver_notas.
    WRITE lv_notas.
  ENDMETHOD.

  METHOD cantidad_puertas.
    WRITE '4'.
  ENDMETHOD.

ENDCLASS.

***********************************************************************
"Definimos una clase lcl_moto que hereda lcl_vehiculo y redefine los
"metodos de la clase padre (lcl_vehiculo).
CLASS lcl_moto DEFINITION
               INHERITING FROM lcl_vehiculo.

  PUBLIC SECTION.
    METHODS: obtener_tipo REDEFINITION,
             obtener_marca REDEFINITION,
             set_notas REDEFINITION,
             ver_notas REDEFINITION.

ENDCLASS.

"Implementamos la clase lcl_moto con sus metodos.
CLASS lcl_moto IMPLEMENTATION.

  METHOD obtener_tipo.
    WRITE 'MOTO'.
  ENDMETHOD.

  METHOD obtener_marca.
    WRITE 'HONDA'.
  ENDMETHOD.

  METHOD set_notas.
    lv_notas = notas.
  ENDMETHOD.

  METHOD ver_notas.
    WRITE lv_notas.
  ENDMETHOD.

ENDCLASS.

***********************************************************************
START-OF-SELECTION.

"Declaramos los objetos.
DATA: lo_autos TYPE REF TO lcl_auto,
      lo_motos TYPE REF TO lcl_moto.

"Creamos los objetos.
CREATE OBJECT lo_autos.
CREATE OBJECT lo_motos.

"Llamamos a los metodos que setean las notas.
CALL METHOD lo_autos->set_notas( 'ALGO Autos' ).
CALL METHOD lo_motos->set_notas( 'ALGO 2 Motos').

"Llamamos a los metodos que muestran la informacion.
WRITE 'TIPO:'.
CALL METHOD lo_autos->obtener_tipo( ).
NEW-LINE.
WRITE 'MARCA:'.
CALL METHOD lo_autos->obtener_marca( ).
NEW-LINE.
WRITE 'NOTAS:'.
CALL METHOD lo_autos->ver_notas( ).
NEW-LINE.
WRITE 'PUERTAS:'.
CALL METHOD lo_autos->cantidad_puertas( ).
NEW-LINE.
WRITE '**************'.
NEW-LINE.
WRITE 'TIPO:'.
CALL METHOD lo_motos->obtener_tipo( ).
NEW-LINE.
WRITE 'MARCA:'.
CALL METHOD lo_motos->obtener_marca( ).
NEW-LINE.
WRITE 'NOTAS:'.
CALL METHOD lo_motos->ver_notas( ).

Simple ALV en ABAP

Les comparto una forma de hacer un ALV muy rápido que incluye varias opciones útiles.

Lo primero que tenemos que hacer es crear un reporte, en este caso lo llamaremos ZPRUEBA_ALV.

Con el reporte ya creado, hacemos un select simple como para tener datos para mostrar:

Por ejemplo:

SELECT * FROM sflight
  INTO TABLE @DATA(lt_sflight)
  UP TO 1000 ROWS.
IF sy-subrc = 0.

ENDIF.

Con lo cual nos quedaría de la siguiente forma:

Paso siguiente creamos el código que nos interesa para mostrar el ALV:

Primero declaramos los siguientes objetos:

DATA: lo_table     TYPE REF TO cl_salv_table,
      lo_functions TYPE REF TO cl_salv_functions_list,
      lo_columns   TYPE REF TO cl_salv_columns_table.

Estos objetos usan clases (las que están después de TYPE REF TO) para poder usar el ALV.

Dentro del IF que valida si encontró datos el SELECT agregamos el siguiente código:

  cl_salv_table=>factory( IMPORTING r_salv_table = lo_table
                          CHANGING t_table = lt_sflight ).

  lo_columns = lo_table->get_columns( ).
  lo_columns->set_optimize( abap_true ).
  lo_functions = lo_table->get_functions( ).
  lo_functions->set_all( ).

  lo_table->set_screen_status(
              pfstatus = 'ZSTATUS_GUI'
              report = sy-repid
              set_functions = lo_table->c_functions_all ).

  lo_table->display( ).

La clase cl_salv_table es una clase que contiene métodos relacionados a los ALV, con el método factory lo que hacemos es obtener una nueva instancia del ALV, pasando como parámetros lo_table, que es el que declaramos previamente, y los datos a mostrar (lt_sflight) que es donde guardamos el resultado del SELECT.

lo_columns = lo_table->get_columns( ). Con esta sentencia le indicamos que obtenga las columnas.

lo_columns->set_optimize( abap_true ). Con esta sentencia le indicamos que el ALV ajuste automáticamente el tamaño de las columnas.

lo_functions->set_all( ). Usamos este método para activar todas las funciones genéricas del ALV.

Con la sentencia lo_table->set_screen_status, lo que hacemos es especificarle un STATUS (que crearemos en breve).

Finalmente con el método lo_table->display( ). Mostramos el ALV.

Ahora agregamos el STATUS.

Para esto desde la se38 podemos hacer click en el siguiente icono para abrir el árbol de objetos:

En la ventana de objetos hacemos click derecho Create–>GUI STATUS:

Completamos la pantalla que nos aparece y le damos OK:

Nos aparecerá la siguiente pantalla:

Vamos al menu Extras–>Adjust Template:

Nos aparecerá otra ventana en donde seleccionamos “List Viewer” y le damos OK

Haciendo esto se nos completara todo lo que necesitamos, todos lo que nos resta es activar el GUI STATUS.

El código completo quedaría así:

REPORT zprueba_alv.

DATA: lo_table     TYPE REF TO cl_salv_table,
      lo_functions TYPE REF TO cl_salv_functions_list,
      lo_columns   TYPE REF TO cl_salv_columns_table.

SELECT * FROM sflight
  INTO TABLE @DATA(lt_sflight)
  UP TO 1000 ROWS.
IF sy-subrc = 0.

  cl_salv_table=>factory( IMPORTING r_salv_table = lo_table
                          CHANGING t_table = lt_sflight ).

  lo_columns = lo_table->get_columns( ).
  lo_columns->set_optimize( abap_true ).
  lo_functions = lo_table->get_functions( ).
  lo_functions->set_all( ).

  lo_table->set_screen_status(
              pfstatus = 'ZSTATUS_GUI'
              report = sy-repid
              set_functions = lo_table->c_functions_all ).

  lo_table->display( ).

ENDIF.

Finalmente si ejecutamos nuestro reporte vemos lo siguiente:

Si por ejemplo queremos bajar el listado a un archivo .XLSX, hacemos click en el siguiente botón y seleccionamos como aparece en la pantalla.